diff --git a/docs/file-conventions/remix-config.md b/docs/file-conventions/remix-config.md
index a37bc5b1bbe..ee634410583 100644
--- a/docs/file-conventions/remix-config.md
+++ b/docs/file-conventions/remix-config.md
@@ -19,7 +19,6 @@ module.exports = {
});
},
serverBuildPath: "build/index.js",
- serverBuildTarget: "node-cjs",
};
```
@@ -103,7 +102,8 @@ either a `.js` or `.ts` file extension.
## serverBuildDirectory
This option is deprecated and will likely be removed in a future
-stable release. Use [`serverBuildPath`][server-build-path] instead.
+stable release. Use [`serverBuildPath`][server-build-path]
+instead.
The path to the server build, relative to `remix.config.js`. Defaults to
"build". This needs to be deployed to your server.
@@ -111,13 +111,19 @@ The path to the server build, relative to `remix.config.js`. Defaults to
## serverBuildPath
The path to the server build file, relative to `remix.config.js`. This file
-should end in a `.js` extension and should be deployed to your server.
-
-If omitted, the default build path will be based on your
-[`serverBuildTarget`][server-build-target].
+should end in a `.js` extension and should be deployed to your server. Defaults
+to `"build/index.js"`.
## serverBuildTarget
+This option is deprecated and will likely be removed in a future
+stable release. Use a combination of [`publicPath`][public-path],
+[`serverBuildPath`][server-build-path], [`serverConditions`][server-conditions],
+[`serverDependenciesToBundle`][server-dependencies-to-bundle]
+[`serverMainFields`][server-main-fields], [`serverMinify`][server-minify],
+[`serverModuleFormat`][server-module-format] and/or
+[`serverPlatform`][server-platform] instead.
+
The target of the server build. Defaults to `"node-cjs"`.
The `serverBuildTarget` can be one of the following:
@@ -130,11 +136,22 @@ The `serverBuildTarget` can be one of the following:
- [`"node-cjs"`][node-cjs]
- [`"vercel"`][vercel]
+## serverConditions
+
+The order of conditions to use when resolving server dependencies' `exports`
+field in `package.json`.
+
## serverDependenciesToBundle
-A list of regex patterns that determines if a module is transpiled and included in the server bundle. This can be useful when consuming ESM only packages in a CJS build, or when consuming packages with [CSS side-effect imports][css-side-effect-imports].
+A list of regex patterns that determines if a module is transpiled and included
+in the server bundle. This can be useful when consuming ESM only packages in a
+CJS build, or when consuming packages with [CSS side effect
+imports][css-side-effect-imports].
-For example, the `unified` ecosystem is all ESM-only. Let's also say we're using a `@sindresorhus/slugify` which is ESM-only as well. Here's how you would be able to consume those packages in a CJS app without having to use dynamic imports:
+For example, the `unified` ecosystem is all ESM-only. Let's also say we're using
+a `@sindresorhus/slugify` which is ESM-only as well. Here's how you would be
+able to consume those packages in a CJS app without having to use dynamic
+imports:
```ts filename=remix.config.js lines=[8-13]
/** @type {import('@remix-run/dev').AppConfig} */
@@ -153,6 +170,29 @@ module.exports = {
};
```
+If you want to bundle all server dependencies, you can
+`serverDependenciesToBundle` to `"all"`.
+
+## serverMainFields
+
+The order of main fields to use when resolving server dependencies. Defaults to
+`["main", "module"]` when `serverModuleFormat` is set to `"cjs"`. Defaults to
+`["module", "main"]` when `serverModuleFormat` is set to `"esm"`.
+
+## serverMinify
+
+Whether to minify the server build in production or not. Defaults to `false`.
+
+## serverModuleFormat
+
+The output format of the server build, which can either be `"cjs"` or `"esm"`.
+Defaults to `"cjs"`.
+
+## serverPlatform
+
+The platform the server build is targeting, which can either be `"neutral"` or
+`"node"`. Defaults to `"node"`.
+
## watchPaths
An array, string, or async function that defines custom directories, relative to the project root, to watch while running [remix dev][remix-dev]. These directories are in addition to [`appDirectory`][app-directory].
@@ -173,8 +213,14 @@ There are a few conventions that Remix uses you should be aware of.
[Dilum Sanjaya][dilum-sanjaya] made [an awesome visualization][an-awesome-visualization] of how routes in the file system map to the URL in your app that might help you understand these conventions.
[minimatch]: https://www.npmjs.com/package/minimatch
+[public-path]: #publicpath
[server-build-path]: #serverbuildpath
-[server-build-target]: #serverbuildtarget
+[server-conditions]: #serverconditions
+[server-dependencies-to-bundle]: #serverdependenciestobundle
+[server-main-fields]: #servermainfields
+[server-minify]: #serverminify
+[server-module-format]: #servermoduleformat
+[server-platform]: #serverplatform
[arc]: https://arc.codes
[cloudflare-pages]: https://pages.cloudflare.com
[cloudflare-workers]: https://workers.cloudflare.com
diff --git a/packages/remix-dev/__tests__/readConfig-test.ts b/packages/remix-dev/__tests__/readConfig-test.ts
index 98f71a24c59..eb5de05fa57 100644
--- a/packages/remix-dev/__tests__/readConfig-test.ts
+++ b/packages/remix-dev/__tests__/readConfig-test.ts
@@ -63,8 +63,14 @@ describe("readConfig", () => {
"serverBuildPath": Any,
"serverBuildTarget": undefined,
"serverBuildTargetEntryModule": "export * from \\"@remix-run/dev/server-build\\";",
+ "serverConditions": undefined,
"serverDependenciesToBundle": Array [],
"serverEntryPoint": undefined,
+ "serverMainFields": Array [
+ "main",
+ "module",
+ ],
+ "serverMinify": false,
"serverMode": "production",
"serverModuleFormat": "cjs",
"serverPlatform": "node",
diff --git a/packages/remix-dev/compiler/compilerServer.ts b/packages/remix-dev/compiler/compilerServer.ts
index ec9d3cc5ee1..99caee12f24 100644
--- a/packages/remix-dev/compiler/compilerServer.ts
+++ b/packages/remix-dev/compiler/compilerServer.ts
@@ -45,11 +45,6 @@ const createEsbuildConfig = (
};
}
- let isCloudflareRuntime = ["cloudflare-pages", "cloudflare-workers"].includes(
- config.serverBuildTarget ?? ""
- );
- let isDenoRuntime = config.serverBuildTarget === "deno";
-
let { mode } = options;
let { rootDirectory } = config;
let outputCss = false;
@@ -84,11 +79,7 @@ const createEsbuildConfig = (
stdin,
entryPoints,
outfile: config.serverBuildPath,
- conditions: isCloudflareRuntime
- ? ["worker"]
- : isDenoRuntime
- ? ["deno", "worker"]
- : undefined,
+ conditions: config.serverConditions,
platform: config.serverPlatform,
format: config.serverModuleFormat,
treeShaking: true,
@@ -100,12 +91,8 @@ const createEsbuildConfig = (
// PR makes dev mode behave closer to production in terms of dead
// code elimination / tree shaking is concerned.
minifySyntax: true,
- minify: options.mode === "production" && isCloudflareRuntime,
- mainFields: isCloudflareRuntime
- ? ["browser", "module", "main"]
- : config.serverModuleFormat === "esm"
- ? ["module", "main"]
- : ["main", "module"],
+ minify: options.mode === "production" && config.serverMinify,
+ mainFields: config.serverMainFields,
target: options.target,
loader: loaders,
bundle: true,
diff --git a/packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts b/packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts
index e2c840cd817..1c15aa00e45 100644
--- a/packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts
+++ b/packages/remix-dev/compiler/plugins/serverBareModulesPlugin.ts
@@ -103,12 +103,8 @@ export function serverBareModulesPlugin(
}
}
- switch (remixConfig.serverBuildTarget) {
- // Always bundle everything for cloudflare.
- case "cloudflare-pages":
- case "cloudflare-workers":
- case "deno":
- return undefined;
+ if (remixConfig.serverDependenciesToBundle === "all") {
+ return undefined;
}
for (let pattern of remixConfig.serverDependenciesToBundle) {
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index c41db0e614e..913d3b264eb 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -74,23 +74,6 @@ export interface AppConfig {
defineRoutes: DefineRoutesFunction
) => Promise>;
- /**
- * The path to the server build, relative to `remix.config.js`. Defaults to
- * "build".
- *
- * @deprecated Use {@link ServerConfig.serverBuildPath} instead.
- */
- serverBuildDirectory?: string;
-
- /**
- * The path to the server build file, relative to `remix.config.js`. This file
- * should end in a `.js` extension and should be deployed to your server.
- *
- * If omitted, the default build path will be based on your
- * {@link ServerConfig.serverBuildTarget}.
- */
- serverBuildPath?: string;
-
/**
* The path to the browser build, relative to `remix.config.js`. Defaults to
* "public/build".
@@ -101,7 +84,7 @@ export interface AppConfig {
* The path to the browser build, relative to remix.config.js. Defaults to
* "public/build".
*
- * @deprecated Use `{@link ServerConfig.assetsBuildDirectory}` instead
+ * @deprecated Use `{@link AppConfig.assetsBuildDirectory}` instead
*/
browserBuildDirectory?: string;
@@ -128,45 +111,75 @@ export interface AppConfig {
mdx?: RemixMdxConfig | RemixMdxConfigFunction;
/**
- * The output format of the server build. Defaults to "cjs".
- *
- * @deprecated Use {@link ServerConfig.serverBuildTarget} instead.
+ * A server entrypoint, relative to the root directory that becomes your
+ * server's main module. If specified, Remix will compile this file along with
+ * your application into a single file to be deployed to your server. This
+ * file can use either a `.js` or `.ts` file extension.
*/
- serverModuleFormat?: ServerModuleFormat;
+ server?: string;
/**
- * The platform the server build is targeting. Defaults to "node".
+ * The path to the server build, relative to `remix.config.js`. Defaults to
+ * "build".
*
- * @deprecated Use {@link ServerConfig.serverBuildTarget} instead.
+ * @deprecated Use {@link AppConfig.serverBuildPath} instead.
*/
- serverPlatform?: ServerPlatform;
+ serverBuildDirectory?: string;
/**
- * The target of the server build. Defaults to "node-cjs".
+ * The path to the server build file, relative to `remix.config.js`. This file
+ * should end in a `.js` extension and should be deployed to your server.
*/
- serverBuildTarget?: ServerBuildTarget;
+ serverBuildPath?: string;
/**
- * A server entrypoint, relative to the root directory that becomes your
- * server's main module. If specified, Remix will compile this file along with
- * your application into a single file to be deployed to your server. This
- * file can use either a `.js` or `.ts` file extension.
+ * The target of the server build. Defaults to "node-cjs".
+ *
+ * @deprecated Use a combination of `{@link AppConfig.publicPath}`, `{@link AppConfig.serverBuildPath}`, `{@link AppConfig.serverConditions}`, `{@link AppConfig.serverDependenciesToBundle}`, `{@link AppConfig.serverMainFields}`, `{@link AppConfig.serverMinify}`, `{@link AppConfig.serverModuleFormat}` and/or `{@link AppConfig.serverPlatform}` instead.
*/
- server?: string;
+ serverBuildTarget?: ServerBuildTarget;
/**
- * A list of filenames or a glob patterns to match files in the `app/routes`
- * directory that Remix will ignore. Matching files will not be recognized as
- * routes.
+ * The order of conditions to use when resolving server dependencies'
+ * `exports` field in `package.json`.
*/
- ignoredRouteFiles?: string[];
+ serverConditions?: string[];
/**
* A list of patterns that determined if a module is transpiled and included
* in the server bundle. This can be useful when consuming ESM only packages
* in a CJS build.
*/
- serverDependenciesToBundle?: Array;
+ serverDependenciesToBundle?: "all" | Array;
+
+ /**
+ * The order of main fields to use when resolving server dependencies.
+ * Defaults to `["main", "module"]`.
+ */
+ serverMainFields?: string[];
+
+ /**
+ * Whether to minify the server build in production or not.
+ * Defaults to `false`.
+ */
+ serverMinify?: boolean;
+
+ /**
+ * The output format of the server build. Defaults to "cjs".
+ */
+ serverModuleFormat?: ServerModuleFormat;
+
+ /**
+ * The platform the server build is targeting. Defaults to "node".
+ */
+ serverPlatform?: ServerPlatform;
+
+ /**
+ * A list of filenames or a glob patterns to match files in the `app/routes`
+ * directory that Remix will ignore. Matching files will not be recognized as
+ * routes.
+ */
+ ignoredRouteFiles?: string[];
/**
* A function for defining custom directories to watch while running `remix dev`, in addition to `appDirectory`.
@@ -213,12 +226,6 @@ export interface RemixConfig {
*/
routes: RouteManifest;
- /**
- * The path to the server build file. This file should end in a `.js`. Defaults
- * are based on {@link ServerConfig.serverBuildTarget}.
- */
- serverBuildPath: string;
-
/**
* The absolute path to the assets build directory.
*/
@@ -234,11 +241,6 @@ export interface RemixConfig {
*/
publicPath: string;
- /**
- * The mode to use to run the server.
- */
- serverMode: ServerMode;
-
/**
* The port number to use for the dev (asset) server.
*/
@@ -255,36 +257,67 @@ export interface RemixConfig {
mdx?: RemixMdxConfig | RemixMdxConfigFunction;
/**
- * The output format of the server build. Defaults to "cjs".
- */
- serverModuleFormat: ServerModuleFormat;
-
- /**
- * The platform the server build is targeting. Defaults to "node".
+ * The path to the server build file. This file should end in a `.js`.
*/
- serverPlatform: ServerPlatform;
+ serverBuildPath: string;
/**
- * The target of the server build.
- */
+ * The target of the server build. Defaults to "node-cjs".
+ *
+ * @deprecated Use a combination of `{@link AppConfig.publicPath}`, `{@link AppConfig.serverBuildPath}`, `{@link AppConfig.serverConditions}`, `{@link AppConfig.serverDependenciesToBundle}`, `{@link AppConfig.serverMainFields}`, `{@link AppConfig.serverMinify}`, `{@link AppConfig.serverModuleFormat}` and/or `{@link AppConfig.serverPlatform}` instead. */
serverBuildTarget?: ServerBuildTarget;
/**
- * The default entry module for the server build if a {@see RemixConfig.customServer} is not provided.
+ * The default entry module for the server build if a {@see AppConfig.server}
+ * is not provided.
*/
serverBuildTargetEntryModule: string;
/**
- * A server entrypoint relative to the root directory that becomes your server's main module.
+ * The order of conditions to use when resolving server dependencies'
+ * `exports` field in `package.json`.
*/
- serverEntryPoint?: string;
+ serverConditions?: string[];
/**
* A list of patterns that determined if a module is transpiled and included
* in the server bundle. This can be useful when consuming ESM only packages
* in a CJS build.
*/
- serverDependenciesToBundle: Array;
+ serverDependenciesToBundle: "all" | Array;
+
+ /**
+ * A server entrypoint relative to the root directory that becomes your
+ * server's main module.
+ */
+ serverEntryPoint?: string;
+
+ /**
+ * The order of main fields to use when resolving server dependencies.
+ * Defaults to `["main", "module"]`.
+ */
+ serverMainFields: string[];
+
+ /**
+ * Whether to minify the server build in production or not.
+ * Defaults to `false`.
+ */
+ serverMinify: boolean;
+
+ /**
+ * The mode to use to run the server.
+ */
+ serverMode: ServerMode;
+
+ /**
+ * The output format of the server build. Defaults to "cjs".
+ */
+ serverModuleFormat: ServerModuleFormat;
+
+ /**
+ * The platform the server build is targeting. Defaults to "node".
+ */
+ serverPlatform: ServerPlatform;
/**
* A list of directories to watch.
@@ -340,19 +373,41 @@ export async function readConfig(
}
}
- let customServerEntryPoint = appConfig.server;
- let serverBuildTarget: ServerBuildTarget | undefined =
- appConfig.serverBuildTarget;
+ let isCloudflareRuntime = ["cloudflare-pages", "cloudflare-workers"].includes(
+ appConfig.serverBuildTarget ?? ""
+ );
+ let isDenoRuntime = appConfig.serverBuildTarget === "deno";
+
+ let serverBuildPath = resolveServerBuildPath(rootDirectory, appConfig);
+ let serverBuildTarget = appConfig.serverBuildTarget;
+ let serverBuildTargetEntryModule = `export * from ${JSON.stringify(
+ serverBuildVirtualModule.id
+ )};`;
+ let serverConditions = appConfig.serverConditions;
+ let serverDependenciesToBundle = appConfig.serverDependenciesToBundle || [];
+ let serverEntryPoint = appConfig.server;
+ let serverMainFields = appConfig.serverMainFields || ["main", "module"];
+ let serverMinify = appConfig.serverMinify || false;
let serverModuleFormat: ServerModuleFormat =
appConfig.serverModuleFormat || "cjs";
let serverPlatform: ServerPlatform = appConfig.serverPlatform || "node";
- switch (appConfig.serverBuildTarget) {
- case "cloudflare-pages":
- case "cloudflare-workers":
- case "deno":
- serverModuleFormat = "esm";
- serverPlatform = "neutral";
- break;
+ if (isCloudflareRuntime) {
+ serverConditions = ["worker"];
+ serverDependenciesToBundle = "all";
+ serverMainFields = ["browser", "module", "main"];
+ serverMinify = true;
+ serverModuleFormat = "esm";
+ serverPlatform = "neutral";
+ }
+ if (isDenoRuntime) {
+ serverConditions = ["deno", "worker"];
+ serverDependenciesToBundle = "all";
+ serverMainFields = ["module", "main"];
+ serverModuleFormat = "esm";
+ serverPlatform = "neutral";
+ }
+ if (serverModuleFormat === "esm") {
+ serverMainFields = ["module", "main"];
}
let mdx = appConfig.mdx;
@@ -377,35 +432,6 @@ export async function readConfig(
throw new Error(`Missing "entry.server" file in ${appDirectory}`);
}
- let serverBuildPath = "build/index.js";
- switch (serverBuildTarget) {
- case "arc":
- serverBuildPath = "server/index.js";
- break;
- case "cloudflare-pages":
- serverBuildPath = "functions/[[path]].js";
- break;
- case "netlify":
- serverBuildPath = ".netlify/functions-internal/server.js";
- break;
- case "vercel":
- serverBuildPath = "api/index.js";
- break;
- }
- serverBuildPath = path.resolve(rootDirectory, serverBuildPath);
-
- // retain deprecated behavior for now
- if (appConfig.serverBuildDirectory) {
- serverBuildPath = path.resolve(
- rootDirectory,
- path.join(appConfig.serverBuildDirectory, "index.js")
- );
- }
-
- if (appConfig.serverBuildPath) {
- serverBuildPath = path.resolve(rootDirectory, appConfig.serverBuildPath);
- }
-
let assetsBuildDirectory =
appConfig.assetsBuildDirectory ||
appConfig.browserBuildDirectory ||
@@ -423,12 +449,8 @@ export async function readConfig(
process.env.REMIX_DEV_SERVER_WS_PORT = `${devServerPort}`;
let devServerBroadcastDelay = appConfig.devServerBroadcastDelay || 0;
- let defaultPublicPath = "/build/";
- switch (serverBuildTarget) {
- case "arc":
- defaultPublicPath = "/_static/build/";
- break;
- }
+ let defaultPublicPath =
+ appConfig.serverBuildTarget === "arc" ? "/_static/build/" : "/build/";
let publicPath = addTrailingSlash(appConfig.publicPath || defaultPublicPath);
@@ -477,12 +499,6 @@ export async function readConfig(
);
}
- let serverBuildTargetEntryModule = `export * from ${JSON.stringify(
- serverBuildVirtualModule.id
- )};`;
-
- let serverDependenciesToBundle = appConfig.serverDependenciesToBundle || [];
-
// When tsconfigPath is undefined, the default "tsconfig.json" is not
// found in the root directory.
let tsconfigPath: string | undefined;
@@ -523,13 +539,16 @@ export async function readConfig(
rootDirectory,
routes,
serverBuildPath,
- serverMode,
- serverModuleFormat,
- serverPlatform,
serverBuildTarget,
serverBuildTargetEntryModule,
- serverEntryPoint: customServerEntryPoint,
+ serverConditions,
serverDependenciesToBundle,
+ serverEntryPoint,
+ serverMainFields,
+ serverMinify,
+ serverMode,
+ serverModuleFormat,
+ serverPlatform,
mdx,
watchPaths,
tsconfigPath,
@@ -562,3 +581,36 @@ function findConfig(dir: string, basename: string): string | undefined {
return undefined;
}
+
+const resolveServerBuildPath = (
+ rootDirectory: string,
+ appConfig: AppConfig
+) => {
+ let serverBuildPath = "build/index.js";
+
+ switch (appConfig.serverBuildTarget) {
+ case "arc":
+ serverBuildPath = "server/index.js";
+ break;
+ case "cloudflare-pages":
+ serverBuildPath = "functions/[[path]].js";
+ break;
+ case "netlify":
+ serverBuildPath = ".netlify/functions-internal/server.js";
+ break;
+ case "vercel":
+ serverBuildPath = "api/index.js";
+ break;
+ }
+
+ // retain deprecated behavior for now
+ if (appConfig.serverBuildDirectory) {
+ serverBuildPath = path.join(appConfig.serverBuildDirectory, "index.js");
+ }
+
+ if (appConfig.serverBuildPath) {
+ serverBuildPath = appConfig.serverBuildPath;
+ }
+
+ return path.resolve(rootDirectory, serverBuildPath);
+};