vinext 0.0.32 → 0.0.33
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/README.md +7 -6
- package/dist/config/next-config.d.ts +2 -0
- package/dist/config/next-config.js +4 -0
- package/dist/config/next-config.js.map +1 -1
- package/dist/entries/app-browser-entry.js +3 -330
- package/dist/entries/app-browser-entry.js.map +1 -1
- package/dist/entries/app-rsc-entry.js +284 -643
- package/dist/entries/app-rsc-entry.js.map +1 -1
- package/dist/entries/app-ssr-entry.js +4 -460
- package/dist/entries/app-ssr-entry.js.map +1 -1
- package/dist/entries/runtime-entry-module.d.ts +13 -0
- package/dist/entries/runtime-entry-module.js +27 -0
- package/dist/entries/runtime-entry-module.js.map +1 -0
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/plugins/optimize-imports.d.ts +38 -0
- package/dist/plugins/optimize-imports.js +557 -0
- package/dist/plugins/optimize-imports.js.map +1 -0
- package/dist/server/app-browser-entry.d.ts +1 -0
- package/dist/server/app-browser-entry.js +161 -0
- package/dist/server/app-browser-entry.js.map +1 -0
- package/dist/server/app-browser-stream.d.ts +33 -0
- package/dist/server/app-browser-stream.js +54 -0
- package/dist/server/app-browser-stream.js.map +1 -0
- package/dist/server/app-page-cache.d.ts +61 -0
- package/dist/server/app-page-cache.js +133 -0
- package/dist/server/app-page-cache.js.map +1 -0
- package/dist/server/app-page-response.d.ts +51 -0
- package/dist/server/app-page-response.js +90 -0
- package/dist/server/app-page-response.js.map +1 -0
- package/dist/server/app-route-handler-cache.d.ts +42 -0
- package/dist/server/app-route-handler-cache.js +69 -0
- package/dist/server/app-route-handler-cache.js.map +1 -0
- package/dist/server/app-route-handler-execution.d.ts +64 -0
- package/dist/server/app-route-handler-execution.js +100 -0
- package/dist/server/app-route-handler-execution.js.map +1 -0
- package/dist/server/app-route-handler-policy.d.ts +51 -0
- package/dist/server/app-route-handler-policy.js +57 -0
- package/dist/server/app-route-handler-policy.js.map +1 -0
- package/dist/server/app-route-handler-response.d.ts +26 -0
- package/dist/server/app-route-handler-response.js +61 -0
- package/dist/server/app-route-handler-response.js.map +1 -0
- package/dist/server/app-route-handler-runtime.d.ts +27 -0
- package/dist/server/app-route-handler-runtime.js +99 -0
- package/dist/server/app-route-handler-runtime.js.map +1 -0
- package/dist/server/app-ssr-entry.d.ts +19 -0
- package/dist/server/app-ssr-entry.js +105 -0
- package/dist/server/app-ssr-entry.js.map +1 -0
- package/dist/server/app-ssr-stream.d.ts +30 -0
- package/dist/server/app-ssr-stream.js +116 -0
- package/dist/server/app-ssr-stream.js.map +1 -0
- package/dist/shims/metadata.js +3 -3
- package/dist/shims/metadata.js.map +1 -1
- package/dist/shims/request-state-types.d.ts +2 -2
- package/dist/shims/unified-request-context.d.ts +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -480,12 +480,13 @@ Every `next/*` import is shimmed to a Vite-compatible implementation.
|
|
|
480
480
|
|
|
481
481
|
### Configuration
|
|
482
482
|
|
|
483
|
-
| Feature | | Notes
|
|
484
|
-
| ------------------------------------------------ | --- |
|
|
485
|
-
| `next.config.js` / `.ts` / `.mjs` | ✅ | Function configs, phase argument
|
|
486
|
-
| `rewrites` / `redirects` / `headers` | ✅ | All phases, param interpolation
|
|
487
|
-
| Environment variables (`.env*`, `NEXT_PUBLIC_*`) | ✅ | Auto-loads Next.js-style dotenv files; only public vars are inlined
|
|
488
|
-
| `images` config | 🟡 | Parsed but not used for optimization
|
|
483
|
+
| Feature | | Notes |
|
|
484
|
+
| ------------------------------------------------ | --- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
485
|
+
| `next.config.js` / `.ts` / `.mjs` | ✅ | Function configs, phase argument |
|
|
486
|
+
| `rewrites` / `redirects` / `headers` | ✅ | All phases, param interpolation |
|
|
487
|
+
| Environment variables (`.env*`, `NEXT_PUBLIC_*`) | ✅ | Auto-loads Next.js-style dotenv files; only public vars are inlined |
|
|
488
|
+
| `images` config | 🟡 | Parsed but not used for optimization |
|
|
489
|
+
| `experimental.optimizePackageImports` | ✅ | Rewrites barrel imports to direct sub-module imports in RSC/SSR environments. A default set (lucide-react, date-fns, radix-ui, antd, MUI, and others) are always optimized. Add package names here to extend the list. |
|
|
489
490
|
|
|
490
491
|
### Environment variable loading (`.env*`)
|
|
491
492
|
|
|
@@ -161,6 +161,8 @@ interface ResolvedNextConfig {
|
|
|
161
161
|
allowedDevOrigins: string[];
|
|
162
162
|
/** Extra allowed origins for server action CSRF validation (from experimental.serverActions.allowedOrigins). */
|
|
163
163
|
serverActionsAllowedOrigins: string[];
|
|
164
|
+
/** Packages whose barrel imports should be optimized (from experimental.optimizePackageImports). */
|
|
165
|
+
optimizePackageImports: string[];
|
|
164
166
|
/** Parsed body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). Defaults to 1MB. */
|
|
165
167
|
serverActionsBodySizeLimit: number;
|
|
166
168
|
/**
|
|
@@ -180,6 +180,7 @@ async function resolveNextConfig(config, root = process.cwd()) {
|
|
|
180
180
|
aliases: {},
|
|
181
181
|
allowedDevOrigins: [],
|
|
182
182
|
serverActionsAllowedOrigins: [],
|
|
183
|
+
optimizePackageImports: [],
|
|
183
184
|
serverActionsBodySizeLimit: 1 * 1024 * 1024,
|
|
184
185
|
serverExternalPackages: [],
|
|
185
186
|
buildId
|
|
@@ -231,6 +232,8 @@ async function resolveNextConfig(config, root = process.cwd()) {
|
|
|
231
232
|
const serverActionsConfig = experimental?.serverActions;
|
|
232
233
|
const serverActionsAllowedOrigins = Array.isArray(serverActionsConfig?.allowedOrigins) ? serverActionsConfig.allowedOrigins : [];
|
|
233
234
|
const serverActionsBodySizeLimit = parseBodySizeLimit(serverActionsConfig?.bodySizeLimit);
|
|
235
|
+
const rawOptimize = experimental?.optimizePackageImports;
|
|
236
|
+
const optimizePackageImports = Array.isArray(rawOptimize) ? rawOptimize.filter((x) => typeof x === "string") : [];
|
|
234
237
|
const legacyServerComponentsExternal = experimental?.serverComponentsExternalPackages;
|
|
235
238
|
const serverExternalPackages = Array.isArray(config.serverExternalPackages) ? config.serverExternalPackages : Array.isArray(legacyServerComponentsExternal) ? legacyServerComponentsExternal : [];
|
|
236
239
|
if (config.webpack !== void 0) if (mdx || Object.keys(webpackProbe.aliases).length > 0) console.warn("[vinext] next.config option \"webpack\" is only partially supported. vinext preserves resolve.alias entries and MDX loader settings, but other webpack customization is ignored");
|
|
@@ -262,6 +265,7 @@ async function resolveNextConfig(config, root = process.cwd()) {
|
|
|
262
265
|
aliases,
|
|
263
266
|
allowedDevOrigins,
|
|
264
267
|
serverActionsAllowedOrigins,
|
|
268
|
+
optimizePackageImports,
|
|
265
269
|
serverActionsBodySizeLimit,
|
|
266
270
|
serverExternalPackages,
|
|
267
271
|
buildId
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"next-config.js","names":[],"sources":["../../src/config/next-config.ts"],"sourcesContent":["/**\n * next.config.js / next.config.mjs / next.config.ts parser\n *\n * Loads the Next.js config file (if present) and extracts supported options.\n * Unsupported options are logged as warnings.\n */\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport { randomUUID } from \"node:crypto\";\nimport { PHASE_DEVELOPMENT_SERVER } from \"../shims/constants.js\";\nimport { normalizePageExtensions } from \"../routing/file-matcher.js\";\nimport { isExternalUrl } from \"./config-matchers.js\";\n\n/**\n * Parse a body size limit value (string or number) into bytes.\n * Accepts Next.js-style strings like \"1mb\", \"500kb\", \"10mb\", bare number strings like \"1048576\" (bytes),\n * and numeric values. Supports b, kb, mb, gb, tb, pb units.\n * Returns the default 1MB if the value is not provided or invalid.\n * Throws if the parsed value is less than 1.\n */\nexport function parseBodySizeLimit(value: string | number | undefined | null): number {\n if (value === undefined || value === null) return 1 * 1024 * 1024;\n if (typeof value === \"number\") {\n if (value < 1) throw new Error(`Body size limit must be a positive number, got ${value}`);\n return value;\n }\n const trimmed = value.trim();\n const match = trimmed.match(/^(\\d+(?:\\.\\d+)?)\\s*(b|kb|mb|gb|tb|pb)?$/i);\n if (!match) {\n console.warn(\n `[vinext] Invalid bodySizeLimit value: \"${value}\". Expected a number or a string like \"1mb\", \"500kb\". Falling back to 1MB.`,\n );\n return 1 * 1024 * 1024;\n }\n const num = parseFloat(match[1]);\n const unit = (match[2] ?? \"b\").toLowerCase();\n let bytes: number;\n switch (unit) {\n case \"b\":\n bytes = Math.floor(num);\n break;\n case \"kb\":\n bytes = Math.floor(num * 1024);\n break;\n case \"mb\":\n bytes = Math.floor(num * 1024 * 1024);\n break;\n case \"gb\":\n bytes = Math.floor(num * 1024 * 1024 * 1024);\n break;\n case \"tb\":\n bytes = Math.floor(num * 1024 * 1024 * 1024 * 1024);\n break;\n case \"pb\":\n bytes = Math.floor(num * 1024 * 1024 * 1024 * 1024 * 1024);\n break;\n default:\n return 1 * 1024 * 1024;\n }\n if (bytes < 1) throw new Error(`Body size limit must be a positive number, got ${bytes}`);\n return bytes;\n}\n\nexport interface HasCondition {\n type: \"header\" | \"cookie\" | \"query\" | \"host\";\n key: string;\n value?: string;\n}\n\nexport interface NextRedirect {\n source: string;\n destination: string;\n permanent: boolean;\n has?: HasCondition[];\n missing?: HasCondition[];\n}\n\nexport interface NextRewrite {\n source: string;\n destination: string;\n has?: HasCondition[];\n missing?: HasCondition[];\n}\n\nexport interface NextHeader {\n source: string;\n has?: HasCondition[];\n missing?: HasCondition[];\n headers: Array<{ key: string; value: string }>;\n}\n\nexport interface NextI18nConfig {\n /** List of supported locales */\n locales: string[];\n /** The default locale (used when no locale prefix is in the URL) */\n defaultLocale: string;\n /**\n * Whether to auto-detect locale from Accept-Language header.\n * Defaults to true in Next.js.\n */\n localeDetection?: boolean;\n /**\n * Domain-based routing. Each domain maps to a specific locale.\n */\n domains?: Array<{\n domain: string;\n defaultLocale: string;\n locales?: string[];\n http?: boolean;\n }>;\n}\n\n/**\n * MDX compilation options extracted from @next/mdx config.\n * These are passed through to @mdx-js/rollup so that custom\n * remark/rehype/recma plugins configured in next.config work with Vite.\n */\nexport interface MdxOptions {\n remarkPlugins?: unknown[];\n rehypePlugins?: unknown[];\n recmaPlugins?: unknown[];\n}\n\nexport interface NextConfig {\n /** Additional env variables */\n env?: Record<string, string>;\n /** Base URL path prefix */\n basePath?: string;\n /** Whether to add trailing slashes */\n trailingSlash?: boolean;\n /** Internationalization routing config */\n i18n?: NextI18nConfig;\n /** URL redirect rules */\n redirects?: () => Promise<NextRedirect[]> | NextRedirect[];\n /** URL rewrite rules */\n rewrites?: () =>\n | Promise<\n | NextRewrite[]\n | {\n beforeFiles: NextRewrite[];\n afterFiles: NextRewrite[];\n fallback: NextRewrite[];\n }\n >\n | NextRewrite[]\n | {\n beforeFiles: NextRewrite[];\n afterFiles: NextRewrite[];\n fallback: NextRewrite[];\n };\n /** Custom response headers */\n headers?: () => Promise<NextHeader[]> | NextHeader[];\n /** Image optimization config */\n images?: {\n remotePatterns?: Array<{\n protocol?: string;\n hostname: string;\n port?: string;\n pathname?: string;\n search?: string;\n }>;\n domains?: string[];\n unoptimized?: boolean;\n /** Allowed device widths for image optimization. Defaults to Next.js defaults: [640, 750, 828, 1080, 1200, 1920, 2048, 3840] */\n deviceSizes?: number[];\n /** Allowed image sizes for fixed-width images. Defaults to Next.js defaults: [16, 32, 48, 64, 96, 128, 256, 384] */\n imageSizes?: number[];\n /** Allow SVG images through the image optimization endpoint. SVG can contain scripts, so only enable if you trust all image sources. */\n dangerouslyAllowSVG?: boolean;\n /** Content-Disposition header for image responses. Defaults to \"inline\". */\n contentDispositionType?: \"inline\" | \"attachment\";\n /** Content-Security-Policy header for image responses. Defaults to \"script-src 'none'; frame-src 'none'; sandbox;\" */\n contentSecurityPolicy?: string;\n };\n /** Build output mode: 'export' for full static export, 'standalone' for single server */\n output?: \"export\" | \"standalone\";\n /** File extensions treated as routable pages/routes (Next.js pageExtensions) */\n pageExtensions?: string[];\n /** Extra origins allowed to access the dev server. */\n allowedDevOrigins?: string[];\n /**\n * Enable Cache Components (Next.js 16).\n * When true, enables the \"use cache\" directive for pages, components, and functions.\n * Replaces the removed experimental.ppr and experimental.dynamicIO flags.\n */\n cacheComponents?: boolean;\n /** Transpile packages (Vite handles this natively) */\n transpilePackages?: string[];\n /**\n * Packages that should be treated as server-external (not bundled by Vite).\n * Corresponds to Next.js `serverExternalPackages` (or the legacy\n * `experimental.serverComponentsExternalPackages`).\n */\n serverExternalPackages?: string[];\n /** Webpack config (ignored — we use Vite) */\n webpack?: unknown;\n /**\n * Custom build ID generator. If provided, called once at build/dev start.\n * Must return a non-empty string, or null to use the default random ID.\n */\n generateBuildId?: () => string | null | Promise<string | null>;\n /** Any other options */\n [key: string]: unknown;\n}\n\n/**\n * Resolved configuration with all async values awaited.\n */\nexport interface ResolvedNextConfig {\n env: Record<string, string>;\n basePath: string;\n trailingSlash: boolean;\n output: \"\" | \"export\" | \"standalone\";\n pageExtensions: string[];\n cacheComponents: boolean;\n redirects: NextRedirect[];\n rewrites: {\n beforeFiles: NextRewrite[];\n afterFiles: NextRewrite[];\n fallback: NextRewrite[];\n };\n headers: NextHeader[];\n images: NextConfig[\"images\"];\n i18n: NextI18nConfig | null;\n /** MDX remark/rehype/recma plugins extracted from @next/mdx config */\n mdx: MdxOptions | null;\n /** Explicit module aliases preserved from wrapped next.config plugins. */\n aliases: Record<string, string>;\n /** Extra allowed origins for dev server access (from allowedDevOrigins). */\n allowedDevOrigins: string[];\n /** Extra allowed origins for server action CSRF validation (from experimental.serverActions.allowedOrigins). */\n serverActionsAllowedOrigins: string[];\n /** Parsed body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). Defaults to 1MB. */\n serverActionsBodySizeLimit: number;\n /**\n * Packages that should be treated as server-external (not bundled by Vite).\n * Sourced from `serverExternalPackages` or the legacy\n * `experimental.serverComponentsExternalPackages` in next.config.\n */\n serverExternalPackages: string[];\n /** Resolved build ID (from generateBuildId, or a random UUID if not provided). */\n buildId: string;\n}\n\nconst CONFIG_FILES = [\"next.config.ts\", \"next.config.mjs\", \"next.config.js\", \"next.config.cjs\"];\n\n/**\n * Check whether an error indicates a CJS module was loaded in an ESM context\n * (i.e. the file uses `require()` which is not available in ESM).\n */\nfunction isCjsError(e: unknown): boolean {\n if (!(e instanceof Error)) return false;\n const msg = e.message;\n return (\n msg.includes(\"require is not a function\") ||\n msg.includes(\"require is not defined\") ||\n msg.includes(\"exports is not defined\") ||\n msg.includes(\"module is not defined\") ||\n msg.includes(\"__dirname is not defined\") ||\n msg.includes(\"__filename is not defined\")\n );\n}\n\n/**\n * Emit a warning when config loading fails, with a targeted hint for\n * known plugin wrappers that are unnecessary in vinext.\n */\nfunction warnConfigLoadFailure(filename: string, err: Error): void {\n const msg = err.message ?? \"\";\n const stack = err.stack ?? \"\";\n const isNextIntlPlugin =\n msg.includes(\"next-intl\") ||\n stack.includes(\"next-intl/plugin\") ||\n stack.includes(\"next-intl/dist\");\n\n console.log();\n console.error(`[vinext] Failed to load ${filename}: ${msg}`);\n console.log();\n if (isNextIntlPlugin) {\n console.warn(\n \"[vinext] Hint: createNextIntlPlugin() is not needed with vinext. \" +\n \"Remove the next-intl/plugin wrapper from your next.config — \" +\n \"vinext auto-detects next-intl and registers the i18n config alias automatically.\",\n );\n }\n}\n\n/**\n * Unwrap the config value from a loaded module, calling it if it's a\n * function-form config (Next.js supports `module.exports = (phase, opts) => config`).\n */\nasync function unwrapConfig(\n mod: any,\n phase: string = PHASE_DEVELOPMENT_SERVER,\n): Promise<NextConfig> {\n const config = mod.default ?? mod;\n if (typeof config === \"function\") {\n const result = await config(phase, {\n defaultConfig: {},\n });\n return result as NextConfig;\n }\n return config as NextConfig;\n}\n\n/**\n * Find and load the next.config file from the project root.\n * Returns null if no config file is found.\n *\n * Attempts Vite's module runner first so TS configs and extensionless local\n * imports (e.g. `import \"./env\"`) resolve consistently. If loading fails due\n * to CJS constructs (`require`, `module.exports`), falls back to `createRequire`\n * so common CJS plugin wrappers (nextra, @next/mdx, etc.) still work.\n */\nexport async function loadNextConfig(\n root: string,\n phase: string = PHASE_DEVELOPMENT_SERVER,\n): Promise<NextConfig | null> {\n for (const filename of CONFIG_FILES) {\n const configPath = path.join(root, filename);\n if (!fs.existsSync(configPath)) continue;\n\n try {\n // Load config via Vite's module runner (TS + extensionless import support)\n const { runnerImport } = await import(\"vite\");\n const { module: mod } = await runnerImport(configPath, {\n root,\n logLevel: \"error\",\n clearScreen: false,\n });\n return await unwrapConfig(mod, phase);\n } catch (e) {\n // If the error indicates a CJS file loaded in ESM context, retry with\n // createRequire which provides a proper CommonJS environment.\n if (isCjsError(e) && (filename.endsWith(\".js\") || filename.endsWith(\".cjs\"))) {\n try {\n const require = createRequire(path.join(root, \"package.json\"));\n const mod = require(configPath);\n return await unwrapConfig({ default: mod }, phase);\n } catch (e2) {\n warnConfigLoadFailure(filename, e2 as Error);\n throw e2;\n }\n }\n\n warnConfigLoadFailure(filename, e as Error);\n throw e;\n }\n }\n\n return null;\n}\n\n/**\n * Generate a UUID that doesn't contain \"ad\" to avoid false-positive ad-blocker hits.\n * Mirrors Next.js's own nanoid retry loop.\n */\nfunction safeUUID(): string {\n let id = randomUUID();\n while (/ad/i.test(id)) id = randomUUID();\n return id;\n}\n\n/**\n * Call the user's generateBuildId function and validate its return value.\n * Follows Next.js semantics: null return falls back to a random UUID; any\n * other non-string throws. Leading/trailing whitespace is trimmed.\n *\n * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/generateBuildId\n */\nasync function resolveBuildId(\n generate: (() => string | null | Promise<string | null>) | undefined,\n): Promise<string> {\n if (!generate) return safeUUID();\n\n const result = await generate();\n\n if (result === null) return safeUUID();\n\n if (typeof result !== \"string\") {\n throw new Error(\n \"generateBuildId did not return a string. https://nextjs.org/docs/messages/generatebuildid-not-a-string\",\n );\n }\n\n const trimmed = result.trim();\n if (trimmed.length === 0) {\n throw new Error(\n \"generateBuildId returned an empty string. https://nextjs.org/docs/messages/generatebuildid-not-a-string\",\n );\n }\n\n return trimmed;\n}\n\n/**\n * Resolve a NextConfig into a fully-resolved ResolvedNextConfig.\n * Awaits async functions for redirects/rewrites/headers.\n */\nexport async function resolveNextConfig(\n config: NextConfig | null,\n root: string = process.cwd(),\n): Promise<ResolvedNextConfig> {\n if (!config) {\n const buildId = await resolveBuildId(undefined);\n const resolved: ResolvedNextConfig = {\n env: {},\n basePath: \"\",\n trailingSlash: false,\n output: \"\",\n pageExtensions: normalizePageExtensions(),\n cacheComponents: false,\n redirects: [],\n rewrites: { beforeFiles: [], afterFiles: [], fallback: [] },\n headers: [],\n images: undefined,\n i18n: null,\n mdx: null,\n aliases: {},\n allowedDevOrigins: [],\n serverActionsAllowedOrigins: [],\n serverActionsBodySizeLimit: 1 * 1024 * 1024,\n serverExternalPackages: [],\n buildId,\n };\n detectNextIntlConfig(root, resolved);\n return resolved;\n }\n\n // Resolve redirects\n let redirects: NextRedirect[] = [];\n if (config.redirects) {\n const result = await config.redirects();\n redirects = Array.isArray(result) ? result : [];\n }\n\n // Resolve rewrites\n let rewrites = {\n beforeFiles: [] as NextRewrite[],\n afterFiles: [] as NextRewrite[],\n fallback: [] as NextRewrite[],\n };\n if (config.rewrites) {\n const result = await config.rewrites();\n if (Array.isArray(result)) {\n rewrites.afterFiles = result;\n } else {\n rewrites = {\n beforeFiles: result.beforeFiles ?? [],\n afterFiles: result.afterFiles ?? [],\n fallback: result.fallback ?? [],\n };\n }\n }\n\n {\n const allRewrites = [...rewrites.beforeFiles, ...rewrites.afterFiles, ...rewrites.fallback];\n const externalRewrites = allRewrites.filter((rewrite) => isExternalUrl(rewrite.destination));\n\n if (externalRewrites.length > 0) {\n const noun = externalRewrites.length === 1 ? \"external rewrite\" : \"external rewrites\";\n const listing = externalRewrites\n .map((rewrite) => ` ${rewrite.source} → ${rewrite.destination}`)\n .join(\"\\n\");\n\n console.warn(\n `[vinext] Found ${externalRewrites.length} ${noun} that proxy requests to external origins:\\n` +\n `${listing}\\n` +\n `Request headers, including credential headers (cookie, authorization, proxy-authorization, x-api-key), ` +\n `are forwarded to the external origin to match Next.js behavior. ` +\n `If you do not want to forward credentials, use an API route or route handler where you control exactly which headers are sent.`,\n );\n }\n }\n\n // Resolve headers\n let headers: NextHeader[] = [];\n if (config.headers) {\n headers = await config.headers();\n }\n\n // Probe wrapped webpack config once so alias extraction and MDX extraction\n // observe the same mock environment.\n const webpackProbe = await probeWebpackConfig(config, root);\n const mdx = webpackProbe.mdx;\n const aliases = {\n ...extractTurboAliases(config, root),\n ...webpackProbe.aliases,\n };\n\n const allowedDevOrigins = Array.isArray(config.allowedDevOrigins) ? config.allowedDevOrigins : [];\n\n // Resolve serverActions.allowedOrigins and bodySizeLimit from experimental config\n const experimental = config.experimental as Record<string, unknown> | undefined;\n const serverActionsConfig = experimental?.serverActions as Record<string, unknown> | undefined;\n const serverActionsAllowedOrigins = Array.isArray(serverActionsConfig?.allowedOrigins)\n ? (serverActionsConfig.allowedOrigins as string[])\n : [];\n const serverActionsBodySizeLimit = parseBodySizeLimit(\n serverActionsConfig?.bodySizeLimit as string | number | undefined,\n );\n\n // Resolve serverExternalPackages — support the current top-level key and the\n // legacy experimental.serverComponentsExternalPackages name that Next.js still\n // accepts (it moved out of experimental in Next.js 14.2).\n const legacyServerComponentsExternal = experimental?.serverComponentsExternalPackages;\n const serverExternalPackages: string[] = Array.isArray(config.serverExternalPackages)\n ? (config.serverExternalPackages as string[])\n : Array.isArray(legacyServerComponentsExternal)\n ? (legacyServerComponentsExternal as string[])\n : [];\n\n // Warn about unsupported webpack usage. We preserve alias injection and\n // extract MDX settings, but all other webpack customization is still ignored.\n if (config.webpack !== undefined) {\n if (mdx || Object.keys(webpackProbe.aliases).length > 0) {\n console.warn(\n '[vinext] next.config option \"webpack\" is only partially supported. ' +\n \"vinext preserves resolve.alias entries and MDX loader settings, but other webpack customization is ignored\",\n );\n } else {\n console.warn(\n '[vinext] next.config option \"webpack\" is not yet supported and will be ignored',\n );\n }\n }\n\n const output = config.output ?? \"\";\n if (output && output !== \"export\" && output !== \"standalone\") {\n console.warn(`[vinext] Unknown output mode \"${output as string}\", ignoring`);\n }\n\n const pageExtensions = normalizePageExtensions(config.pageExtensions);\n\n // Parse i18n config\n let i18n: NextI18nConfig | null = null;\n if (config.i18n) {\n i18n = {\n locales: config.i18n.locales,\n defaultLocale: config.i18n.defaultLocale,\n localeDetection: config.i18n.localeDetection ?? true,\n domains: config.i18n.domains,\n };\n }\n\n const buildId = await resolveBuildId(\n config.generateBuildId as (() => string | null | Promise<string | null>) | undefined,\n );\n\n const resolved: ResolvedNextConfig = {\n env: config.env ?? {},\n basePath: config.basePath ?? \"\",\n trailingSlash: config.trailingSlash ?? false,\n output: output === \"export\" || output === \"standalone\" ? output : \"\",\n pageExtensions,\n cacheComponents: config.cacheComponents ?? false,\n redirects,\n rewrites,\n headers,\n images: config.images,\n i18n,\n mdx,\n aliases,\n allowedDevOrigins,\n serverActionsAllowedOrigins,\n serverActionsBodySizeLimit,\n serverExternalPackages,\n buildId,\n };\n\n // Auto-detect next-intl (lowest priority — explicit aliases from\n // webpack/turbopack already in `aliases` take precedence)\n detectNextIntlConfig(root, resolved);\n\n return resolved;\n}\n\nfunction normalizeAliasEntries(\n aliases: Record<string, unknown> | undefined,\n root: string,\n): Record<string, string> {\n if (!aliases) return {};\n\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(aliases)) {\n if (typeof value !== \"string\") continue;\n normalized[key] = path.isAbsolute(value) ? value : path.resolve(root, value);\n }\n return normalized;\n}\n\nfunction extractTurboAliases(config: NextConfig, root: string): Record<string, string> {\n const experimental = config.experimental as Record<string, unknown> | undefined;\n const experimentalTurbo = experimental?.turbo as Record<string, unknown> | undefined;\n const topLevelTurbopack = config.turbopack as Record<string, unknown> | undefined;\n\n return {\n ...normalizeAliasEntries(\n experimentalTurbo?.resolveAlias as Record<string, unknown> | undefined,\n root,\n ),\n ...normalizeAliasEntries(\n topLevelTurbopack?.resolveAlias as Record<string, unknown> | undefined,\n root,\n ),\n };\n}\n\nasync function probeWebpackConfig(\n config: NextConfig,\n root: string,\n): Promise<{ aliases: Record<string, string>; mdx: MdxOptions | null }> {\n if (typeof config.webpack !== \"function\") {\n return { aliases: {}, mdx: null };\n }\n\n const mockModuleRules: any[] = [];\n const mockConfig = {\n context: root,\n resolve: { alias: {} as Record<string, unknown> },\n module: { rules: mockModuleRules },\n plugins: [] as any[],\n };\n const mockOptions = {\n defaultLoaders: { babel: { loader: \"next-babel-loader\" } },\n isServer: false,\n dev: false,\n dir: root,\n };\n\n try {\n const result = await (config.webpack as Function)(mockConfig, mockOptions);\n const finalConfig = result ?? mockConfig;\n const rules: any[] = finalConfig.module?.rules ?? mockModuleRules;\n return {\n aliases: normalizeAliasEntries(finalConfig.resolve?.alias, root),\n mdx: extractMdxOptionsFromRules(rules),\n };\n } catch {\n return { aliases: {}, mdx: null };\n }\n}\n\n/**\n * Extract MDX compilation options (remark/rehype/recma plugins) from\n * a Next.js config that uses @next/mdx.\n *\n * @next/mdx wraps the config with a webpack function that injects an MDX\n * loader rule. The remark/rehype plugins are captured in that closure.\n * We probe the webpack function with a mock config to extract them.\n */\nexport async function extractMdxOptions(\n config: NextConfig,\n root: string = process.cwd(),\n): Promise<MdxOptions | null> {\n return (await probeWebpackConfig(config, root)).mdx;\n}\n\n/**\n * Probe file candidates relative to root. Returns the first one that exists,\n * or null if none match.\n */\nfunction probeFiles(root: string, candidates: string[]): string | null {\n for (const candidate of candidates) {\n const abs = path.resolve(root, candidate);\n if (fs.existsSync(abs)) return abs;\n }\n return null;\n}\n\nconst I18N_REQUEST_CANDIDATES = [\n \"i18n/request.ts\",\n \"i18n/request.tsx\",\n \"i18n/request.js\",\n \"i18n/request.jsx\",\n \"src/i18n/request.ts\",\n \"src/i18n/request.tsx\",\n \"src/i18n/request.js\",\n \"src/i18n/request.jsx\",\n];\n\n/**\n * Detect next-intl in the project and auto-register the `next-intl/config`\n * alias if needed.\n *\n * next-intl's `createNextIntlPlugin()` crashes in vinext because it calls\n * `require('next/package.json')` to check the Next.js version. Instead,\n * vinext detects next-intl and registers the alias automatically.\n *\n * Note: `require.resolve('next-intl')` walks up to parent `node_modules`\n * directories via standard Node module resolution. In a monorepo, next-intl\n * installed at the workspace root will trigger detection even if not listed\n * in the project's own package.json. This is acceptable since a workspace-root\n * install implies the user wants it available.\n *\n * Mutates `resolved.aliases` and `resolved.env` in place.\n */\nexport function detectNextIntlConfig(root: string, resolved: ResolvedNextConfig): void {\n // Explicit alias wins — user or plugin already set it\n if (resolved.aliases[\"next-intl/config\"]) return;\n\n // Check if next-intl is installed (use main entry — some packages\n // don't expose ./package.json in their exports map)\n const require = createRequire(path.join(root, \"package.json\"));\n try {\n require.resolve(\"next-intl\");\n } catch {\n return; // next-intl not installed\n }\n\n // Probe for the i18n request config file\n const configPath = probeFiles(root, I18N_REQUEST_CANDIDATES);\n if (!configPath) return;\n\n resolved.aliases[\"next-intl/config\"] = configPath;\n\n if (resolved.trailingSlash) {\n resolved.env._next_intl_trailing_slash = \"true\";\n }\n}\n\nfunction extractMdxOptionsFromRules(rules: any[]): MdxOptions | null {\n // Search through webpack rules for the MDX loader injected by @next/mdx\n for (const rule of rules) {\n const loaders = extractMdxLoaders(rule);\n if (loaders) return loaders;\n }\n return null;\n}\n\n/**\n * Recursively search a webpack rule (which may have nested `oneOf` arrays)\n * for an MDX loader and extract its remark/rehype/recma plugin options.\n */\nfunction extractMdxLoaders(rule: any): MdxOptions | null {\n if (!rule) return null;\n\n // Check `oneOf` arrays (Next.js uses these extensively)\n if (Array.isArray(rule.oneOf)) {\n for (const child of rule.oneOf) {\n const result = extractMdxLoaders(child);\n if (result) return result;\n }\n }\n\n // Check `use` array (loader chain)\n const use = Array.isArray(rule.use) ? rule.use : rule.use ? [rule.use] : [];\n for (const loader of use) {\n const loaderPath = typeof loader === \"string\" ? loader : loader?.loader;\n if (typeof loaderPath === \"string\" && isMdxLoader(loaderPath)) {\n const opts = typeof loader === \"object\" ? loader.options : {};\n return extractPluginsFromOptions(opts);\n }\n }\n\n // Check direct `loader` field\n if (typeof rule.loader === \"string\" && isMdxLoader(rule.loader)) {\n return extractPluginsFromOptions(rule.options);\n }\n\n return null;\n}\n\nfunction isMdxLoader(loaderPath: string): boolean {\n return (\n loaderPath.includes(\"mdx\") &&\n (loaderPath.includes(\"@next\") ||\n loaderPath.includes(\"@mdx-js\") ||\n loaderPath.includes(\"mdx-js-loader\") ||\n loaderPath.includes(\"next-mdx\"))\n );\n}\n\nfunction extractPluginsFromOptions(opts: any): MdxOptions | null {\n if (!opts || typeof opts !== \"object\") return null;\n\n const remarkPlugins = Array.isArray(opts.remarkPlugins) ? opts.remarkPlugins : undefined;\n const rehypePlugins = Array.isArray(opts.rehypePlugins) ? opts.rehypePlugins : undefined;\n const recmaPlugins = Array.isArray(opts.recmaPlugins) ? opts.recmaPlugins : undefined;\n\n // Only return if at least one plugin array is non-empty\n if (\n (remarkPlugins && remarkPlugins.length > 0) ||\n (rehypePlugins && rehypePlugins.length > 0) ||\n (recmaPlugins && recmaPlugins.length > 0)\n ) {\n return {\n ...(remarkPlugins && remarkPlugins.length > 0 ? { remarkPlugins } : {}),\n ...(rehypePlugins && rehypePlugins.length > 0 ? { rehypePlugins } : {}),\n ...(recmaPlugins && recmaPlugins.length > 0 ? { recmaPlugins } : {}),\n };\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"next-config.js","names":[],"sources":["../../src/config/next-config.ts"],"sourcesContent":["/**\n * next.config.js / next.config.mjs / next.config.ts parser\n *\n * Loads the Next.js config file (if present) and extracts supported options.\n * Unsupported options are logged as warnings.\n */\nimport path from \"node:path\";\nimport { createRequire } from \"node:module\";\nimport fs from \"node:fs\";\nimport { randomUUID } from \"node:crypto\";\nimport { PHASE_DEVELOPMENT_SERVER } from \"../shims/constants.js\";\nimport { normalizePageExtensions } from \"../routing/file-matcher.js\";\nimport { isExternalUrl } from \"./config-matchers.js\";\n\n/**\n * Parse a body size limit value (string or number) into bytes.\n * Accepts Next.js-style strings like \"1mb\", \"500kb\", \"10mb\", bare number strings like \"1048576\" (bytes),\n * and numeric values. Supports b, kb, mb, gb, tb, pb units.\n * Returns the default 1MB if the value is not provided or invalid.\n * Throws if the parsed value is less than 1.\n */\nexport function parseBodySizeLimit(value: string | number | undefined | null): number {\n if (value === undefined || value === null) return 1 * 1024 * 1024;\n if (typeof value === \"number\") {\n if (value < 1) throw new Error(`Body size limit must be a positive number, got ${value}`);\n return value;\n }\n const trimmed = value.trim();\n const match = trimmed.match(/^(\\d+(?:\\.\\d+)?)\\s*(b|kb|mb|gb|tb|pb)?$/i);\n if (!match) {\n console.warn(\n `[vinext] Invalid bodySizeLimit value: \"${value}\". Expected a number or a string like \"1mb\", \"500kb\". Falling back to 1MB.`,\n );\n return 1 * 1024 * 1024;\n }\n const num = parseFloat(match[1]);\n const unit = (match[2] ?? \"b\").toLowerCase();\n let bytes: number;\n switch (unit) {\n case \"b\":\n bytes = Math.floor(num);\n break;\n case \"kb\":\n bytes = Math.floor(num * 1024);\n break;\n case \"mb\":\n bytes = Math.floor(num * 1024 * 1024);\n break;\n case \"gb\":\n bytes = Math.floor(num * 1024 * 1024 * 1024);\n break;\n case \"tb\":\n bytes = Math.floor(num * 1024 * 1024 * 1024 * 1024);\n break;\n case \"pb\":\n bytes = Math.floor(num * 1024 * 1024 * 1024 * 1024 * 1024);\n break;\n default:\n return 1 * 1024 * 1024;\n }\n if (bytes < 1) throw new Error(`Body size limit must be a positive number, got ${bytes}`);\n return bytes;\n}\n\nexport interface HasCondition {\n type: \"header\" | \"cookie\" | \"query\" | \"host\";\n key: string;\n value?: string;\n}\n\nexport interface NextRedirect {\n source: string;\n destination: string;\n permanent: boolean;\n has?: HasCondition[];\n missing?: HasCondition[];\n}\n\nexport interface NextRewrite {\n source: string;\n destination: string;\n has?: HasCondition[];\n missing?: HasCondition[];\n}\n\nexport interface NextHeader {\n source: string;\n has?: HasCondition[];\n missing?: HasCondition[];\n headers: Array<{ key: string; value: string }>;\n}\n\nexport interface NextI18nConfig {\n /** List of supported locales */\n locales: string[];\n /** The default locale (used when no locale prefix is in the URL) */\n defaultLocale: string;\n /**\n * Whether to auto-detect locale from Accept-Language header.\n * Defaults to true in Next.js.\n */\n localeDetection?: boolean;\n /**\n * Domain-based routing. Each domain maps to a specific locale.\n */\n domains?: Array<{\n domain: string;\n defaultLocale: string;\n locales?: string[];\n http?: boolean;\n }>;\n}\n\n/**\n * MDX compilation options extracted from @next/mdx config.\n * These are passed through to @mdx-js/rollup so that custom\n * remark/rehype/recma plugins configured in next.config work with Vite.\n */\nexport interface MdxOptions {\n remarkPlugins?: unknown[];\n rehypePlugins?: unknown[];\n recmaPlugins?: unknown[];\n}\n\nexport interface NextConfig {\n /** Additional env variables */\n env?: Record<string, string>;\n /** Base URL path prefix */\n basePath?: string;\n /** Whether to add trailing slashes */\n trailingSlash?: boolean;\n /** Internationalization routing config */\n i18n?: NextI18nConfig;\n /** URL redirect rules */\n redirects?: () => Promise<NextRedirect[]> | NextRedirect[];\n /** URL rewrite rules */\n rewrites?: () =>\n | Promise<\n | NextRewrite[]\n | {\n beforeFiles: NextRewrite[];\n afterFiles: NextRewrite[];\n fallback: NextRewrite[];\n }\n >\n | NextRewrite[]\n | {\n beforeFiles: NextRewrite[];\n afterFiles: NextRewrite[];\n fallback: NextRewrite[];\n };\n /** Custom response headers */\n headers?: () => Promise<NextHeader[]> | NextHeader[];\n /** Image optimization config */\n images?: {\n remotePatterns?: Array<{\n protocol?: string;\n hostname: string;\n port?: string;\n pathname?: string;\n search?: string;\n }>;\n domains?: string[];\n unoptimized?: boolean;\n /** Allowed device widths for image optimization. Defaults to Next.js defaults: [640, 750, 828, 1080, 1200, 1920, 2048, 3840] */\n deviceSizes?: number[];\n /** Allowed image sizes for fixed-width images. Defaults to Next.js defaults: [16, 32, 48, 64, 96, 128, 256, 384] */\n imageSizes?: number[];\n /** Allow SVG images through the image optimization endpoint. SVG can contain scripts, so only enable if you trust all image sources. */\n dangerouslyAllowSVG?: boolean;\n /** Content-Disposition header for image responses. Defaults to \"inline\". */\n contentDispositionType?: \"inline\" | \"attachment\";\n /** Content-Security-Policy header for image responses. Defaults to \"script-src 'none'; frame-src 'none'; sandbox;\" */\n contentSecurityPolicy?: string;\n };\n /** Build output mode: 'export' for full static export, 'standalone' for single server */\n output?: \"export\" | \"standalone\";\n /** File extensions treated as routable pages/routes (Next.js pageExtensions) */\n pageExtensions?: string[];\n /** Extra origins allowed to access the dev server. */\n allowedDevOrigins?: string[];\n /**\n * Enable Cache Components (Next.js 16).\n * When true, enables the \"use cache\" directive for pages, components, and functions.\n * Replaces the removed experimental.ppr and experimental.dynamicIO flags.\n */\n cacheComponents?: boolean;\n /** Transpile packages (Vite handles this natively) */\n transpilePackages?: string[];\n /**\n * Packages that should be treated as server-external (not bundled by Vite).\n * Corresponds to Next.js `serverExternalPackages` (or the legacy\n * `experimental.serverComponentsExternalPackages`).\n */\n serverExternalPackages?: string[];\n /** Webpack config (ignored — we use Vite) */\n webpack?: unknown;\n /**\n * Custom build ID generator. If provided, called once at build/dev start.\n * Must return a non-empty string, or null to use the default random ID.\n */\n generateBuildId?: () => string | null | Promise<string | null>;\n /** Any other options */\n [key: string]: unknown;\n}\n\n/**\n * Resolved configuration with all async values awaited.\n */\nexport interface ResolvedNextConfig {\n env: Record<string, string>;\n basePath: string;\n trailingSlash: boolean;\n output: \"\" | \"export\" | \"standalone\";\n pageExtensions: string[];\n cacheComponents: boolean;\n redirects: NextRedirect[];\n rewrites: {\n beforeFiles: NextRewrite[];\n afterFiles: NextRewrite[];\n fallback: NextRewrite[];\n };\n headers: NextHeader[];\n images: NextConfig[\"images\"];\n i18n: NextI18nConfig | null;\n /** MDX remark/rehype/recma plugins extracted from @next/mdx config */\n mdx: MdxOptions | null;\n /** Explicit module aliases preserved from wrapped next.config plugins. */\n aliases: Record<string, string>;\n /** Extra allowed origins for dev server access (from allowedDevOrigins). */\n allowedDevOrigins: string[];\n /** Extra allowed origins for server action CSRF validation (from experimental.serverActions.allowedOrigins). */\n serverActionsAllowedOrigins: string[];\n /** Packages whose barrel imports should be optimized (from experimental.optimizePackageImports). */\n optimizePackageImports: string[];\n /** Parsed body size limit for server actions in bytes (from experimental.serverActions.bodySizeLimit). Defaults to 1MB. */\n serverActionsBodySizeLimit: number;\n /**\n * Packages that should be treated as server-external (not bundled by Vite).\n * Sourced from `serverExternalPackages` or the legacy\n * `experimental.serverComponentsExternalPackages` in next.config.\n */\n serverExternalPackages: string[];\n /** Resolved build ID (from generateBuildId, or a random UUID if not provided). */\n buildId: string;\n}\n\nconst CONFIG_FILES = [\"next.config.ts\", \"next.config.mjs\", \"next.config.js\", \"next.config.cjs\"];\n\n/**\n * Check whether an error indicates a CJS module was loaded in an ESM context\n * (i.e. the file uses `require()` which is not available in ESM).\n */\nfunction isCjsError(e: unknown): boolean {\n if (!(e instanceof Error)) return false;\n const msg = e.message;\n return (\n msg.includes(\"require is not a function\") ||\n msg.includes(\"require is not defined\") ||\n msg.includes(\"exports is not defined\") ||\n msg.includes(\"module is not defined\") ||\n msg.includes(\"__dirname is not defined\") ||\n msg.includes(\"__filename is not defined\")\n );\n}\n\n/**\n * Emit a warning when config loading fails, with a targeted hint for\n * known plugin wrappers that are unnecessary in vinext.\n */\nfunction warnConfigLoadFailure(filename: string, err: Error): void {\n const msg = err.message ?? \"\";\n const stack = err.stack ?? \"\";\n const isNextIntlPlugin =\n msg.includes(\"next-intl\") ||\n stack.includes(\"next-intl/plugin\") ||\n stack.includes(\"next-intl/dist\");\n\n console.log();\n console.error(`[vinext] Failed to load ${filename}: ${msg}`);\n console.log();\n if (isNextIntlPlugin) {\n console.warn(\n \"[vinext] Hint: createNextIntlPlugin() is not needed with vinext. \" +\n \"Remove the next-intl/plugin wrapper from your next.config — \" +\n \"vinext auto-detects next-intl and registers the i18n config alias automatically.\",\n );\n }\n}\n\n/**\n * Unwrap the config value from a loaded module, calling it if it's a\n * function-form config (Next.js supports `module.exports = (phase, opts) => config`).\n */\nasync function unwrapConfig(\n mod: any,\n phase: string = PHASE_DEVELOPMENT_SERVER,\n): Promise<NextConfig> {\n const config = mod.default ?? mod;\n if (typeof config === \"function\") {\n const result = await config(phase, {\n defaultConfig: {},\n });\n return result as NextConfig;\n }\n return config as NextConfig;\n}\n\n/**\n * Find and load the next.config file from the project root.\n * Returns null if no config file is found.\n *\n * Attempts Vite's module runner first so TS configs and extensionless local\n * imports (e.g. `import \"./env\"`) resolve consistently. If loading fails due\n * to CJS constructs (`require`, `module.exports`), falls back to `createRequire`\n * so common CJS plugin wrappers (nextra, @next/mdx, etc.) still work.\n */\nexport async function loadNextConfig(\n root: string,\n phase: string = PHASE_DEVELOPMENT_SERVER,\n): Promise<NextConfig | null> {\n for (const filename of CONFIG_FILES) {\n const configPath = path.join(root, filename);\n if (!fs.existsSync(configPath)) continue;\n\n try {\n // Load config via Vite's module runner (TS + extensionless import support)\n const { runnerImport } = await import(\"vite\");\n const { module: mod } = await runnerImport(configPath, {\n root,\n logLevel: \"error\",\n clearScreen: false,\n });\n return await unwrapConfig(mod, phase);\n } catch (e) {\n // If the error indicates a CJS file loaded in ESM context, retry with\n // createRequire which provides a proper CommonJS environment.\n if (isCjsError(e) && (filename.endsWith(\".js\") || filename.endsWith(\".cjs\"))) {\n try {\n const require = createRequire(path.join(root, \"package.json\"));\n const mod = require(configPath);\n return await unwrapConfig({ default: mod }, phase);\n } catch (e2) {\n warnConfigLoadFailure(filename, e2 as Error);\n throw e2;\n }\n }\n\n warnConfigLoadFailure(filename, e as Error);\n throw e;\n }\n }\n\n return null;\n}\n\n/**\n * Generate a UUID that doesn't contain \"ad\" to avoid false-positive ad-blocker hits.\n * Mirrors Next.js's own nanoid retry loop.\n */\nfunction safeUUID(): string {\n let id = randomUUID();\n while (/ad/i.test(id)) id = randomUUID();\n return id;\n}\n\n/**\n * Call the user's generateBuildId function and validate its return value.\n * Follows Next.js semantics: null return falls back to a random UUID; any\n * other non-string throws. Leading/trailing whitespace is trimmed.\n *\n * @see https://nextjs.org/docs/app/api-reference/config/next-config-js/generateBuildId\n */\nasync function resolveBuildId(\n generate: (() => string | null | Promise<string | null>) | undefined,\n): Promise<string> {\n if (!generate) return safeUUID();\n\n const result = await generate();\n\n if (result === null) return safeUUID();\n\n if (typeof result !== \"string\") {\n throw new Error(\n \"generateBuildId did not return a string. https://nextjs.org/docs/messages/generatebuildid-not-a-string\",\n );\n }\n\n const trimmed = result.trim();\n if (trimmed.length === 0) {\n throw new Error(\n \"generateBuildId returned an empty string. https://nextjs.org/docs/messages/generatebuildid-not-a-string\",\n );\n }\n\n return trimmed;\n}\n\n/**\n * Resolve a NextConfig into a fully-resolved ResolvedNextConfig.\n * Awaits async functions for redirects/rewrites/headers.\n */\nexport async function resolveNextConfig(\n config: NextConfig | null,\n root: string = process.cwd(),\n): Promise<ResolvedNextConfig> {\n if (!config) {\n const buildId = await resolveBuildId(undefined);\n const resolved: ResolvedNextConfig = {\n env: {},\n basePath: \"\",\n trailingSlash: false,\n output: \"\",\n pageExtensions: normalizePageExtensions(),\n cacheComponents: false,\n redirects: [],\n rewrites: { beforeFiles: [], afterFiles: [], fallback: [] },\n headers: [],\n images: undefined,\n i18n: null,\n mdx: null,\n aliases: {},\n allowedDevOrigins: [],\n serverActionsAllowedOrigins: [],\n optimizePackageImports: [],\n serverActionsBodySizeLimit: 1 * 1024 * 1024,\n serverExternalPackages: [],\n buildId,\n };\n detectNextIntlConfig(root, resolved);\n return resolved;\n }\n\n // Resolve redirects\n let redirects: NextRedirect[] = [];\n if (config.redirects) {\n const result = await config.redirects();\n redirects = Array.isArray(result) ? result : [];\n }\n\n // Resolve rewrites\n let rewrites = {\n beforeFiles: [] as NextRewrite[],\n afterFiles: [] as NextRewrite[],\n fallback: [] as NextRewrite[],\n };\n if (config.rewrites) {\n const result = await config.rewrites();\n if (Array.isArray(result)) {\n rewrites.afterFiles = result;\n } else {\n rewrites = {\n beforeFiles: result.beforeFiles ?? [],\n afterFiles: result.afterFiles ?? [],\n fallback: result.fallback ?? [],\n };\n }\n }\n\n {\n const allRewrites = [...rewrites.beforeFiles, ...rewrites.afterFiles, ...rewrites.fallback];\n const externalRewrites = allRewrites.filter((rewrite) => isExternalUrl(rewrite.destination));\n\n if (externalRewrites.length > 0) {\n const noun = externalRewrites.length === 1 ? \"external rewrite\" : \"external rewrites\";\n const listing = externalRewrites\n .map((rewrite) => ` ${rewrite.source} → ${rewrite.destination}`)\n .join(\"\\n\");\n\n console.warn(\n `[vinext] Found ${externalRewrites.length} ${noun} that proxy requests to external origins:\\n` +\n `${listing}\\n` +\n `Request headers, including credential headers (cookie, authorization, proxy-authorization, x-api-key), ` +\n `are forwarded to the external origin to match Next.js behavior. ` +\n `If you do not want to forward credentials, use an API route or route handler where you control exactly which headers are sent.`,\n );\n }\n }\n\n // Resolve headers\n let headers: NextHeader[] = [];\n if (config.headers) {\n headers = await config.headers();\n }\n\n // Probe wrapped webpack config once so alias extraction and MDX extraction\n // observe the same mock environment.\n const webpackProbe = await probeWebpackConfig(config, root);\n const mdx = webpackProbe.mdx;\n const aliases = {\n ...extractTurboAliases(config, root),\n ...webpackProbe.aliases,\n };\n\n const allowedDevOrigins = Array.isArray(config.allowedDevOrigins) ? config.allowedDevOrigins : [];\n\n // Resolve serverActions.allowedOrigins and bodySizeLimit from experimental config\n const experimental = config.experimental as Record<string, unknown> | undefined;\n const serverActionsConfig = experimental?.serverActions as Record<string, unknown> | undefined;\n const serverActionsAllowedOrigins = Array.isArray(serverActionsConfig?.allowedOrigins)\n ? (serverActionsConfig.allowedOrigins as string[])\n : [];\n const serverActionsBodySizeLimit = parseBodySizeLimit(\n serverActionsConfig?.bodySizeLimit as string | number | undefined,\n );\n\n // Resolve optimizePackageImports from experimental config\n const rawOptimize = experimental?.optimizePackageImports;\n const optimizePackageImports = Array.isArray(rawOptimize)\n ? rawOptimize.filter((x): x is string => typeof x === \"string\")\n : [];\n\n // Resolve serverExternalPackages — support the current top-level key and the\n // legacy experimental.serverComponentsExternalPackages name that Next.js still\n // accepts (it moved out of experimental in Next.js 14.2).\n const legacyServerComponentsExternal = experimental?.serverComponentsExternalPackages;\n const serverExternalPackages: string[] = Array.isArray(config.serverExternalPackages)\n ? (config.serverExternalPackages as string[])\n : Array.isArray(legacyServerComponentsExternal)\n ? (legacyServerComponentsExternal as string[])\n : [];\n\n // Warn about unsupported webpack usage. We preserve alias injection and\n // extract MDX settings, but all other webpack customization is still ignored.\n if (config.webpack !== undefined) {\n if (mdx || Object.keys(webpackProbe.aliases).length > 0) {\n console.warn(\n '[vinext] next.config option \"webpack\" is only partially supported. ' +\n \"vinext preserves resolve.alias entries and MDX loader settings, but other webpack customization is ignored\",\n );\n } else {\n console.warn(\n '[vinext] next.config option \"webpack\" is not yet supported and will be ignored',\n );\n }\n }\n\n const output = config.output ?? \"\";\n if (output && output !== \"export\" && output !== \"standalone\") {\n console.warn(`[vinext] Unknown output mode \"${output as string}\", ignoring`);\n }\n\n const pageExtensions = normalizePageExtensions(config.pageExtensions);\n\n // Parse i18n config\n let i18n: NextI18nConfig | null = null;\n if (config.i18n) {\n i18n = {\n locales: config.i18n.locales,\n defaultLocale: config.i18n.defaultLocale,\n localeDetection: config.i18n.localeDetection ?? true,\n domains: config.i18n.domains,\n };\n }\n\n const buildId = await resolveBuildId(\n config.generateBuildId as (() => string | null | Promise<string | null>) | undefined,\n );\n\n const resolved: ResolvedNextConfig = {\n env: config.env ?? {},\n basePath: config.basePath ?? \"\",\n trailingSlash: config.trailingSlash ?? false,\n output: output === \"export\" || output === \"standalone\" ? output : \"\",\n pageExtensions,\n cacheComponents: config.cacheComponents ?? false,\n redirects,\n rewrites,\n headers,\n images: config.images,\n i18n,\n mdx,\n aliases,\n allowedDevOrigins,\n serverActionsAllowedOrigins,\n optimizePackageImports,\n serverActionsBodySizeLimit,\n serverExternalPackages,\n buildId,\n };\n\n // Auto-detect next-intl (lowest priority — explicit aliases from\n // webpack/turbopack already in `aliases` take precedence)\n detectNextIntlConfig(root, resolved);\n\n return resolved;\n}\n\nfunction normalizeAliasEntries(\n aliases: Record<string, unknown> | undefined,\n root: string,\n): Record<string, string> {\n if (!aliases) return {};\n\n const normalized: Record<string, string> = {};\n for (const [key, value] of Object.entries(aliases)) {\n if (typeof value !== \"string\") continue;\n normalized[key] = path.isAbsolute(value) ? value : path.resolve(root, value);\n }\n return normalized;\n}\n\nfunction extractTurboAliases(config: NextConfig, root: string): Record<string, string> {\n const experimental = config.experimental as Record<string, unknown> | undefined;\n const experimentalTurbo = experimental?.turbo as Record<string, unknown> | undefined;\n const topLevelTurbopack = config.turbopack as Record<string, unknown> | undefined;\n\n return {\n ...normalizeAliasEntries(\n experimentalTurbo?.resolveAlias as Record<string, unknown> | undefined,\n root,\n ),\n ...normalizeAliasEntries(\n topLevelTurbopack?.resolveAlias as Record<string, unknown> | undefined,\n root,\n ),\n };\n}\n\nasync function probeWebpackConfig(\n config: NextConfig,\n root: string,\n): Promise<{ aliases: Record<string, string>; mdx: MdxOptions | null }> {\n if (typeof config.webpack !== \"function\") {\n return { aliases: {}, mdx: null };\n }\n\n const mockModuleRules: any[] = [];\n const mockConfig = {\n context: root,\n resolve: { alias: {} as Record<string, unknown> },\n module: { rules: mockModuleRules },\n plugins: [] as any[],\n };\n const mockOptions = {\n defaultLoaders: { babel: { loader: \"next-babel-loader\" } },\n isServer: false,\n dev: false,\n dir: root,\n };\n\n try {\n const result = await (config.webpack as Function)(mockConfig, mockOptions);\n const finalConfig = result ?? mockConfig;\n const rules: any[] = finalConfig.module?.rules ?? mockModuleRules;\n return {\n aliases: normalizeAliasEntries(finalConfig.resolve?.alias, root),\n mdx: extractMdxOptionsFromRules(rules),\n };\n } catch {\n return { aliases: {}, mdx: null };\n }\n}\n\n/**\n * Extract MDX compilation options (remark/rehype/recma plugins) from\n * a Next.js config that uses @next/mdx.\n *\n * @next/mdx wraps the config with a webpack function that injects an MDX\n * loader rule. The remark/rehype plugins are captured in that closure.\n * We probe the webpack function with a mock config to extract them.\n */\nexport async function extractMdxOptions(\n config: NextConfig,\n root: string = process.cwd(),\n): Promise<MdxOptions | null> {\n return (await probeWebpackConfig(config, root)).mdx;\n}\n\n/**\n * Probe file candidates relative to root. Returns the first one that exists,\n * or null if none match.\n */\nfunction probeFiles(root: string, candidates: string[]): string | null {\n for (const candidate of candidates) {\n const abs = path.resolve(root, candidate);\n if (fs.existsSync(abs)) return abs;\n }\n return null;\n}\n\nconst I18N_REQUEST_CANDIDATES = [\n \"i18n/request.ts\",\n \"i18n/request.tsx\",\n \"i18n/request.js\",\n \"i18n/request.jsx\",\n \"src/i18n/request.ts\",\n \"src/i18n/request.tsx\",\n \"src/i18n/request.js\",\n \"src/i18n/request.jsx\",\n];\n\n/**\n * Detect next-intl in the project and auto-register the `next-intl/config`\n * alias if needed.\n *\n * next-intl's `createNextIntlPlugin()` crashes in vinext because it calls\n * `require('next/package.json')` to check the Next.js version. Instead,\n * vinext detects next-intl and registers the alias automatically.\n *\n * Note: `require.resolve('next-intl')` walks up to parent `node_modules`\n * directories via standard Node module resolution. In a monorepo, next-intl\n * installed at the workspace root will trigger detection even if not listed\n * in the project's own package.json. This is acceptable since a workspace-root\n * install implies the user wants it available.\n *\n * Mutates `resolved.aliases` and `resolved.env` in place.\n */\nexport function detectNextIntlConfig(root: string, resolved: ResolvedNextConfig): void {\n // Explicit alias wins — user or plugin already set it\n if (resolved.aliases[\"next-intl/config\"]) return;\n\n // Check if next-intl is installed (use main entry — some packages\n // don't expose ./package.json in their exports map)\n const require = createRequire(path.join(root, \"package.json\"));\n try {\n require.resolve(\"next-intl\");\n } catch {\n return; // next-intl not installed\n }\n\n // Probe for the i18n request config file\n const configPath = probeFiles(root, I18N_REQUEST_CANDIDATES);\n if (!configPath) return;\n\n resolved.aliases[\"next-intl/config\"] = configPath;\n\n if (resolved.trailingSlash) {\n resolved.env._next_intl_trailing_slash = \"true\";\n }\n}\n\nfunction extractMdxOptionsFromRules(rules: any[]): MdxOptions | null {\n // Search through webpack rules for the MDX loader injected by @next/mdx\n for (const rule of rules) {\n const loaders = extractMdxLoaders(rule);\n if (loaders) return loaders;\n }\n return null;\n}\n\n/**\n * Recursively search a webpack rule (which may have nested `oneOf` arrays)\n * for an MDX loader and extract its remark/rehype/recma plugin options.\n */\nfunction extractMdxLoaders(rule: any): MdxOptions | null {\n if (!rule) return null;\n\n // Check `oneOf` arrays (Next.js uses these extensively)\n if (Array.isArray(rule.oneOf)) {\n for (const child of rule.oneOf) {\n const result = extractMdxLoaders(child);\n if (result) return result;\n }\n }\n\n // Check `use` array (loader chain)\n const use = Array.isArray(rule.use) ? rule.use : rule.use ? [rule.use] : [];\n for (const loader of use) {\n const loaderPath = typeof loader === \"string\" ? loader : loader?.loader;\n if (typeof loaderPath === \"string\" && isMdxLoader(loaderPath)) {\n const opts = typeof loader === \"object\" ? loader.options : {};\n return extractPluginsFromOptions(opts);\n }\n }\n\n // Check direct `loader` field\n if (typeof rule.loader === \"string\" && isMdxLoader(rule.loader)) {\n return extractPluginsFromOptions(rule.options);\n }\n\n return null;\n}\n\nfunction isMdxLoader(loaderPath: string): boolean {\n return (\n loaderPath.includes(\"mdx\") &&\n (loaderPath.includes(\"@next\") ||\n loaderPath.includes(\"@mdx-js\") ||\n loaderPath.includes(\"mdx-js-loader\") ||\n loaderPath.includes(\"next-mdx\"))\n );\n}\n\nfunction extractPluginsFromOptions(opts: any): MdxOptions | null {\n if (!opts || typeof opts !== \"object\") return null;\n\n const remarkPlugins = Array.isArray(opts.remarkPlugins) ? opts.remarkPlugins : undefined;\n const rehypePlugins = Array.isArray(opts.rehypePlugins) ? opts.rehypePlugins : undefined;\n const recmaPlugins = Array.isArray(opts.recmaPlugins) ? opts.recmaPlugins : undefined;\n\n // Only return if at least one plugin array is non-empty\n if (\n (remarkPlugins && remarkPlugins.length > 0) ||\n (rehypePlugins && rehypePlugins.length > 0) ||\n (recmaPlugins && recmaPlugins.length > 0)\n ) {\n return {\n ...(remarkPlugins && remarkPlugins.length > 0 ? { remarkPlugins } : {}),\n ...(rehypePlugins && rehypePlugins.length > 0 ? { rehypePlugins } : {}),\n ...(recmaPlugins && recmaPlugins.length > 0 ? { recmaPlugins } : {}),\n };\n }\n\n return null;\n}\n"],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveRuntimeEntryModule } from "./runtime-entry-module.js";
|
|
1
2
|
//#region src/entries/app-browser-entry.ts
|
|
2
3
|
/**
|
|
3
4
|
* Generate the virtual browser entry module.
|
|
@@ -7,336 +8,8 @@
|
|
|
7
8
|
* RSC streams.
|
|
8
9
|
*/
|
|
9
10
|
function generateBrowserEntry() {
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
createFromReadableStream,
|
|
13
|
-
createFromFetch,
|
|
14
|
-
setServerCallback,
|
|
15
|
-
encodeReply,
|
|
16
|
-
createTemporaryReferenceSet,
|
|
17
|
-
} from "@vitejs/plugin-rsc/browser";
|
|
18
|
-
import { hydrateRoot } from "react-dom/client";
|
|
19
|
-
import { flushSync } from "react-dom";
|
|
20
|
-
import { setClientParams, setNavigationContext, toRscUrl, getPrefetchCache, getPrefetchedUrls, PREFETCH_CACHE_TTL } from "next/navigation";
|
|
21
|
-
|
|
22
|
-
let reactRoot;
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Convert the embedded RSC chunks back to a ReadableStream.
|
|
26
|
-
* Each chunk is a text string that needs to be encoded back to Uint8Array.
|
|
27
|
-
*/
|
|
28
|
-
function chunksToReadableStream(chunks) {
|
|
29
|
-
const encoder = new TextEncoder();
|
|
30
|
-
return new ReadableStream({
|
|
31
|
-
start(controller) {
|
|
32
|
-
for (const chunk of chunks) {
|
|
33
|
-
controller.enqueue(encoder.encode(chunk));
|
|
34
|
-
}
|
|
35
|
-
controller.close();
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Create a ReadableStream from progressively-embedded RSC chunks.
|
|
42
|
-
* The server injects RSC data as <script> tags that push to
|
|
43
|
-
* self.__VINEXT_RSC_CHUNKS__ throughout the HTML stream, and sets
|
|
44
|
-
* self.__VINEXT_RSC_DONE__ = true when complete.
|
|
45
|
-
*
|
|
46
|
-
* Instead of polling with setTimeout, we monkey-patch the array's
|
|
47
|
-
* push() method so new chunks are delivered immediately when the
|
|
48
|
-
* server's <script> tags execute. This eliminates unnecessary
|
|
49
|
-
* wakeups and reduces latency — same pattern Next.js uses with
|
|
50
|
-
* __next_f. The stream closes on DOMContentLoaded (when all
|
|
51
|
-
* server-injected scripts have executed) or when __VINEXT_RSC_DONE__
|
|
52
|
-
* is set, whichever comes first.
|
|
53
|
-
*/
|
|
54
|
-
function createProgressiveRscStream() {
|
|
55
|
-
const encoder = new TextEncoder();
|
|
56
|
-
return new ReadableStream({
|
|
57
|
-
start(controller) {
|
|
58
|
-
const chunks = self.__VINEXT_RSC_CHUNKS__ || [];
|
|
59
|
-
|
|
60
|
-
// Deliver any chunks that arrived before this code ran
|
|
61
|
-
// (from <script> tags that executed before the browser entry loaded)
|
|
62
|
-
for (const chunk of chunks) {
|
|
63
|
-
controller.enqueue(encoder.encode(chunk));
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// If the stream is already complete, close immediately
|
|
67
|
-
if (self.__VINEXT_RSC_DONE__) {
|
|
68
|
-
controller.close();
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Monkey-patch push() so future chunks stream in immediately
|
|
73
|
-
// when the server's <script> tags execute
|
|
74
|
-
let closed = false;
|
|
75
|
-
function closeOnce() {
|
|
76
|
-
if (!closed) {
|
|
77
|
-
closed = true;
|
|
78
|
-
controller.close();
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const arr = self.__VINEXT_RSC_CHUNKS__ = self.__VINEXT_RSC_CHUNKS__ || [];
|
|
83
|
-
arr.push = function(chunk) {
|
|
84
|
-
Array.prototype.push.call(this, chunk);
|
|
85
|
-
if (!closed) {
|
|
86
|
-
controller.enqueue(encoder.encode(chunk));
|
|
87
|
-
if (self.__VINEXT_RSC_DONE__) {
|
|
88
|
-
closeOnce();
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
return this.length;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
// Safety net: if the server crashes mid-stream and __VINEXT_RSC_DONE__
|
|
95
|
-
// never arrives, close the stream when all server-injected scripts
|
|
96
|
-
// have executed (DOMContentLoaded). Without this, a truncated response
|
|
97
|
-
// leaves the ReadableStream open forever, hanging hydration.
|
|
98
|
-
if (typeof document !== "undefined") {
|
|
99
|
-
if (document.readyState === "loading") {
|
|
100
|
-
document.addEventListener("DOMContentLoaded", closeOnce);
|
|
101
|
-
} else {
|
|
102
|
-
// Document already loaded — close immediately if not already done
|
|
103
|
-
closeOnce();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// Register the server action callback — React calls this internally
|
|
111
|
-
// when a "use server" function is invoked from client code.
|
|
112
|
-
setServerCallback(async (id, args) => {
|
|
113
|
-
const temporaryReferences = createTemporaryReferenceSet();
|
|
114
|
-
const body = await encodeReply(args, { temporaryReferences });
|
|
115
|
-
|
|
116
|
-
const fetchResponse = await fetch(toRscUrl(window.location.pathname + window.location.search), {
|
|
117
|
-
method: "POST",
|
|
118
|
-
headers: { "x-rsc-action": id },
|
|
119
|
-
body,
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
// Check for redirect signal from server action that called redirect()
|
|
123
|
-
const actionRedirect = fetchResponse.headers.get("x-action-redirect");
|
|
124
|
-
if (actionRedirect) {
|
|
125
|
-
// External URLs (different origin) need a hard redirect — client-side
|
|
126
|
-
// RSC navigation only works for same-origin paths.
|
|
127
|
-
try {
|
|
128
|
-
const redirectUrl = new URL(actionRedirect, window.location.origin);
|
|
129
|
-
if (redirectUrl.origin !== window.location.origin) {
|
|
130
|
-
window.location.href = actionRedirect;
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
|
-
} catch {
|
|
134
|
-
// If URL parsing fails, fall through to client-side navigation
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Navigate to the redirect target using client-side navigation
|
|
138
|
-
const redirectType = fetchResponse.headers.get("x-action-redirect-type") || "replace";
|
|
139
|
-
if (redirectType === "push") {
|
|
140
|
-
window.history.pushState(null, "", actionRedirect);
|
|
141
|
-
} else {
|
|
142
|
-
window.history.replaceState(null, "", actionRedirect);
|
|
143
|
-
}
|
|
144
|
-
// Trigger RSC navigation to the redirect target
|
|
145
|
-
if (typeof window.__VINEXT_RSC_NAVIGATE__ === "function") {
|
|
146
|
-
window.__VINEXT_RSC_NAVIGATE__(actionRedirect);
|
|
147
|
-
}
|
|
148
|
-
return undefined;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const result = await createFromFetch(Promise.resolve(fetchResponse), { temporaryReferences });
|
|
152
|
-
|
|
153
|
-
// The RSC response for actions contains { root, returnValue }.
|
|
154
|
-
// Re-render the page with the updated tree.
|
|
155
|
-
if (result && typeof result === "object" && "root" in result) {
|
|
156
|
-
reactRoot.render(result.root);
|
|
157
|
-
// Return the action's return value to the caller
|
|
158
|
-
if (result.returnValue) {
|
|
159
|
-
if (!result.returnValue.ok) throw result.returnValue.data;
|
|
160
|
-
return result.returnValue.data;
|
|
161
|
-
}
|
|
162
|
-
return undefined;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Fallback: render the entire result as the tree
|
|
166
|
-
reactRoot.render(result);
|
|
167
|
-
return result;
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
async function main() {
|
|
171
|
-
let rscStream;
|
|
172
|
-
|
|
173
|
-
// Use embedded RSC data for initial hydration if available.
|
|
174
|
-
// This ensures we use the SAME RSC payload that generated the HTML,
|
|
175
|
-
// avoiding hydration mismatches (React error #418).
|
|
176
|
-
//
|
|
177
|
-
// The server embeds RSC chunks progressively as <script> tags that push
|
|
178
|
-
// to self.__VINEXT_RSC_CHUNKS__. When complete, self.__VINEXT_RSC_DONE__
|
|
179
|
-
// is set and self.__VINEXT_RSC_PARAMS__ contains route params.
|
|
180
|
-
// For backwards compat, also check the legacy self.__VINEXT_RSC__ format.
|
|
181
|
-
if (self.__VINEXT_RSC_CHUNKS__ || self.__VINEXT_RSC_DONE__ || self.__VINEXT_RSC__) {
|
|
182
|
-
if (self.__VINEXT_RSC__) {
|
|
183
|
-
// Legacy format: single object with all chunks
|
|
184
|
-
const embedData = self.__VINEXT_RSC__;
|
|
185
|
-
delete self.__VINEXT_RSC__;
|
|
186
|
-
if (embedData.params) {
|
|
187
|
-
setClientParams(embedData.params);
|
|
188
|
-
}
|
|
189
|
-
// Legacy format may include nav context for hydration snapshot consistency.
|
|
190
|
-
if (embedData.nav) {
|
|
191
|
-
setNavigationContext({ pathname: embedData.nav.pathname, searchParams: new URLSearchParams(embedData.nav.searchParams || {}), params: embedData.params || {} });
|
|
192
|
-
}
|
|
193
|
-
rscStream = chunksToReadableStream(embedData.rsc);
|
|
194
|
-
} else {
|
|
195
|
-
// Progressive format: chunks arrive incrementally via script tags.
|
|
196
|
-
// Params are embedded in <head> so they're always available by this point.
|
|
197
|
-
if (self.__VINEXT_RSC_PARAMS__) {
|
|
198
|
-
setClientParams(self.__VINEXT_RSC_PARAMS__);
|
|
199
|
-
}
|
|
200
|
-
// Restore the server navigation context so useSyncExternalStore getServerSnapshot
|
|
201
|
-
// matches what was rendered on the server, preventing hydration mismatches.
|
|
202
|
-
if (self.__VINEXT_RSC_NAV__) {
|
|
203
|
-
const __nav = self.__VINEXT_RSC_NAV__;
|
|
204
|
-
setNavigationContext({ pathname: __nav.pathname, searchParams: new URLSearchParams(__nav.searchParams), params: self.__VINEXT_RSC_PARAMS__ || {} });
|
|
205
|
-
}
|
|
206
|
-
rscStream = createProgressiveRscStream();
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
// Fallback: fetch fresh RSC (shouldn't happen on initial page load)
|
|
210
|
-
const rscResponse = await fetch(toRscUrl(window.location.pathname + window.location.search));
|
|
211
|
-
|
|
212
|
-
// Hydrate useParams() with route params from the server before React hydration
|
|
213
|
-
const paramsHeader = rscResponse.headers.get("X-Vinext-Params");
|
|
214
|
-
if (paramsHeader) {
|
|
215
|
-
try { setClientParams(JSON.parse(paramsHeader)); } catch (_e) { /* ignore */ }
|
|
216
|
-
}
|
|
217
|
-
// Set nav context from current URL for hydration snapshot consistency.
|
|
218
|
-
setNavigationContext({ pathname: window.location.pathname, searchParams: new URLSearchParams(window.location.search), params: self.__VINEXT_RSC_PARAMS__ || {} });
|
|
219
|
-
|
|
220
|
-
rscStream = rscResponse.body;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const root = await createFromReadableStream(rscStream);
|
|
224
|
-
|
|
225
|
-
// Hydrate the document
|
|
226
|
-
// In development, suppress Vite's error overlay for errors caught by React error
|
|
227
|
-
// boundaries. Without this, React re-throws caught errors to the global handler,
|
|
228
|
-
// which triggers Vite's overlay even though the error was handled by an error.tsx.
|
|
229
|
-
// In production, preserve React's default onCaughtError (console.error) so
|
|
230
|
-
// boundary-caught errors remain visible to error monitoring.
|
|
231
|
-
reactRoot = hydrateRoot(document, root, import.meta.env.DEV ? {
|
|
232
|
-
onCaughtError: function() {},
|
|
233
|
-
} : undefined);
|
|
234
|
-
|
|
235
|
-
// Store for client-side navigation
|
|
236
|
-
window.__VINEXT_RSC_ROOT__ = reactRoot;
|
|
237
|
-
|
|
238
|
-
// Client-side navigation handler
|
|
239
|
-
// Checks the prefetch cache (populated by <Link> IntersectionObserver and
|
|
240
|
-
// router.prefetch()) before making a network request. This makes navigation
|
|
241
|
-
// near-instant for prefetched routes.
|
|
242
|
-
window.__VINEXT_RSC_NAVIGATE__ = async function navigateRsc(href, __redirectDepth) {
|
|
243
|
-
if ((__redirectDepth || 0) > 10) {
|
|
244
|
-
console.error("[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.");
|
|
245
|
-
window.location.href = href;
|
|
246
|
-
return;
|
|
247
|
-
}
|
|
248
|
-
try {
|
|
249
|
-
const url = new URL(href, window.location.origin);
|
|
250
|
-
const rscUrl = toRscUrl(url.pathname + url.search);
|
|
251
|
-
|
|
252
|
-
// Check the in-memory prefetch cache first
|
|
253
|
-
let navResponse;
|
|
254
|
-
const prefetchCache = getPrefetchCache();
|
|
255
|
-
const cached = prefetchCache.get(rscUrl);
|
|
256
|
-
if (cached && (Date.now() - cached.timestamp) < PREFETCH_CACHE_TTL) {
|
|
257
|
-
navResponse = cached.response;
|
|
258
|
-
prefetchCache.delete(rscUrl); // Consume the cached entry (one-time use)
|
|
259
|
-
getPrefetchedUrls().delete(rscUrl); // Allow re-prefetch when link is visible again
|
|
260
|
-
} else if (cached) {
|
|
261
|
-
prefetchCache.delete(rscUrl); // Expired, clean up
|
|
262
|
-
getPrefetchedUrls().delete(rscUrl);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Fallback to network fetch if not in cache
|
|
266
|
-
if (!navResponse) {
|
|
267
|
-
navResponse = await fetch(rscUrl, {
|
|
268
|
-
headers: { Accept: "text/x-component" },
|
|
269
|
-
credentials: "include",
|
|
270
|
-
});
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// Detect if fetch followed a redirect: compare the final response URL to
|
|
274
|
-
// what we requested. If they differ, the server issued a 3xx — push the
|
|
275
|
-
// canonical destination URL into history before rendering.
|
|
276
|
-
const __finalUrl = new URL(navResponse.url);
|
|
277
|
-
const __requestedUrl = new URL(rscUrl, window.location.origin);
|
|
278
|
-
if (__finalUrl.pathname !== __requestedUrl.pathname) {
|
|
279
|
-
// Strip .rsc suffix from the final URL to get the page path for history.
|
|
280
|
-
// Use replaceState instead of pushState: the caller (navigateImpl) already
|
|
281
|
-
// pushed the pre-redirect URL; replacing it avoids a stale history entry.
|
|
282
|
-
const __destPath = __finalUrl.pathname.replace(/\\.rsc$/, "") + __finalUrl.search;
|
|
283
|
-
window.history.replaceState(null, "", __destPath);
|
|
284
|
-
return window.__VINEXT_RSC_NAVIGATE__(__destPath, (__redirectDepth || 0) + 1);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Update useParams() with route params from the server before re-rendering
|
|
288
|
-
const navParamsHeader = navResponse.headers.get("X-Vinext-Params");
|
|
289
|
-
if (navParamsHeader) {
|
|
290
|
-
try { setClientParams(JSON.parse(navParamsHeader)); } catch (_e) { /* ignore */ }
|
|
291
|
-
} else {
|
|
292
|
-
setClientParams({});
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const rscPayload = await createFromFetch(Promise.resolve(navResponse));
|
|
296
|
-
// Use flushSync to guarantee React commits the new tree to the DOM
|
|
297
|
-
// synchronously before this function returns. Callers scroll to top
|
|
298
|
-
// after awaiting, so the new content must be painted first.
|
|
299
|
-
flushSync(function () { reactRoot.render(rscPayload); });
|
|
300
|
-
} catch (err) {
|
|
301
|
-
console.error("[vinext] RSC navigation error:", err);
|
|
302
|
-
// Fallback to full page load
|
|
303
|
-
window.location.href = href;
|
|
304
|
-
}
|
|
305
|
-
};
|
|
306
|
-
|
|
307
|
-
// Handle popstate (browser back/forward)
|
|
308
|
-
// Store the navigation promise on a well-known property so that
|
|
309
|
-
// restoreScrollPosition (in navigation.ts) can await it before scrolling.
|
|
310
|
-
// This prevents a flash where the old content is visible at the restored
|
|
311
|
-
// scroll position before the new RSC payload has rendered.
|
|
312
|
-
window.addEventListener("popstate", () => {
|
|
313
|
-
const p = window.__VINEXT_RSC_NAVIGATE__(window.location.href);
|
|
314
|
-
window.__VINEXT_RSC_PENDING__ = p;
|
|
315
|
-
p.finally(() => {
|
|
316
|
-
// Clear once settled so stale promises aren't awaited later
|
|
317
|
-
if (window.__VINEXT_RSC_PENDING__ === p) {
|
|
318
|
-
window.__VINEXT_RSC_PENDING__ = null;
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
});
|
|
322
|
-
|
|
323
|
-
// HMR: re-render on server module updates
|
|
324
|
-
if (import.meta.hot) {
|
|
325
|
-
import.meta.hot.on("rsc:update", async () => {
|
|
326
|
-
try {
|
|
327
|
-
const rscPayload = await createFromFetch(
|
|
328
|
-
fetch(toRscUrl(window.location.pathname + window.location.search))
|
|
329
|
-
);
|
|
330
|
-
reactRoot.render(rscPayload);
|
|
331
|
-
} catch (err) {
|
|
332
|
-
console.error("[vinext] RSC HMR error:", err);
|
|
333
|
-
}
|
|
334
|
-
});
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
main();
|
|
339
|
-
`;
|
|
11
|
+
const entryPath = resolveRuntimeEntryModule("app-browser-entry");
|
|
12
|
+
return `import ${JSON.stringify(entryPath)};`;
|
|
340
13
|
}
|
|
341
14
|
//#endregion
|
|
342
15
|
export { generateBrowserEntry };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-browser-entry.js","names":[],"sources":["../../src/entries/app-browser-entry.ts"],"sourcesContent":["/**\n * Generate the virtual browser entry module.\n *\n * This runs in the client (browser). It hydrates the page from the\n * embedded RSC payload and handles client-side navigation by re-fetching\n * RSC streams.\n */\nexport function generateBrowserEntry(): string {\n return `\nimport {\n createFromReadableStream,\n createFromFetch,\n setServerCallback,\n encodeReply,\n createTemporaryReferenceSet,\n} from \"@vitejs/plugin-rsc/browser\";\nimport { hydrateRoot } from \"react-dom/client\";\nimport { flushSync } from \"react-dom\";\nimport { setClientParams, setNavigationContext, toRscUrl, getPrefetchCache, getPrefetchedUrls, PREFETCH_CACHE_TTL } from \"next/navigation\";\n\nlet reactRoot;\n\n/**\n * Convert the embedded RSC chunks back to a ReadableStream.\n * Each chunk is a text string that needs to be encoded back to Uint8Array.\n */\nfunction chunksToReadableStream(chunks) {\n const encoder = new TextEncoder();\n return new ReadableStream({\n start(controller) {\n for (const chunk of chunks) {\n controller.enqueue(encoder.encode(chunk));\n }\n controller.close();\n }\n });\n}\n\n/**\n * Create a ReadableStream from progressively-embedded RSC chunks.\n * The server injects RSC data as <script> tags that push to\n * self.__VINEXT_RSC_CHUNKS__ throughout the HTML stream, and sets\n * self.__VINEXT_RSC_DONE__ = true when complete.\n *\n * Instead of polling with setTimeout, we monkey-patch the array's\n * push() method so new chunks are delivered immediately when the\n * server's <script> tags execute. This eliminates unnecessary\n * wakeups and reduces latency — same pattern Next.js uses with\n * __next_f. The stream closes on DOMContentLoaded (when all\n * server-injected scripts have executed) or when __VINEXT_RSC_DONE__\n * is set, whichever comes first.\n */\nfunction createProgressiveRscStream() {\n const encoder = new TextEncoder();\n return new ReadableStream({\n start(controller) {\n const chunks = self.__VINEXT_RSC_CHUNKS__ || [];\n\n // Deliver any chunks that arrived before this code ran\n // (from <script> tags that executed before the browser entry loaded)\n for (const chunk of chunks) {\n controller.enqueue(encoder.encode(chunk));\n }\n\n // If the stream is already complete, close immediately\n if (self.__VINEXT_RSC_DONE__) {\n controller.close();\n return;\n }\n\n // Monkey-patch push() so future chunks stream in immediately\n // when the server's <script> tags execute\n let closed = false;\n function closeOnce() {\n if (!closed) {\n closed = true;\n controller.close();\n }\n }\n\n const arr = self.__VINEXT_RSC_CHUNKS__ = self.__VINEXT_RSC_CHUNKS__ || [];\n arr.push = function(chunk) {\n Array.prototype.push.call(this, chunk);\n if (!closed) {\n controller.enqueue(encoder.encode(chunk));\n if (self.__VINEXT_RSC_DONE__) {\n closeOnce();\n }\n }\n return this.length;\n };\n\n // Safety net: if the server crashes mid-stream and __VINEXT_RSC_DONE__\n // never arrives, close the stream when all server-injected scripts\n // have executed (DOMContentLoaded). Without this, a truncated response\n // leaves the ReadableStream open forever, hanging hydration.\n if (typeof document !== \"undefined\") {\n if (document.readyState === \"loading\") {\n document.addEventListener(\"DOMContentLoaded\", closeOnce);\n } else {\n // Document already loaded — close immediately if not already done\n closeOnce();\n }\n }\n }\n });\n}\n\n// Register the server action callback — React calls this internally\n// when a \"use server\" function is invoked from client code.\nsetServerCallback(async (id, args) => {\n const temporaryReferences = createTemporaryReferenceSet();\n const body = await encodeReply(args, { temporaryReferences });\n\n const fetchResponse = await fetch(toRscUrl(window.location.pathname + window.location.search), {\n method: \"POST\",\n headers: { \"x-rsc-action\": id },\n body,\n });\n\n // Check for redirect signal from server action that called redirect()\n const actionRedirect = fetchResponse.headers.get(\"x-action-redirect\");\n if (actionRedirect) {\n // External URLs (different origin) need a hard redirect — client-side\n // RSC navigation only works for same-origin paths.\n try {\n const redirectUrl = new URL(actionRedirect, window.location.origin);\n if (redirectUrl.origin !== window.location.origin) {\n window.location.href = actionRedirect;\n return undefined;\n }\n } catch {\n // If URL parsing fails, fall through to client-side navigation\n }\n\n // Navigate to the redirect target using client-side navigation\n const redirectType = fetchResponse.headers.get(\"x-action-redirect-type\") || \"replace\";\n if (redirectType === \"push\") {\n window.history.pushState(null, \"\", actionRedirect);\n } else {\n window.history.replaceState(null, \"\", actionRedirect);\n }\n // Trigger RSC navigation to the redirect target\n if (typeof window.__VINEXT_RSC_NAVIGATE__ === \"function\") {\n window.__VINEXT_RSC_NAVIGATE__(actionRedirect);\n }\n return undefined;\n }\n\n const result = await createFromFetch(Promise.resolve(fetchResponse), { temporaryReferences });\n\n // The RSC response for actions contains { root, returnValue }.\n // Re-render the page with the updated tree.\n if (result && typeof result === \"object\" && \"root\" in result) {\n reactRoot.render(result.root);\n // Return the action's return value to the caller\n if (result.returnValue) {\n if (!result.returnValue.ok) throw result.returnValue.data;\n return result.returnValue.data;\n }\n return undefined;\n }\n\n // Fallback: render the entire result as the tree\n reactRoot.render(result);\n return result;\n});\n\nasync function main() {\n let rscStream;\n\n // Use embedded RSC data for initial hydration if available.\n // This ensures we use the SAME RSC payload that generated the HTML,\n // avoiding hydration mismatches (React error #418).\n //\n // The server embeds RSC chunks progressively as <script> tags that push\n // to self.__VINEXT_RSC_CHUNKS__. When complete, self.__VINEXT_RSC_DONE__\n // is set and self.__VINEXT_RSC_PARAMS__ contains route params.\n // For backwards compat, also check the legacy self.__VINEXT_RSC__ format.\n if (self.__VINEXT_RSC_CHUNKS__ || self.__VINEXT_RSC_DONE__ || self.__VINEXT_RSC__) {\n if (self.__VINEXT_RSC__) {\n // Legacy format: single object with all chunks\n const embedData = self.__VINEXT_RSC__;\n delete self.__VINEXT_RSC__;\n if (embedData.params) {\n setClientParams(embedData.params);\n }\n // Legacy format may include nav context for hydration snapshot consistency.\n if (embedData.nav) {\n setNavigationContext({ pathname: embedData.nav.pathname, searchParams: new URLSearchParams(embedData.nav.searchParams || {}), params: embedData.params || {} });\n }\n rscStream = chunksToReadableStream(embedData.rsc);\n } else {\n // Progressive format: chunks arrive incrementally via script tags.\n // Params are embedded in <head> so they're always available by this point.\n if (self.__VINEXT_RSC_PARAMS__) {\n setClientParams(self.__VINEXT_RSC_PARAMS__);\n }\n // Restore the server navigation context so useSyncExternalStore getServerSnapshot\n // matches what was rendered on the server, preventing hydration mismatches.\n if (self.__VINEXT_RSC_NAV__) {\n const __nav = self.__VINEXT_RSC_NAV__;\n setNavigationContext({ pathname: __nav.pathname, searchParams: new URLSearchParams(__nav.searchParams), params: self.__VINEXT_RSC_PARAMS__ || {} });\n }\n rscStream = createProgressiveRscStream();\n }\n } else {\n // Fallback: fetch fresh RSC (shouldn't happen on initial page load)\n const rscResponse = await fetch(toRscUrl(window.location.pathname + window.location.search));\n\n // Hydrate useParams() with route params from the server before React hydration\n const paramsHeader = rscResponse.headers.get(\"X-Vinext-Params\");\n if (paramsHeader) {\n try { setClientParams(JSON.parse(paramsHeader)); } catch (_e) { /* ignore */ }\n }\n // Set nav context from current URL for hydration snapshot consistency.\n setNavigationContext({ pathname: window.location.pathname, searchParams: new URLSearchParams(window.location.search), params: self.__VINEXT_RSC_PARAMS__ || {} });\n\n rscStream = rscResponse.body;\n }\n\n const root = await createFromReadableStream(rscStream);\n\n // Hydrate the document\n // In development, suppress Vite's error overlay for errors caught by React error\n // boundaries. Without this, React re-throws caught errors to the global handler,\n // which triggers Vite's overlay even though the error was handled by an error.tsx.\n // In production, preserve React's default onCaughtError (console.error) so\n // boundary-caught errors remain visible to error monitoring.\n reactRoot = hydrateRoot(document, root, import.meta.env.DEV ? {\n onCaughtError: function() {},\n } : undefined);\n\n // Store for client-side navigation\n window.__VINEXT_RSC_ROOT__ = reactRoot;\n\n // Client-side navigation handler\n // Checks the prefetch cache (populated by <Link> IntersectionObserver and\n // router.prefetch()) before making a network request. This makes navigation\n // near-instant for prefetched routes.\n window.__VINEXT_RSC_NAVIGATE__ = async function navigateRsc(href, __redirectDepth) {\n if ((__redirectDepth || 0) > 10) {\n console.error(\"[vinext] Too many RSC redirects — aborting navigation to prevent infinite loop.\");\n window.location.href = href;\n return;\n }\n try {\n const url = new URL(href, window.location.origin);\n const rscUrl = toRscUrl(url.pathname + url.search);\n\n // Check the in-memory prefetch cache first\n let navResponse;\n const prefetchCache = getPrefetchCache();\n const cached = prefetchCache.get(rscUrl);\n if (cached && (Date.now() - cached.timestamp) < PREFETCH_CACHE_TTL) {\n navResponse = cached.response;\n prefetchCache.delete(rscUrl); // Consume the cached entry (one-time use)\n getPrefetchedUrls().delete(rscUrl); // Allow re-prefetch when link is visible again\n } else if (cached) {\n prefetchCache.delete(rscUrl); // Expired, clean up\n getPrefetchedUrls().delete(rscUrl);\n }\n\n // Fallback to network fetch if not in cache\n if (!navResponse) {\n navResponse = await fetch(rscUrl, {\n headers: { Accept: \"text/x-component\" },\n credentials: \"include\",\n });\n }\n\n // Detect if fetch followed a redirect: compare the final response URL to\n // what we requested. If they differ, the server issued a 3xx — push the\n // canonical destination URL into history before rendering.\n const __finalUrl = new URL(navResponse.url);\n const __requestedUrl = new URL(rscUrl, window.location.origin);\n if (__finalUrl.pathname !== __requestedUrl.pathname) {\n // Strip .rsc suffix from the final URL to get the page path for history.\n // Use replaceState instead of pushState: the caller (navigateImpl) already\n // pushed the pre-redirect URL; replacing it avoids a stale history entry.\n const __destPath = __finalUrl.pathname.replace(/\\\\.rsc$/, \"\") + __finalUrl.search;\n window.history.replaceState(null, \"\", __destPath);\n return window.__VINEXT_RSC_NAVIGATE__(__destPath, (__redirectDepth || 0) + 1);\n }\n\n // Update useParams() with route params from the server before re-rendering\n const navParamsHeader = navResponse.headers.get(\"X-Vinext-Params\");\n if (navParamsHeader) {\n try { setClientParams(JSON.parse(navParamsHeader)); } catch (_e) { /* ignore */ }\n } else {\n setClientParams({});\n }\n\n const rscPayload = await createFromFetch(Promise.resolve(navResponse));\n // Use flushSync to guarantee React commits the new tree to the DOM\n // synchronously before this function returns. Callers scroll to top\n // after awaiting, so the new content must be painted first.\n flushSync(function () { reactRoot.render(rscPayload); });\n } catch (err) {\n console.error(\"[vinext] RSC navigation error:\", err);\n // Fallback to full page load\n window.location.href = href;\n }\n };\n\n // Handle popstate (browser back/forward)\n // Store the navigation promise on a well-known property so that\n // restoreScrollPosition (in navigation.ts) can await it before scrolling.\n // This prevents a flash where the old content is visible at the restored\n // scroll position before the new RSC payload has rendered.\n window.addEventListener(\"popstate\", () => {\n const p = window.__VINEXT_RSC_NAVIGATE__(window.location.href);\n window.__VINEXT_RSC_PENDING__ = p;\n p.finally(() => {\n // Clear once settled so stale promises aren't awaited later\n if (window.__VINEXT_RSC_PENDING__ === p) {\n window.__VINEXT_RSC_PENDING__ = null;\n }\n });\n });\n\n // HMR: re-render on server module updates\n if (import.meta.hot) {\n import.meta.hot.on(\"rsc:update\", async () => {\n try {\n const rscPayload = await createFromFetch(\n fetch(toRscUrl(window.location.pathname + window.location.search))\n );\n reactRoot.render(rscPayload);\n } catch (err) {\n console.error(\"[vinext] RSC HMR error:\", err);\n }\n });\n }\n}\n\nmain();\n`;\n}\n"],"mappings":";;;;;;;;AAOA,SAAgB,uBAA+B;AAC7C,QAAO"}
|
|
1
|
+
{"version":3,"file":"app-browser-entry.js","names":[],"sources":["../../src/entries/app-browser-entry.ts"],"sourcesContent":["import { resolveRuntimeEntryModule } from \"./runtime-entry-module.js\";\n\n/**\n * Generate the virtual browser entry module.\n *\n * This runs in the client (browser). It hydrates the page from the\n * embedded RSC payload and handles client-side navigation by re-fetching\n * RSC streams.\n */\nexport function generateBrowserEntry(): string {\n const entryPath = resolveRuntimeEntryModule(\"app-browser-entry\");\n return `import ${JSON.stringify(entryPath)};`;\n}\n"],"mappings":";;;;;;;;;AASA,SAAgB,uBAA+B;CAC7C,MAAM,YAAY,0BAA0B,oBAAoB;AAChE,QAAO,UAAU,KAAK,UAAU,UAAU,CAAC"}
|