@react-router/dev 0.0.0-experimental-df0f1dfda → 0.0.0-experimental-fde52e515

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/vite.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @react-router/dev v0.0.0-experimental-df0f1dfda
2
+ * @react-router/dev v0.0.0-experimental-fde52e515
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -52,8 +52,10 @@ var fse = __toESM(require("fs-extra"));
52
52
  var babel = __toESM(require("@babel/core"));
53
53
  var import_react_router2 = require("react-router");
54
54
  var import_es_module_lexer = require("es-module-lexer");
55
+ var import_pick3 = __toESM(require("lodash/pick"));
55
56
  var import_jsesc = __toESM(require("jsesc"));
56
57
  var import_picocolors3 = __toESM(require("picocolors"));
58
+ var import_kebabCase = __toESM(require("lodash/kebabCase"));
57
59
 
58
60
  // typegen/index.ts
59
61
  var import_node_fs2 = __toESM(require("fs"));
@@ -270,6 +272,13 @@ var detectPackageManager = () => {
270
272
 
271
273
  // config/config.ts
272
274
  var excludedConfigPresetKeys = ["presets"];
275
+ var branchRouteProperties = [
276
+ "id",
277
+ "path",
278
+ "file",
279
+ "index"
280
+ ];
281
+ var configRouteToBranchRoute = (configRoute) => (0, import_pick2.default)(configRoute, branchRouteProperties);
273
282
  var mergeReactRouterConfig = (...configs) => {
274
283
  let reducer = (configA, configB) => {
275
284
  let mergeRequired = (key) => configA[key] !== void 0 && configB[key] !== void 0;
@@ -1797,6 +1806,8 @@ var plugin = {
1797
1806
  return function Wrapped() {
1798
1807
  const props = {
1799
1808
  params: useParams(),
1809
+ loaderData: useLoaderData(),
1810
+ actionData: useActionData(),
1800
1811
  };
1801
1812
  return h(HydrateFallback, props);
1802
1813
  };
@@ -2062,16 +2073,24 @@ var getRouteModuleExports = async (viteChildCompiler, ctx, routeFile, readRouteF
2062
2073
  );
2063
2074
  return getExportNames(code);
2064
2075
  };
2065
- var getServerBundleBuildConfig = (viteUserConfig) => {
2066
- if (!("__reactRouterServerBundleBuildConfig" in viteUserConfig) || !viteUserConfig.__reactRouterServerBundleBuildConfig) {
2076
+ var resolveEnvironmentBuildContext = ({
2077
+ viteCommand,
2078
+ viteUserConfig
2079
+ }) => {
2080
+ if (!("__reactRouterEnvironmentBuildContext" in viteUserConfig) || !viteUserConfig.__reactRouterEnvironmentBuildContext) {
2067
2081
  return null;
2068
2082
  }
2069
- return viteUserConfig.__reactRouterServerBundleBuildConfig;
2083
+ let buildContext = viteUserConfig.__reactRouterEnvironmentBuildContext;
2084
+ let resolvedBuildContext = {
2085
+ name: buildContext.name,
2086
+ options: buildContext.resolveOptions({ viteCommand, viteUserConfig })
2087
+ };
2088
+ return resolvedBuildContext;
2070
2089
  };
2071
- var getServerBuildDirectory = (ctx) => path6.join(
2090
+ var getServerBuildDirectory = (ctx, { serverBundleId } = {}) => path6.join(
2072
2091
  ctx.reactRouterConfig.buildDirectory,
2073
2092
  "server",
2074
- ...ctx.serverBundleBuildConfig ? [ctx.serverBundleBuildConfig.serverBundleId] : []
2093
+ ...serverBundleId ? [serverBundleId] : []
2075
2094
  );
2076
2095
  var getClientBuildDirectory = (reactRouterConfig) => path6.join(reactRouterConfig.buildDirectory, "client");
2077
2096
  var defaultEntriesDir = path6.resolve(
@@ -2123,36 +2142,36 @@ var reactRouterVitePlugin = () => {
2123
2142
  process.exit(1);
2124
2143
  }
2125
2144
  let viteManifestEnabled = viteUserConfig.build?.manifest === true;
2126
- let ssrBuildCtx = viteConfigEnv.isSsrBuild && viteCommand === "build" ? {
2127
- isSsrBuild: true,
2128
- getReactRouterServerManifest: async () => (await generateReactRouterManifestsForBuild()).reactRouterServerManifest,
2129
- serverBundleBuildConfig: getServerBundleBuildConfig(viteUserConfig)
2130
- } : { isSsrBuild: false };
2145
+ let environmentBuildContext = viteCommand === "build" ? resolveEnvironmentBuildContext({ viteCommand, viteUserConfig }) : null;
2131
2146
  firstLoad = false;
2132
2147
  ctx = {
2148
+ environmentBuildContext,
2133
2149
  reactRouterConfig,
2134
2150
  rootDirectory,
2135
2151
  entryClientFilePath,
2136
2152
  entryServerFilePath,
2137
2153
  publicPath,
2138
- viteManifestEnabled,
2139
- ...ssrBuildCtx
2154
+ viteManifestEnabled
2140
2155
  };
2141
2156
  };
2142
2157
  let pluginIndex = (pluginName) => {
2143
2158
  invariant(viteConfig);
2144
2159
  return viteConfig.plugins.findIndex((plugin2) => plugin2.name === pluginName);
2145
2160
  };
2146
- let getServerEntry = async () => {
2161
+ let getServerEntry = async ({ routeIds }) => {
2147
2162
  invariant(viteConfig, "viteconfig required to generate the server entry");
2148
- let routes = ctx.serverBundleBuildConfig ? (
2163
+ let routes = routeIds ? (
2149
2164
  // For server bundle builds, the server build should only import the
2150
2165
  // routes for this bundle rather than importing all routes
2151
- ctx.serverBundleBuildConfig.routes
2166
+ (0, import_pick3.default)(ctx.reactRouterConfig.routes, routeIds)
2152
2167
  ) : (
2153
2168
  // Otherwise, all routes are imported as usual
2154
2169
  ctx.reactRouterConfig.routes
2155
2170
  );
2171
+ let prerenderPaths = await getPrerenderPaths(
2172
+ ctx.reactRouterConfig.prerender,
2173
+ routes
2174
+ );
2156
2175
  return `
2157
2176
  import * as entryServer from ${JSON.stringify(
2158
2177
  resolveFileUrl(ctx, ctx.entryServerFilePath)
@@ -2167,7 +2186,7 @@ var reactRouterVitePlugin = () => {
2167
2186
  )};`;
2168
2187
  }).join("\n")}
2169
2188
  export { default as assets } from ${JSON.stringify(
2170
- virtual.serverManifest.id
2189
+ `${virtual.serverManifest.id}${routeIds ? `?route-ids=${routeIds.join(",")}` : ""}`
2171
2190
  )};
2172
2191
  export const assetsBuildDirectory = ${JSON.stringify(
2173
2192
  path6.relative(
@@ -2177,7 +2196,9 @@ var reactRouterVitePlugin = () => {
2177
2196
  )};
2178
2197
  export const basename = ${JSON.stringify(ctx.reactRouterConfig.basename)};
2179
2198
  export const future = ${JSON.stringify(ctx.reactRouterConfig.future)};
2180
- export const isSpaMode = ${!ctx.reactRouterConfig.ssr && ctx.reactRouterConfig.prerender == null};
2199
+ export const ssr = ${ctx.reactRouterConfig.ssr};
2200
+ export const isSpaMode = ${isSpaModeEnabled(ctx.reactRouterConfig)};
2201
+ export const prerender = ${JSON.stringify(prerenderPaths)};
2181
2202
  export const publicPath = ${JSON.stringify(ctx.publicPath)};
2182
2203
  export const entry = { module: entryServer };
2183
2204
  export const routes = {
@@ -2215,7 +2236,9 @@ var reactRouterVitePlugin = () => {
2215
2236
  );
2216
2237
  return /* @__PURE__ */ new Set([...cssUrlPaths, ...chunkAssetPaths]);
2217
2238
  };
2218
- let generateReactRouterManifestsForBuild = async () => {
2239
+ let generateReactRouterManifestsForBuild = async ({
2240
+ routeIds
2241
+ }) => {
2219
2242
  invariant(viteConfig);
2220
2243
  let viteManifest = await loadViteManifest(
2221
2244
  getClientBuildDirectory(ctx.reactRouterConfig)
@@ -2232,9 +2255,9 @@ var reactRouterVitePlugin = () => {
2232
2255
  ctx
2233
2256
  );
2234
2257
  let enforceSplitRouteModules = ctx.reactRouterConfig.future.unstable_splitRouteModules === "enforce";
2235
- for (let [key, route] of Object.entries(ctx.reactRouterConfig.routes)) {
2258
+ for (let route of Object.values(ctx.reactRouterConfig.routes)) {
2236
2259
  let routeFile = path6.join(ctx.reactRouterConfig.appDirectory, route.file);
2237
- let sourceExports = routeManifestExports[key];
2260
+ let sourceExports = routeManifestExports[route.id];
2238
2261
  let isRootRoute = route.parentId === void 0;
2239
2262
  let hasClientAction = sourceExports.includes("clientAction");
2240
2263
  let hasClientLoader = sourceExports.includes("clientLoader");
@@ -2292,10 +2315,9 @@ var reactRouterVitePlugin = () => {
2292
2315
  getRouteChunkModuleId(routeFile, "HydrateFallback")
2293
2316
  ) : void 0
2294
2317
  };
2295
- browserRoutes[key] = routeManifestEntry;
2296
- let serverBundleRoutes = ctx.serverBundleBuildConfig?.routes;
2297
- if (!serverBundleRoutes || serverBundleRoutes[key]) {
2298
- serverRoutes[key] = routeManifestEntry;
2318
+ browserRoutes[route.id] = routeManifestEntry;
2319
+ if (!routeIds || routeIds.includes(route.id)) {
2320
+ serverRoutes[route.id] = routeManifestEntry;
2299
2321
  }
2300
2322
  }
2301
2323
  let fingerprintedValues = { entry, routes: browserRoutes };
@@ -2443,23 +2465,6 @@ var reactRouterVitePlugin = () => {
2443
2465
  ""
2444
2466
  )
2445
2467
  );
2446
- let baseRollupOptions = {
2447
- // Silence Rollup "use client" warnings
2448
- // Adapted from https://github.com/vitejs/vite-plugin-react/pull/144
2449
- onwarn(warning, defaultHandler) {
2450
- if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("use client")) {
2451
- return;
2452
- }
2453
- if (viteUserConfig.build?.rollupOptions?.onwarn) {
2454
- viteUserConfig.build.rollupOptions.onwarn(
2455
- warning,
2456
- defaultHandler
2457
- );
2458
- } else {
2459
- defaultHandler(warning);
2460
- }
2461
- }
2462
- };
2463
2468
  return {
2464
2469
  __reactRouterPluginContext: ctx,
2465
2470
  appType: viteCommand === "serve" && viteConfigEnv.mode === "production" && ctx.reactRouterConfig.ssr === false ? "spa" : "custom",
@@ -2517,73 +2522,11 @@ var reactRouterVitePlugin = () => {
2517
2522
  // will throw an error that the file is not allowed to be read.
2518
2523
  // https://vitejs.dev/config/server-options#server-fs-allow
2519
2524
  server: viteUserConfig.server?.fs?.allow ? { fs: { allow: defaultEntries } } : void 0,
2520
- // Vite config options for building
2521
- ...viteCommand === "build" ? {
2522
- build: {
2523
- cssMinify: viteUserConfig.build?.cssMinify ?? true,
2524
- ...!viteConfigEnv.isSsrBuild ? {
2525
- manifest: true,
2526
- outDir: getClientBuildDirectory(ctx.reactRouterConfig),
2527
- rollupOptions: {
2528
- ...baseRollupOptions,
2529
- preserveEntrySignatures: "exports-only",
2530
- input: [
2531
- ctx.entryClientFilePath,
2532
- ...Object.values(
2533
- ctx.reactRouterConfig.routes
2534
- ).flatMap((route) => {
2535
- let routeFilePath = path6.resolve(
2536
- ctx.reactRouterConfig.appDirectory,
2537
- route.file
2538
- );
2539
- let isRootRoute = route.file === ctx.reactRouterConfig.routes.root.file;
2540
- let code = fse.readFileSync(
2541
- routeFilePath,
2542
- "utf-8"
2543
- );
2544
- return [
2545
- `${routeFilePath}${BUILD_CLIENT_ROUTE_QUERY_STRING}`,
2546
- ...ctx.reactRouterConfig.future.unstable_splitRouteModules && !isRootRoute ? routeChunkExportNames.map(
2547
- (exportName) => code.includes(exportName) ? getRouteChunkModuleId(
2548
- routeFilePath,
2549
- exportName
2550
- ) : null
2551
- ) : []
2552
- ].filter(isNonNullable);
2553
- })
2554
- ]
2555
- }
2556
- } : {
2557
- // We move SSR-only assets to client assets. Note that the
2558
- // SSR build can also emit code-split JS files (e.g. by
2559
- // dynamic import) under the same assets directory
2560
- // regardless of "ssrEmitAssets" option, so we also need to
2561
- // keep these JS files have to be kept as-is.
2562
- ssrEmitAssets: true,
2563
- copyPublicDir: false,
2564
- // Assets in the public directory are only used by the client
2565
- manifest: true,
2566
- // We need the manifest to detect SSR-only assets
2567
- outDir: getServerBuildDirectory(ctx),
2568
- rollupOptions: {
2569
- ...baseRollupOptions,
2570
- preserveEntrySignatures: "exports-only",
2571
- input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id,
2572
- output: {
2573
- entryFileNames: ctx.reactRouterConfig.serverBuildFile,
2574
- format: ctx.reactRouterConfig.serverModuleFormat
2575
- }
2576
- }
2577
- }
2578
- }
2579
- } : void 0,
2580
- // Vite config options for SPA preview mode
2581
- ...viteCommand === "serve" && ctx.reactRouterConfig.ssr === false ? {
2582
- build: {
2583
- manifest: true,
2584
- outDir: getClientBuildDirectory(ctx.reactRouterConfig)
2585
- }
2586
- } : void 0
2525
+ build: ctx.environmentBuildContext?.options.build ?? (await getEnvironmentOptions(
2526
+ ctx,
2527
+ viteConfigEnv.isSsrBuild ? "ssr" : "client",
2528
+ { viteCommand, viteUserConfig }
2529
+ )).build
2587
2530
  };
2588
2531
  },
2589
2532
  async configResolved(resolvedViteConfig) {
@@ -2599,8 +2542,7 @@ var reactRouterVitePlugin = () => {
2599
2542
  let childCompilerConfigFile = await vite2.loadConfigFromFile(
2600
2543
  {
2601
2544
  command: viteConfig.command,
2602
- mode: viteConfig.mode,
2603
- isSsrBuild: ctx.isSsrBuild
2545
+ mode: viteConfig.mode
2604
2546
  },
2605
2547
  viteConfig.configFile
2606
2548
  );
@@ -2742,14 +2684,14 @@ var reactRouterVitePlugin = () => {
2742
2684
  // After the SSR build is finished, we inspect the Vite manifest for
2743
2685
  // the SSR build and move server-only assets to client assets directory
2744
2686
  async handler() {
2745
- if (!ctx.isSsrBuild) {
2687
+ if (!viteConfigEnv.isSsrBuild) {
2746
2688
  return;
2747
2689
  }
2748
2690
  invariant(viteConfig);
2749
2691
  let clientBuildDirectory = getClientBuildDirectory(
2750
2692
  ctx.reactRouterConfig
2751
2693
  );
2752
- let serverBuildDirectory = getServerBuildDirectory(ctx);
2694
+ let serverBuildDirectory = ctx.environmentBuildContext?.options.build.outDir ?? getServerBuildDirectory(ctx);
2753
2695
  let ssrViteManifest = await loadViteManifest(serverBuildDirectory);
2754
2696
  let ssrAssetPaths = getViteManifestAssetPaths(ssrViteManifest);
2755
2697
  let movedAssetPaths = [];
@@ -2783,7 +2725,7 @@ var reactRouterVitePlugin = () => {
2783
2725
  ].join("\n")
2784
2726
  );
2785
2727
  }
2786
- if (ctx.reactRouterConfig.prerender != null && ctx.reactRouterConfig.prerender !== false) {
2728
+ if (isPrerenderingEnabled(ctx.reactRouterConfig)) {
2787
2729
  await handlePrerender(
2788
2730
  viteConfig,
2789
2731
  ctx.reactRouterConfig,
@@ -2938,16 +2880,25 @@ var reactRouterVitePlugin = () => {
2938
2880
  name: "react-router:virtual-modules",
2939
2881
  enforce: "pre",
2940
2882
  resolveId(id) {
2941
- const vmod2 = Object.values(virtual).find((vmod3) => vmod3.id === id);
2942
- if (vmod2) return vmod2.resolvedId;
2883
+ let [baseId, queryString] = id.split("?");
2884
+ const vmod2 = Object.values(virtual).find((vmod3) => vmod3.id === baseId);
2885
+ if (vmod2)
2886
+ return vmod2.resolvedId + (queryString ? `?${queryString}` : "");
2943
2887
  },
2944
2888
  async load(id) {
2945
- switch (id) {
2889
+ let [baseId, queryString] = id.split("?");
2890
+ switch (baseId) {
2946
2891
  case virtual.serverBuild.resolvedId: {
2947
- return await getServerEntry();
2892
+ let searchParams = new URLSearchParams(queryString);
2893
+ let routeIds = searchParams.get("route-ids")?.split(",") || void 0;
2894
+ return await getServerEntry({ routeIds });
2948
2895
  }
2949
2896
  case virtual.serverManifest.resolvedId: {
2950
- let reactRouterManifest = ctx.isSsrBuild ? await ctx.getReactRouterServerManifest() : await getReactRouterManifestForDev();
2897
+ let searchParams = new URLSearchParams(queryString);
2898
+ let routeIds = searchParams.get("route-ids")?.split(",") || void 0;
2899
+ let reactRouterManifest = viteCommand === "build" ? (await generateReactRouterManifestsForBuild({
2900
+ routeIds
2901
+ })).reactRouterServerManifest : await getReactRouterManifestForDev();
2951
2902
  return `export default ${(0, import_jsesc.default)(reactRouterManifest, {
2952
2903
  es6: true
2953
2904
  })};`;
@@ -3046,14 +2997,17 @@ var reactRouterVitePlugin = () => {
3046
2997
  }
3047
2998
  let route = getRoute(ctx.reactRouterConfig, id);
3048
2999
  if (!route) return;
3049
- if (!options?.ssr && !ctx.reactRouterConfig.ssr) {
3000
+ if (!options?.ssr && isSpaModeEnabled(ctx.reactRouterConfig)) {
3050
3001
  let exportNames = getExportNames(code);
3051
- let serverOnlyExports = exportNames.filter(
3052
- (exp) => SERVER_ONLY_ROUTE_EXPORTS.includes(exp)
3053
- );
3002
+ let serverOnlyExports = exportNames.filter((exp) => {
3003
+ if (route.id === "root" && exp === "loader") {
3004
+ return false;
3005
+ }
3006
+ return SERVER_ONLY_ROUTE_EXPORTS.includes(exp);
3007
+ });
3054
3008
  if (serverOnlyExports.length > 0) {
3055
3009
  let str = serverOnlyExports.map((e) => `\`${e}\``).join(", ");
3056
- let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://remix.run/guides/spa-mode for more information.`;
3010
+ let message = `SPA Mode: ${serverOnlyExports.length} invalid route export(s) in \`${route.file}\`: ${str}. See https://reactrouter.com/how-to/spa for more information.`;
3057
3011
  throw Error(message);
3058
3012
  }
3059
3013
  if (route.id !== "root") {
@@ -3061,7 +3015,7 @@ var reactRouterVitePlugin = () => {
3061
3015
  (exp) => exp === "HydrateFallback"
3062
3016
  );
3063
3017
  if (hasHydrateFallback) {
3064
- let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://remix.run/guides/spa-mode for more information.`;
3018
+ let message = `SPA Mode: Invalid \`HydrateFallback\` export found in \`${route.file}\`. \`HydrateFallback\` is only permitted on the root route in SPA Mode. See https://reactrouter.com/how-to/spa for more information.`;
3065
3019
  throw Error(message);
3066
3020
  }
3067
3021
  }
@@ -3354,6 +3308,12 @@ async function getRouteMetadata(cache, ctx, viteChildCompiler, route, readRouteF
3354
3308
  };
3355
3309
  return info;
3356
3310
  }
3311
+ function isPrerenderingEnabled(reactRouterConfig) {
3312
+ return reactRouterConfig.prerender != null && reactRouterConfig.prerender !== false;
3313
+ }
3314
+ function isSpaModeEnabled(reactRouterConfig) {
3315
+ return reactRouterConfig.ssr === false && !isPrerenderingEnabled(reactRouterConfig);
3316
+ }
3357
3317
  async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, serverBuildFile) {
3358
3318
  let serverBuildPath = path6.join(serverBuildDirectory, serverBuildFile);
3359
3319
  let build = await import(url.pathToFileURL(serverBuildPath).toString());
@@ -3364,20 +3324,44 @@ async function getPrerenderBuildAndHandler(viteConfig, serverBuildDirectory, ser
3364
3324
  };
3365
3325
  }
3366
3326
  async function handleSpaMode(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildFile, clientBuildDirectory) {
3367
- let { handler } = await getPrerenderBuildAndHandler(
3327
+ let { build, handler } = await getPrerenderBuildAndHandler(
3368
3328
  viteConfig,
3369
3329
  serverBuildDirectory,
3370
3330
  serverBuildFile
3371
3331
  );
3372
- let request = new Request(`http://localhost${reactRouterConfig.basename}`);
3332
+ let request = new Request(`http://localhost${reactRouterConfig.basename}`, {
3333
+ headers: {
3334
+ // Enable SPA mode in the server runtime and only render down to the root
3335
+ "X-React-Router-SPA-Mode": "yes"
3336
+ }
3337
+ });
3373
3338
  let response = await handler(request);
3374
3339
  let html = await response.text();
3375
- validatePrerenderedResponse(response, html, "SPA Mode", "/");
3376
- validatePrerenderedHtml(html, "SPA Mode");
3377
- await fse.writeFile(path6.join(clientBuildDirectory, "index.html"), html);
3378
- viteConfig.logger.info(
3379
- "SPA Mode: index.html has been written to your " + import_picocolors3.default.bold(path6.relative(process.cwd(), clientBuildDirectory)) + " directory"
3380
- );
3340
+ let isPrerenderSpaFallback = build.prerender.includes("/");
3341
+ let filename3 = isPrerenderSpaFallback ? "__spa-fallback__.html" : "index.html";
3342
+ if (response.status !== 200) {
3343
+ if (isPrerenderSpaFallback) {
3344
+ throw new Error(
3345
+ `Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3346
+ ` + html
3347
+ );
3348
+ } else {
3349
+ throw new Error(
3350
+ `SPA Mode: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering your \`${filename3}\` file.
3351
+ ` + html
3352
+ );
3353
+ }
3354
+ }
3355
+ if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3356
+ throw new Error(
3357
+ "SPA Mode: Did you forget to include `<Scripts/>` in your root route? Your pre-rendered HTML cannot hydrate without `<Scripts />`."
3358
+ );
3359
+ }
3360
+ await fse.writeFile(path6.join(clientBuildDirectory, filename3), html);
3361
+ let prettyDir = path6.relative(process.cwd(), clientBuildDirectory);
3362
+ let prettyPath = path6.join(prettyDir, filename3);
3363
+ let prefix = isPrerenderSpaFallback ? "Prerender" : "SPA Mode";
3364
+ viteConfig.logger.info(`${prefix}: Generated \`${prettyPath}\``);
3381
3365
  }
3382
3366
  async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirectory, serverBuildPath, clientBuildDirectory) {
3383
3367
  let { build, handler } = await getPrerenderBuildAndHandler(
@@ -3386,29 +3370,22 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3386
3370
  serverBuildPath
3387
3371
  );
3388
3372
  let routes = createPrerenderRoutes(build.routes);
3389
- let routesToPrerender;
3390
- if (typeof reactRouterConfig.prerender === "boolean") {
3391
- invariant(reactRouterConfig.prerender, "Expected prerender:true");
3392
- routesToPrerender = determineStaticPrerenderRoutes(
3393
- routes,
3394
- viteConfig,
3395
- true
3396
- );
3397
- } else if (typeof reactRouterConfig.prerender === "function") {
3398
- routesToPrerender = await reactRouterConfig.prerender({
3399
- getStaticPaths: () => determineStaticPrerenderRoutes(routes, viteConfig, false)
3400
- });
3401
- } else {
3402
- routesToPrerender = reactRouterConfig.prerender || ["/"];
3403
- }
3373
+ let prerenderedRoutes = /* @__PURE__ */ new Set();
3404
3374
  let headers = {
3405
3375
  // Header that can be used in the loader to know if you're running at
3406
3376
  // build time or runtime
3407
3377
  "X-React-Router-Prerender": "yes"
3408
3378
  };
3409
- for (let path7 of routesToPrerender) {
3379
+ for (let path7 of build.prerender) {
3410
3380
  let matches = (0, import_react_router2.matchRoutes)(routes, `/${path7}/`.replace(/^\/\/+/, "/"));
3411
- let hasLoaders = matches?.some((m) => m.route.loader);
3381
+ invariant(
3382
+ matches,
3383
+ `Unable to prerender path because it does not match any routes: ${path7}`
3384
+ );
3385
+ matches.forEach((m) => prerenderedRoutes.add(m.route.id));
3386
+ let hasLoaders = matches.some(
3387
+ (m) => build.assets.routes[m.route.id]?.hasLoader
3388
+ );
3412
3389
  let data;
3413
3390
  if (hasLoaders) {
3414
3391
  data = await prerenderData(
@@ -3448,14 +3425,34 @@ async function handlePrerender(viteConfig, reactRouterConfig, serverBuildDirecto
3448
3425
  );
3449
3426
  }
3450
3427
  }
3451
- await prerenderManifest(
3452
- build,
3453
- clientBuildDirectory,
3454
- reactRouterConfig,
3455
- viteConfig
3456
- );
3428
+ if (reactRouterConfig.ssr === false) {
3429
+ let errors = [];
3430
+ for (let [routeId, route] of Object.entries(build.routes)) {
3431
+ let invalidApis = [];
3432
+ if (route) {
3433
+ if (route.module.headers) invalidApis.push("headers");
3434
+ if (route.module.action) invalidApis.push("action");
3435
+ if (invalidApis.length > 0) {
3436
+ errors.push(
3437
+ `Prerender: ${invalidApis.length} invalid route export(s) in \`${route.id}\` when prerendering with \`ssr:false\`: ${invalidApis.join(", ")}. See https://reactrouter.com/how-to/spa for more information.`
3438
+ );
3439
+ }
3440
+ if (route.module.loader && !prerenderedRoutes.has(routeId)) {
3441
+ errors.push(
3442
+ `Prerender: 1 invalid route export in \`${route.id}\` when using \`ssr:false\` with \`prerender\` because the route is never prerendered so the loader will never be called. See https://reactrouter.com/how-to/spa for more information.`
3443
+ );
3444
+ }
3445
+ }
3446
+ }
3447
+ if (errors.length > 0) {
3448
+ viteConfig.logger.error(errors.join("\n"));
3449
+ throw new Error(
3450
+ "Invalid route exports found when prerendering with `ssr:false`"
3451
+ );
3452
+ }
3453
+ }
3457
3454
  }
3458
- function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = false) {
3455
+ function getStaticPrerenderPaths(routes) {
3459
3456
  let paths = ["/"];
3460
3457
  let paramRoutes = [];
3461
3458
  function recurse(subtree, prefix = "") {
@@ -3475,28 +3472,29 @@ function determineStaticPrerenderRoutes(routes, viteConfig, isBooleanUsage = fal
3475
3472
  }
3476
3473
  }
3477
3474
  recurse(routes);
3478
- if (isBooleanUsage && paramRoutes.length > 0) {
3479
- viteConfig.logger.warn(
3480
- [
3481
- "\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`.",
3482
- "You may want to use the `prerender()` API to prerender the following paths:",
3483
- ...paramRoutes.map((p) => " - " + p)
3484
- ].join("\n")
3485
- );
3486
- }
3487
- return paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1"));
3475
+ return {
3476
+ paths: paths.map((p) => p.replace(/\/\/+/g, "/").replace(/(.+)\/$/, "$1")),
3477
+ paramRoutes
3478
+ };
3488
3479
  }
3489
3480
  async function prerenderData(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3490
3481
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath === "/" ? "/_root.data" : `${prerenderPath.replace(/\/$/, "")}.data`}`.replace(/\/\/+/g, "/");
3491
3482
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3492
3483
  let response = await handler(request);
3493
3484
  let data = await response.text();
3494
- validatePrerenderedResponse(response, data, "Prerender", normalizedPath);
3485
+ if (response.status !== 200) {
3486
+ throw new Error(
3487
+ `Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path6}\` path.
3488
+ ${normalizedPath}`
3489
+ );
3490
+ }
3495
3491
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3496
3492
  let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3497
3493
  await fse.ensureDir(path6.dirname(outfile));
3498
3494
  await fse.outputFile(outfile, data);
3499
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3495
+ viteConfig.logger.info(
3496
+ `Prerender Data: ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3497
+ );
3500
3498
  return data;
3501
3499
  }
3502
3500
  async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
@@ -3507,54 +3505,83 @@ async function prerenderRoute(handler, prerenderPath, clientBuildDirectory, reac
3507
3505
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3508
3506
  let response = await handler(request);
3509
3507
  let html = await response.text();
3510
- validatePrerenderedResponse(response, html, "Prerender", normalizedPath);
3511
- if (!reactRouterConfig.ssr) {
3512
- validatePrerenderedHtml(html, "Prerender");
3508
+ if (response.status !== 200) {
3509
+ throw new Error(
3510
+ `Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3511
+ ${html}`
3512
+ );
3513
3513
  }
3514
3514
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3515
3515
  let outfile = path6.join(outdir, ...normalizedPath.split("/"), "index.html");
3516
3516
  await fse.ensureDir(path6.dirname(outfile));
3517
3517
  await fse.outputFile(outfile, html);
3518
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3518
+ viteConfig.logger.info(
3519
+ `Prerender: ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3520
+ );
3519
3521
  }
3520
3522
  async function prerenderResourceRoute(handler, prerenderPath, clientBuildDirectory, reactRouterConfig, viteConfig, requestInit) {
3521
3523
  let normalizedPath = `${reactRouterConfig.basename}${prerenderPath}/`.replace(/\/\/+/g, "/").replace(/\/$/g, "");
3522
3524
  let request = new Request(`http://localhost${normalizedPath}`, requestInit);
3523
3525
  let response = await handler(request);
3524
3526
  let text = await response.text();
3525
- validatePrerenderedResponse(response, text, "Prerender", normalizedPath);
3527
+ if (response.status !== 200) {
3528
+ throw new Error(
3529
+ `Prerender: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${normalizedPath}\` path.
3530
+ ${text}`
3531
+ );
3532
+ }
3526
3533
  let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3527
3534
  let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3528
3535
  await fse.ensureDir(path6.dirname(outfile));
3529
3536
  await fse.outputFile(outfile, text);
3530
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3531
- }
3532
- async function prerenderManifest(build, clientBuildDirectory, reactRouterConfig, viteConfig) {
3533
- let normalizedPath = `${reactRouterConfig.basename}/__manifest`.replace(
3534
- /\/\/+/g,
3535
- "/"
3537
+ viteConfig.logger.info(
3538
+ `Prerender: ${prerenderPath} -> ${import_picocolors3.default.bold(outfile)}`
3536
3539
  );
3537
- let outdir = path6.relative(process.cwd(), clientBuildDirectory);
3538
- let outfile = path6.join(outdir, ...normalizedPath.split("/"));
3539
- await fse.ensureDir(path6.dirname(outfile));
3540
- let manifestData = JSON.stringify(build.assets.routes);
3541
- await fse.outputFile(outfile, manifestData);
3542
- viteConfig.logger.info(`Prerender: Generated ${import_picocolors3.default.bold(outfile)}`);
3543
3540
  }
3544
- function validatePrerenderedResponse(response, html, prefix, path7) {
3545
- if (response.status !== 200) {
3546
- throw new Error(
3547
- `${prefix}: Received a ${response.status} status code from \`entry.server.tsx\` while prerendering the \`${path7}\` path.
3548
- ${html}`
3549
- );
3541
+ async function getPrerenderPaths(prerender, routes) {
3542
+ let prerenderPaths = [];
3543
+ if (prerender != null && prerender !== false) {
3544
+ let prerenderRoutes = createPrerenderRoutes(routes);
3545
+ if (prerender === true) {
3546
+ let { paths, paramRoutes } = getStaticPrerenderPaths(prerenderRoutes);
3547
+ if (paramRoutes.length > 0) {
3548
+ console.warn(
3549
+ import_picocolors3.default.yellow(
3550
+ [
3551
+ "\u26A0\uFE0F Paths with dynamic/splat params cannot be prerendered when using `prerender: true`. You may want to use the `prerender()` API to prerender the following paths:",
3552
+ ...paramRoutes.map((p) => " - " + p)
3553
+ ].join("\n")
3554
+ )
3555
+ );
3556
+ }
3557
+ prerenderPaths = paths;
3558
+ } else if (typeof prerender === "function") {
3559
+ prerenderPaths = await prerender({
3560
+ getStaticPaths: () => getStaticPrerenderPaths(prerenderRoutes).paths
3561
+ });
3562
+ } else {
3563
+ prerenderPaths = prerender || ["/"];
3564
+ }
3550
3565
  }
3566
+ return prerenderPaths;
3551
3567
  }
3552
- function validatePrerenderedHtml(html, prefix) {
3553
- if (!html.includes("window.__reactRouterContext =") || !html.includes("window.__reactRouterRouteModules =")) {
3554
- throw new Error(
3555
- `${prefix}: Did you forget to include <Scripts/> in your root route? Your pre-rendered HTML files cannot hydrate without \`<Scripts />\`.`
3556
- );
3557
- }
3568
+ function createPrerenderRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId2(manifest)) {
3569
+ return (routesByParentId[parentId] || []).map((route) => {
3570
+ let commonRoute = {
3571
+ id: route.id,
3572
+ path: route.path
3573
+ };
3574
+ if (route.index) {
3575
+ return {
3576
+ index: true,
3577
+ ...commonRoute
3578
+ };
3579
+ }
3580
+ return {
3581
+ children: createPrerenderRoutes(manifest, route.id, routesByParentId),
3582
+ ...commonRoute
3583
+ };
3584
+ });
3558
3585
  }
3559
3586
  function groupRoutesByParentId2(manifest) {
3560
3587
  let routes = {};
@@ -3569,26 +3596,54 @@ function groupRoutesByParentId2(manifest) {
3569
3596
  });
3570
3597
  return routes;
3571
3598
  }
3572
- function createPrerenderRoutes(manifest, parentId = "", routesByParentId = groupRoutesByParentId2(manifest)) {
3573
- return (routesByParentId[parentId] || []).map((route) => {
3574
- let commonRoute = {
3575
- // Always include root due to default boundaries
3576
- hasErrorBoundary: route.id === "root" || route.module.ErrorBoundary != null,
3577
- id: route.id,
3578
- path: route.path,
3579
- loader: route.module.loader ? () => null : void 0,
3580
- action: void 0,
3581
- handle: route.module.handle
3582
- };
3583
- return route.index ? {
3584
- index: true,
3585
- ...commonRoute
3586
- } : {
3587
- caseSensitive: route.caseSensitive,
3588
- children: createPrerenderRoutes(manifest, route.id, routesByParentId),
3589
- ...commonRoute
3590
- };
3591
- });
3599
+ function getAddressableRoutes(routes) {
3600
+ let nonAddressableIds = /* @__PURE__ */ new Set();
3601
+ for (let id in routes) {
3602
+ let route = routes[id];
3603
+ if (route.index) {
3604
+ invariant(
3605
+ route.parentId,
3606
+ `Expected index route "${route.id}" to have "parentId" set`
3607
+ );
3608
+ nonAddressableIds.add(route.parentId);
3609
+ }
3610
+ if (typeof route.path !== "string" && !route.index) {
3611
+ nonAddressableIds.add(id);
3612
+ }
3613
+ }
3614
+ return Object.values(routes).filter(
3615
+ (route) => !nonAddressableIds.has(route.id)
3616
+ );
3617
+ }
3618
+ function getRouteBranch(routes, routeId) {
3619
+ let branch = [];
3620
+ let currentRouteId = routeId;
3621
+ while (currentRouteId) {
3622
+ let route = routes[currentRouteId];
3623
+ invariant(route, `Missing route for ${currentRouteId}`);
3624
+ branch.push(route);
3625
+ currentRouteId = route.parentId;
3626
+ }
3627
+ return branch.reverse();
3628
+ }
3629
+ function hasServerBundles(buildManifest) {
3630
+ return Object.keys(buildManifest.serverBundles ?? {}).length > 0;
3631
+ }
3632
+ function getRoutesByServerBundleId(buildManifest) {
3633
+ if (!buildManifest.routeIdToServerBundleId) {
3634
+ return {};
3635
+ }
3636
+ let routesByServerBundleId = {};
3637
+ for (let [routeId, serverBundleId] of Object.entries(
3638
+ buildManifest.routeIdToServerBundleId
3639
+ )) {
3640
+ routesByServerBundleId[serverBundleId] ??= {};
3641
+ let branch = getRouteBranch(buildManifest.routes, routeId);
3642
+ for (let route of branch) {
3643
+ routesByServerBundleId[serverBundleId][route.id] = route;
3644
+ }
3645
+ }
3646
+ return routesByServerBundleId;
3592
3647
  }
3593
3648
  var resolveRouteFileCode = async (ctx, input) => {
3594
3649
  if (typeof input === "string") return input;
@@ -3654,6 +3709,192 @@ function validateRouteChunks({
3654
3709
  ].join("\n\n")
3655
3710
  );
3656
3711
  }
3712
+ async function getBuildManifest(ctx) {
3713
+ let { routes, serverBundles, appDirectory } = ctx.reactRouterConfig;
3714
+ if (!serverBundles) {
3715
+ return { routes };
3716
+ }
3717
+ let { normalizePath } = await import("vite");
3718
+ let serverBuildDirectory = getServerBuildDirectory(ctx);
3719
+ let resolvedAppDirectory = path6.resolve(ctx.rootDirectory, appDirectory);
3720
+ let rootRelativeRoutes = Object.fromEntries(
3721
+ Object.entries(routes).map(([id, route]) => {
3722
+ let filePath = path6.join(resolvedAppDirectory, route.file);
3723
+ let rootRelativeFilePath = normalizePath(
3724
+ path6.relative(ctx.rootDirectory, filePath)
3725
+ );
3726
+ return [id, { ...route, file: rootRelativeFilePath }];
3727
+ })
3728
+ );
3729
+ let buildManifest = {
3730
+ serverBundles: {},
3731
+ routeIdToServerBundleId: {},
3732
+ routes: rootRelativeRoutes
3733
+ };
3734
+ await Promise.all(
3735
+ getAddressableRoutes(routes).map(async (route) => {
3736
+ let branch = getRouteBranch(routes, route.id);
3737
+ let serverBundleId = await serverBundles({
3738
+ branch: branch.map(
3739
+ (route2) => configRouteToBranchRoute({
3740
+ ...route2,
3741
+ // Ensure absolute paths are passed to the serverBundles function
3742
+ file: path6.join(resolvedAppDirectory, route2.file)
3743
+ })
3744
+ )
3745
+ });
3746
+ if (typeof serverBundleId !== "string") {
3747
+ throw new Error(`The "serverBundles" function must return a string`);
3748
+ }
3749
+ if (!/^[a-zA-Z0-9-_]+$/.test(serverBundleId)) {
3750
+ throw new Error(
3751
+ `The "serverBundles" function must only return strings containing alphanumeric characters, hyphens and underscores.`
3752
+ );
3753
+ }
3754
+ buildManifest.routeIdToServerBundleId[route.id] = serverBundleId;
3755
+ buildManifest.serverBundles[serverBundleId] ??= {
3756
+ id: serverBundleId,
3757
+ file: normalizePath(
3758
+ path6.join(
3759
+ path6.relative(
3760
+ ctx.rootDirectory,
3761
+ path6.join(serverBuildDirectory, serverBundleId)
3762
+ ),
3763
+ ctx.reactRouterConfig.serverBuildFile
3764
+ )
3765
+ )
3766
+ };
3767
+ })
3768
+ );
3769
+ return buildManifest;
3770
+ }
3771
+ function mergeBuildOptions(base, overrides) {
3772
+ let vite2 = getVite();
3773
+ return vite2.mergeConfig({ build: base }, { build: overrides }).build;
3774
+ }
3775
+ async function getEnvironmentOptionsResolvers(ctx, buildManifest) {
3776
+ let { serverBuildFile, serverModuleFormat } = ctx.reactRouterConfig;
3777
+ function getBaseBuildOptions({
3778
+ viteUserConfig
3779
+ }) {
3780
+ return {
3781
+ cssMinify: viteUserConfig.build?.cssMinify ?? true,
3782
+ manifest: true,
3783
+ // The manifest is enabled for all builds to detect SSR-only assets
3784
+ rollupOptions: {
3785
+ preserveEntrySignatures: "exports-only",
3786
+ // Silence Rollup "use client" warnings
3787
+ // Adapted from https://github.com/vitejs/vite-plugin-react/pull/144
3788
+ onwarn(warning, defaultHandler) {
3789
+ if (warning.code === "MODULE_LEVEL_DIRECTIVE" && warning.message.includes("use client")) {
3790
+ return;
3791
+ }
3792
+ let userHandler = viteUserConfig.build?.rollupOptions?.onwarn;
3793
+ if (userHandler) {
3794
+ userHandler(warning, defaultHandler);
3795
+ } else {
3796
+ defaultHandler(warning);
3797
+ }
3798
+ }
3799
+ }
3800
+ };
3801
+ }
3802
+ function getBaseServerBuildOptions({
3803
+ viteUserConfig
3804
+ }) {
3805
+ return mergeBuildOptions(getBaseBuildOptions({ viteUserConfig }), {
3806
+ // We move SSR-only assets to client assets. Note that the
3807
+ // SSR build can also emit code-split JS files (e.g. by
3808
+ // dynamic import) under the same assets directory
3809
+ // regardless of "ssrEmitAssets" option, so we also need to
3810
+ // keep these JS files have to be kept as-is.
3811
+ ssrEmitAssets: true,
3812
+ copyPublicDir: false,
3813
+ // Assets in the public directory are only used by the client
3814
+ rollupOptions: {
3815
+ output: {
3816
+ entryFileNames: serverBuildFile,
3817
+ format: serverModuleFormat
3818
+ }
3819
+ }
3820
+ });
3821
+ }
3822
+ let environmentOptionsResolvers = {
3823
+ client: ({ viteUserConfig }) => ({
3824
+ build: mergeBuildOptions(getBaseBuildOptions({ viteUserConfig }), {
3825
+ rollupOptions: {
3826
+ input: [
3827
+ ctx.entryClientFilePath,
3828
+ ...Object.values(ctx.reactRouterConfig.routes).flatMap((route) => {
3829
+ let routeFilePath = path6.resolve(
3830
+ ctx.reactRouterConfig.appDirectory,
3831
+ route.file
3832
+ );
3833
+ let isRootRoute = route.file === ctx.reactRouterConfig.routes.root.file;
3834
+ let code = fse.readFileSync(routeFilePath, "utf-8");
3835
+ return [
3836
+ `${routeFilePath}${BUILD_CLIENT_ROUTE_QUERY_STRING}`,
3837
+ ...ctx.reactRouterConfig.future.unstable_splitRouteModules && !isRootRoute ? routeChunkExportNames.map(
3838
+ (exportName) => code.includes(exportName) ? getRouteChunkModuleId(routeFilePath, exportName) : null
3839
+ ) : []
3840
+ ].filter(isNonNullable);
3841
+ })
3842
+ ],
3843
+ output: {
3844
+ entryFileNames({ moduleIds }) {
3845
+ let routeChunkModuleId = moduleIds.find(isRouteChunkModuleId);
3846
+ let routeChunkName = routeChunkModuleId ? getRouteChunkNameFromModuleId(routeChunkModuleId) : null;
3847
+ let routeChunkSuffix = routeChunkName ? `-${(0, import_kebabCase.default)(routeChunkName)}` : "";
3848
+ return `assets/[name]${routeChunkSuffix}-[hash].js`;
3849
+ }
3850
+ }
3851
+ },
3852
+ outDir: getClientBuildDirectory(ctx.reactRouterConfig)
3853
+ })
3854
+ })
3855
+ };
3856
+ if (hasServerBundles(buildManifest)) {
3857
+ for (let [serverBundleId, routes] of Object.entries(
3858
+ getRoutesByServerBundleId(buildManifest)
3859
+ )) {
3860
+ environmentOptionsResolvers[`ssr-bundle-${serverBundleId}`] = ({
3861
+ viteUserConfig
3862
+ }) => ({
3863
+ build: mergeBuildOptions(
3864
+ getBaseServerBuildOptions({ viteUserConfig }),
3865
+ {
3866
+ outDir: getServerBuildDirectory(ctx, { serverBundleId }),
3867
+ rollupOptions: {
3868
+ input: `${virtual.serverBuild.id}?route-ids=${Object.keys(
3869
+ routes
3870
+ ).join(",")}`
3871
+ }
3872
+ }
3873
+ )
3874
+ });
3875
+ }
3876
+ } else {
3877
+ environmentOptionsResolvers.ssr = ({ viteUserConfig }) => ({
3878
+ build: mergeBuildOptions(getBaseServerBuildOptions({ viteUserConfig }), {
3879
+ outDir: getServerBuildDirectory(ctx),
3880
+ rollupOptions: {
3881
+ input: viteUserConfig.build?.rollupOptions?.input ?? virtual.serverBuild.id
3882
+ }
3883
+ })
3884
+ });
3885
+ }
3886
+ return environmentOptionsResolvers;
3887
+ }
3888
+ async function getEnvironmentOptions(ctx, environmentName, resolverOptions) {
3889
+ let buildManifest = await getBuildManifest(ctx);
3890
+ let environmentResolvers = await getEnvironmentOptionsResolvers(
3891
+ ctx,
3892
+ buildManifest
3893
+ );
3894
+ let resolver = environmentResolvers[environmentName];
3895
+ invariant(resolver, `Missing environment resolver for ${environmentName}`);
3896
+ return resolver(resolverOptions);
3897
+ }
3657
3898
  function isNonNullable(x) {
3658
3899
  return x != null;
3659
3900
  }