Repository: remix-run/remix
Pull request #4566 (2022-11-10 16:24:11)

feat(compiler): Add support for remix config feature flags
Adds preliminary tests for feature flags in `remix.config`.

While this technically adds compiler support for the `v2_meta` feature flag, the actual implementation for that feature will come in a follow-up PR. I wanted this PR to include a feature to show how we plan to enable opt-in features.

- [ ] Docs
- [x] Tests


Patch:
diff --git a/.changeset/calm-eels-invite.md b/.changeset/calm-eels-invite.md
new file mode 100644
index 000000000..8d1ea67cb
--- /dev/null
+++ b/.changeset/calm-eels-invite.md
@@ -0,0 +1,5 @@
+---
+"@remix-run/dev": patch
+---
+
+Added support for feature flags in `remix.config.js`
diff --git a/contributors.yml b/contributors.yml
index 38fd18edc..1eb55296b 100644
--- a/contributors.yml
+++ b/contributors.yml
@@ -45,6 +45,7 @@
 - awthwathje
 - axel-habermaier
 - BasixKOR
+- benwis
 - BenMcH
 - bgschiller
 - binajmen
diff --git a/packages/remix-dev/compiler/loaders.ts b/packages/remix-dev/compiler/loaders.ts
index 7eccd766b..3f894859d 100644
--- a/packages/remix-dev/compiler/loaders.ts
+++ b/packages/remix-dev/compiler/loaders.ts
@@ -30,6 +30,7 @@ export const loaders: { [ext: string]: esbuild.Loader } = {
   ".ts": "ts",
   ".tsx": "tsx",
   ".ttf": "file",
+  ".wasm": "file",
   ".wav": "file",
   ".webm": "file",
   ".webmanifest": "file",
diff --git a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
index 941c1a90b..56955b4ce 100644
--- a/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
+++ b/packages/remix-dev/compiler/plugins/serverEntryModulePlugin.ts
@@ -45,6 +45,7 @@ ${Object.keys(config.routes)
   export const assetsBuildDirectory = ${JSON.stringify(
     config.relativeAssetsBuildDirectory
   )};
+  export const future = ${JSON.stringify(config.future)};
   export const publicPath = ${JSON.stringify(config.publicPath)};
   export const entry = { module: entryServer };
   export const routes = {
diff --git a/packages/remix-dev/config.ts b/packages/remix-dev/config.ts
index 0a6d3943e..5c8a67ec2 100644
--- a/packages/remix-dev/config.ts
+++ b/packages/remix-dev/config.ts
@@ -31,6 +31,10 @@ export type ServerBuildTarget =
 export type ServerModuleFormat = "esm" | "cjs";
 export type ServerPlatform = "node" | "neutral";
 
+interface FutureConfig {
+  v2_meta: boolean;
+}
+
 /**
  * The user-provided config in `remix.config.js`.
  */
@@ -157,6 +161,8 @@ export interface AppConfig {
     | string
     | string[]
     | (() => Promise<string | string[]> | string | string[]);
+
+  future?: Partial<FutureConfig>;
 }
 
 /**
@@ -275,6 +281,8 @@ export interface RemixConfig {
    * The path for the tsconfig file, if present on the root directory.
    */
   tsconfigPath: string | undefined;
+
+  future: FutureConfig;
 }
 
 /**
@@ -472,6 +480,10 @@ export async function readConfig(
     writeConfigDefaults(tsconfigPath);
   }
 
+  let future = {
+    v2_meta: appConfig.future?.v2_meta === true,
+  };
+
   return {
     appDirectory,
     cacheDirectory,
@@ -495,6 +507,7 @@ export async function readConfig(
     mdx,
     watchPaths,
     tsconfigPath,
+    future,
   };
 }
 
diff --git a/packages/remix-dev/index.ts b/packages/remix-dev/index.ts
index 7876a895d..ba236fd8b 100644
--- a/packages/remix-dev/index.ts
+++ b/packages/remix-dev/index.ts
@@ -1,6 +1,6 @@
 import "./modules";
 
-export type { AppConfig } from "./config";
+export type { AppConfig, RemixConfig as ResolvedRemixConfig } from "./config";
 
 export * as cli from "./cli/index";
 export { createApp } from "./cli/create";
diff --git a/packages/remix-dev/modules.ts b/packages/remix-dev/modules.ts
index 3ce00d5cd..db6c7eba0 100644
--- a/packages/remix-dev/modules.ts
+++ b/packages/remix-dev/modules.ts
@@ -84,6 +84,10 @@ declare module "*.ttf" {
   let asset: string;
   export default asset;
 }
+declare module "*.wasm" {
+  let asset: string;
+  export default asset;
+}
 declare module "*.wav" {
   let asset: string;
   export default asset;
diff --git a/packages/remix-react/components.tsx b/packages/remix-react/components.tsx
index 1c4353816..ced5b54c7 100644
--- a/packages/remix-react/components.tsx
+++ b/packages/remix-react/components.tsx
@@ -24,7 +24,7 @@ import { createPath } from "history";
 import type { SerializeFrom } from "@remix-run/server-runtime";
 
 import type { AppData, FormEncType, FormMethod } from "./data";
-import type { EntryContext, AssetsManifest } from "./entry";
+import type { AssetsManifest, EntryContext, FutureConfig } from "./entry";
 import type { AppState, SerializedError } from "./errors";
 import {
   RemixRootDefaultErrorBoundary,
@@ -71,6 +71,7 @@ interface RemixEntryContextType {
   serverHandoffString?: string;
   clientRoutes: ClientRoute[];
   transitionManager: ReturnType<typeof createTransitionManager>;
+  future: FutureConfig;
 }
 
 export const RemixEntryContext = React.createContext<
@@ -195,6 +196,7 @@ export function RemixEntry({
         routeData: loaderData,
         actionData,
         transitionManager,
+        future: entryContext.future,
       }}
     >
       <RemixErrorBoundary
diff --git a/packages/remix-react/entry.ts b/packages/remix-react/entry.ts
index 77feb17a8..e4cbb36b2 100644
--- a/packages/remix-react/entry.ts
+++ b/packages/remix-react/entry.ts
@@ -12,6 +12,11 @@ export interface EntryContext {
   actionData?: RouteData;
   routeModules: RouteModules;
   serverHandoffString?: string;
+  future: FutureConfig;
+}
+
+export interface FutureConfig {
+  v2_meta: boolean;
 }
 
 export interface AssetsManifest {
diff --git a/packages/remix-server-runtime/build.ts b/packages/remix-server-runtime/build.ts
index 542c9110c..9c01f8258 100644
--- a/packages/remix-server-runtime/build.ts
+++ b/packages/remix-server-runtime/build.ts
@@ -1,5 +1,5 @@
 import type { DataFunctionArgs } from "./routeModules";
-import type { EntryContext, AssetsManifest } from "./entry";
+import type { AssetsManifest, EntryContext, FutureConfig } from "./entry";
 import type { ServerRouteManifest } from "./routes";
 
 /**
@@ -13,6 +13,7 @@ export interface ServerBuild {
   assets: AssetsManifest;
   publicPath: string;
   assetsBuildDirectory: string;
+  future: FutureConfig;
 }
 
 export interface HandleDocumentRequestFunction {
diff --git a/packages/remix-server-runtime/entry.ts b/packages/remix-server-runtime/entry.ts
index 8a555c429..3986e6a04 100644
--- a/packages/remix-server-runtime/entry.ts
+++ b/packages/remix-server-runtime/entry.ts
@@ -17,6 +17,11 @@ export interface EntryContext {
   actionData?: RouteData;
   routeModules: RouteModules<EntryRouteModule>;
   serverHandoffString?: string;
+  future: FutureConfig;
+}
+
+export interface FutureConfig {
+  v2_meta: boolean;
 }
 
 export interface AssetsManifest {
diff --git a/packages/remix-server-runtime/server.ts b/packages/remix-server-runtime/server.ts
index edc3311f6..21eca52ad 100644
--- a/packages/remix-server-runtime/server.ts
+++ b/packages/remix-server-runtime/server.ts
@@ -577,6 +577,7 @@ async function handleDocumentRequest({
     appState: appState,
     matches: entryMatches,
     routeData,
+    future: build.future,
   };
 
   let entryContext: EntryContext = {


Test patch:
diff --git a/packages/remix-dev/__tests__/readConfig-test.ts b/packages/remix-dev/__tests__/readConfig-test.ts
index 5f14c9f4f..a6cdea1f4 100644
--- a/packages/remix-dev/__tests__/readConfig-test.ts
+++ b/packages/remix-dev/__tests__/readConfig-test.ts
@@ -22,6 +22,9 @@ describe("readConfig", () => {
         assetsBuildDirectory: expect.any(String),
         relativeAssetsBuildDirectory: expect.any(String),
         tsconfigPath: expect.any(String),
+        future: {
+          v2_meta: expect.any(Boolean),
+        },
       },
       `
       Object {
@@ -32,6 +35,9 @@ describe("readConfig", () => {
         "devServerPort": Any<Number>,
         "entryClientFile": "entry.client.tsx",
         "entryServerFile": "entry.server.tsx",
+        "future": Object {
+          "v2_meta": Any<Boolean>,
+        },
         "mdx": undefined,
         "publicPath": "/build/",
         "relativeAssetsBuildDirectory": Any<String>,