Repository: remix-run/remix
Pull request #1071 (2021-12-16 05:20:21)

feat: mainfield config based on serverModuleFormat


Patch:
diff --git a/contributors.yml b/contributors.yml
index 1d14fc314..0d3d6e8f4 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -6,13 +6,16 @@
 - ascorbic
 - ashocean
 - BasixKOR
+- bogas04
 - bruno-oliveira
 - bsharrow
 - chaance
 - christophgockel
 - coryhouse
+- donavon
 - davecalnan
 - derekr
+- donavon
 - developit
 - dunglas
 - dwt47
@@ -81,6 +84,7 @@
 - UsamaHameed
 - veritem
 - VictorPeralta
+- yesmeck
 - weavdale
 - zachdtaylor
 - zainfathoni
diff --git a/fixtures/gists-app/app/root.jsx b/fixtures/gists-app/app/root.jsx
index a4e0de1c8..6ffa060f5 100644
--- a/fixtures/gists-app/app/root.jsx
+++ b/fixtures/gists-app/app/root.jsx
@@ -57,7 +57,7 @@ export default function Root() {
         <header>
           <ol className="breadcrumbs">
             {matches
-              .filter(match => match.handle && match.handle.breadcrumb)
+              .filter(match => match.handle?.breadcrumb)
               .map((match, index) => (
                 <li key={index}>{match.handle.breadcrumb(match)}</li>
               ))}
diff --git a/fixtures/gists-app/app/routes/gists.jsx b/fixtures/gists-app/app/routes/gists.jsx
index e473da158..d41e53091 100644
--- a/fixtures/gists-app/app/routes/gists.jsx
+++ b/fixtures/gists-app/app/routes/gists.jsx
@@ -44,7 +44,7 @@ export default function Gists() {
           {users.map(user => (
             <li key={user.id}>
               <Link
-                prefetch="intent"
+                // prefetch="intent"
                 to={user.id}
                 className="text-blue-700 underline"
               >
diff --git a/fixtures/gists-app/server.js b/fixtures/gists-app/server.js
index 68c2aac25..38dfdf84d 100644
--- a/fixtures/gists-app/server.js
+++ b/fixtures/gists-app/server.js
@@ -5,6 +5,32 @@ const { createRequestHandler } = require("@remix-run/express");
 
 const port = process.env.PORT || 3000;
 
+if (process.env.NODE_ENV === "test") {
+  class MockConsole {
+    assert() {}
+    clear() {}
+    count() {}
+    countReset() {}
+    debug() {}
+    dir() {}
+    dirxml() {}
+    error() {}
+    group() {}
+    groupCollapsed() {}
+    groupEnd() {}
+    info() {}
+    log() {}
+    table() {}
+    time() {}
+    timeEnd() {}
+    timeLog() {}
+    timeStamp() {}
+    trace() {}
+    warn() {}
+  }
+  global.console = new MockConsole();
+}
+
 let app = express();
 
 app.use(compression());
diff --git a/packages/create-remix/templates/netlify/README.md b/packages/create-remix/templates/netlify/README.md
index a3851f619..0c8c7a712 100644
--- a/packages/create-remix/templates/netlify/README.md
+++ b/packages/create-remix/templates/netlify/README.md
@@ -28,12 +28,6 @@ netlify login
 netlify init
 ```
 
-4. You'll need to tell Netlify to use Node 14, as at the time of writing Netlify uses Node 12 by [default](https://docs.netlify.com/functions/build-with-javascript/#runtime-settings)
-
-```sh
-netlify env:set AWS_LAMBDA_JS_RUNTIME nodejs14.x
-```
-
 ## Development
 
 The Netlify CLI starts your app in development mode, rebuilding assets on file changes.
diff --git a/packages/remix-dev/cli/commands.ts b/packages/remix-dev/cli/commands.ts
index 5521ed568..b258dcb33 100644
--- a/packages/remix-dev/cli/commands.ts
+++ b/packages/remix-dev/cli/commands.ts
@@ -105,7 +105,7 @@ export async function watch(
     onInitialBuild,
     onRebuildStart() {
       start = Date.now();
-      onRebuildStart && onRebuildStart();
+      onRebuildStart?.();
       log("Rebuilding...");
     },
     onRebuildFinish() {
diff --git a/packages/remix-dev/compiler.ts b/packages/remix-dev/compiler.ts
index e50d97363..a55926b1e 100644
--- a/packages/remix-dev/compiler.ts
+++ b/packages/remix-dev/compiler.ts
@@ -344,15 +344,19 @@ async function createServerBuild(
       resolveDir: config.serverBuildDirectory
     },
     outfile: path.resolve(config.serverBuildDirectory, "index.js"),
-    platform: "node",
+    platform: config.serverPlatform,
     format: config.serverModuleFormat,
+    mainFields:
+      config.serverModuleFormat === "esm"
+        ? ["module", "main"]
+        : ["main", "module"],
     target: options.target,
     inject: [reactShim],
     loader: loaders,
     bundle: true,
     logLevel: "silent",
     incremental: options.incremental,
-    sourcemap: true,
+    sourcemap: options.sourcemap ? "inline" : false,
     // The server build needs to know how to generate asset URLs for imports
     // of CSS and other files.
     assetNames: "_assets/[name]-[hash]",
@@ -369,7 +373,7 @@ async function createServerBuild(
         // browser build and it's not there yet.
         if (id === "./assets.json" && importer === "<stdin>") return true;
 
-        // Mark all bare imports as external. They will be require()'d at
+        // Mark all bare imports as external. They will be require()'d (or imported if esm) at
         // runtime from node_modules.
         if (isBareModuleId(id)) {
           let packageName = getNpmPackageName(id);
@@ -387,9 +391,14 @@ async function createServerBuild(
             );
           }
 
-          // allow importing css files for bundling / hashing from node_modules.
+          // include .css files from node_modules in the build
+          // so we can get a hashed file name to put into the HTML
           if (id.endsWith(".css")) return false;
 
+          // include "remix" in the build so the server runtime (node) doesn't have to try
+          // to find the magic exports
+          if (packageName === "remix") return false;
+
           return true;
         }
 
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index 7fde8053b..733430371 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -82,7 +82,12 @@ export interface AppConfig {
   /**
    * The output format of the server build. Defaults to "cjs".
    */
-  serverModuleFormat: "esm" | "cjs";
+  serverModuleFormat?: "esm" | "cjs";
+
+  /**
+   * The platform the server build is targeting. Defaults to "node".
+   */
+  serverPlatform?: "node" | "neutral";
 
   /**
    * A list of filenames or a glob patterns to match files in the `app/routes`
@@ -165,6 +170,11 @@ export interface RemixConfig {
    * The output format of the server build. Defaults to "cjs".
    */
   serverModuleFormat: "esm" | "cjs";
+
+  /**
+   * The platform the server build is targeting. Defaults to "node".
+   */
+   serverPlatform: "node" | "neutral";
 }
 
 /**
@@ -194,6 +204,7 @@ export async function readConfig(
   }
 
   let serverModuleFormat = appConfig.serverModuleFormat || "cjs";
+  let serverPlatform = appConfig.serverPlatform || "node";
   let mdx = appConfig.mdx;
 
   let appDirectory = path.resolve(
@@ -273,6 +284,7 @@ export async function readConfig(
     serverBuildDirectory,
     serverMode,
     serverModuleFormat,
+    serverPlatform,
     mdx
   };
 }
diff --git a/packages/remix-node/formData.ts b/packages/remix-node/formData.ts
index 35a166fa9..116a69491 100644
--- a/packages/remix-node/formData.ts
+++ b/packages/remix-node/formData.ts
@@ -58,7 +58,7 @@ class NodeFormData implements FormData {
 
   get(name: string): FormDataEntryValue | null {
     let arr = this._fields[name];
-    return (arr && arr.slice(-1)[0]) || null;
+    return arr?.slice(-1)[0] || null;
   }
 
   getAll(name: string): FormDataEntryValue[] {
diff --git a/packages/remix-node/parseMultipartFormData.ts b/packages/remix-node/parseMultipartFormData.ts
index 889e91538..c73968688 100644
--- a/packages/remix-node/parseMultipartFormData.ts
+++ b/packages/remix-node/parseMultipartFormData.ts
@@ -1,4 +1,4 @@
-import type { Readable } from "stream";
+import { Readable } from "stream";
 import Busboy from "busboy";
 
 import type { Request as NodeRequest } from "./fetch";
@@ -14,13 +14,20 @@ export function parseMultipartFormData(
 
 export async function internalParseFormData(
   contentType: string,
-  stream: Readable,
+  body: string | Buffer | Readable,
   abortController?: AbortController,
   uploadHandler?: UploadHandler
 ) {
   let formData = new NodeFormData();
   let fileWorkQueue: Promise<void>[] = [];
 
+  let stream: Readable;
+  if (typeof body === "string" || Buffer.isBuffer(body)) {
+    stream = Readable.from(body.toString());
+  } else {
+    stream = body;
+  }
+
   await new Promise<void>(async (resolve, reject) => {
     let busboy = new Busboy({
       highWaterMark: 2 * 1024 * 1024,
diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx
index ff908bb85..6869444ad 100644
--- a/packages/remix-react/components.tsx
+++ b/packages/remix-react/components.tsx
@@ -633,13 +633,12 @@ export function Meta() {
 
   return (
     <>
-      {Object.keys(meta).map(name => {
-        let value = meta[name];
+      {Object.entries(meta).map(([name, value]) => {
         // Open Graph tags use the `property` attribute, while other meta tags
         // use `name`. See https://ogp.me/
         let isOpenGraphTag = name.startsWith("og:");
         return name === "title" ? (
-          <title key="title">{meta[name]}</title>
+          <title key="title">{value}</title>
         ) : Array.isArray(value) ? (
           value.map(content =>
             isOpenGraphTag ? (
diff --git a/packages/remix-react/links.ts b/packages/remix-react/links.ts
index d523d564b..4d090e678 100644
--- a/packages/remix-react/links.ts
+++ b/packages/remix-react/links.ts
@@ -171,7 +171,7 @@ export function getLinksForMatches(
   let descriptors = matches
     .map((match): LinkDescriptor[] => {
       let module = routeModules[match.route.id];
-      return (module.links && module.links()) || [];
+      return module.links?.() || [];
     })
     .flat(1);
 
diff --git a/packages/remix-react/routeModules.ts b/packages/remix-react/routeModules.ts
index a52258e6c..bff315475 100644
--- a/packages/remix-react/routeModules.ts
+++ b/packages/remix-react/routeModules.ts
@@ -51,7 +51,7 @@ export interface MetaFunction {
     parentsData: RouteData;
     params: Params;
     location: Location;
-  }): HtmlMetaDescriptor;
+  }): HtmlMetaDescriptor | undefined;
 }
 
 /**
diff --git a/packages/remix-serve/cli.ts b/packages/remix-serve/cli.ts
index de1e622f2..4cae3a74c 100644
--- a/packages/remix-serve/cli.ts
+++ b/packages/remix-serve/cli.ts
@@ -1,8 +1,8 @@
+import "./env";
 import path from "path";
 
 import { createApp } from "./index";
 
-process.env.NODE_ENV = "production";
 let port = process.env.PORT || 3000;
 let buildPathArg = process.argv[2];
 
diff --git a/packages/remix-serve/env.ts b/packages/remix-serve/env.ts
new file mode 100644
index 000000000..f0bae35bd
--- /dev/null
+++ b/packages/remix-serve/env.ts
@@ -0,0 +1,1 @@
+process.env.NODE_ENV = "production";
diff --git a/packages/remix-server-runtime/server.ts b/packages/remix-server-runtime/server.ts
index ea60feeb5..087ca3ea3 100644
--- a/packages/remix-server-runtime/server.ts
+++ b/packages/remix-server-runtime/server.ts
@@ -411,7 +411,7 @@ async function renderDocumentRequest({
     renderableMatches = [];
 
     let root = routes[0];
-    if (root && root.module.CatchBoundary) {
+    if (root?.module.CatchBoundary) {
       appState.catchBoundaryRouteId = "root";
       renderableMatches.push({
         params: {},
diff --git a/packages/remix-server-runtime/sessions.ts b/packages/remix-server-runtime/sessions.ts
index 21c25fb22..d5764cca1 100644
--- a/packages/remix-server-runtime/sessions.ts
+++ b/packages/remix-server-runtime/sessions.ts
@@ -208,7 +208,7 @@ export function createSessionStorage({
 }: SessionIdStorageStrategy): SessionStorage {
   let cookie = isCookie(cookieArg)
     ? cookieArg
-    : createCookie((cookieArg && cookieArg.name) || "__session", cookieArg);
+    : createCookie(cookieArg?.name || "__session", cookieArg);
 
   warnOnceAboutSigningSessionCookie(cookie);
 
diff --git a/packages/remix-server-runtime/sessions/cookieStorage.ts b/packages/remix-server-runtime/sessions/cookieStorage.ts
index 44f63dba1..45e62a0ac 100644
--- a/packages/remix-server-runtime/sessions/cookieStorage.ts
+++ b/packages/remix-server-runtime/sessions/cookieStorage.ts
@@ -24,7 +24,7 @@ export function createCookieSessionStorage({
 }: CookieSessionStorageOptions = {}): SessionStorage {
   let cookie = isCookie(cookieArg)
     ? cookieArg
-    : createCookie((cookieArg && cookieArg.name) || "__session", cookieArg);
+    : createCookie(cookieArg?.name || "__session", cookieArg);
 
   warnOnceAboutSigningSessionCookie(cookie);
 


Test patch:
diff --git a/packages/remix-dev/__tests__/readConfig-test.ts b/packages/remix-dev/__tests__/readConfig-test.ts
index 36173e6d2..dad28fe42 100644
--- a/packages/remix-dev/__tests__/readConfig-test.ts
+++ b/packages/remix-dev/__tests__/readConfig-test.ts
@@ -434,6 +434,7 @@ describe("readConfig", () => {
         "serverBuildDirectory": Any<String>,
         "serverMode": "production",
         "serverModuleFormat": "cjs",
+        "serverPlatform": "node",
       }
     `
     );