@thomas.f/prnext 0.1.17-alpha → 0.1.19-alpha
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 +27 -868
- package/build/actions.mjs +27 -15
- package/build/analyze.mjs +180 -24
- package/build/app-config.mjs +13 -0
- package/build/app-static.mjs +34 -12
- package/build/app-styles.mjs +79 -0
- package/build/app.mjs +122 -46
- package/build/bundle-runtime.mjs +22 -9
- package/build/cache-components.mjs +1 -1
- package/build/cache-handlers.mjs +1 -1
- package/build/compact-css.mjs +53 -0
- package/build/compilation-cache.mjs +269 -0
- package/build/compilation-store.mjs +142 -0
- package/build/compiler.mjs +34 -10
- package/build/config.mjs +34 -3
- package/build/create.mjs +239 -0
- package/build/css-modules.mjs +56 -12
- package/build/deployment-assets.mjs +89 -0
- package/build/deployment-config.mjs +18 -0
- package/build/dev-css-assets.mjs +173 -0
- package/build/dev-css-update.mjs +18 -14
- package/build/dev-dependencies.mjs +1 -1
- package/build/dev-discovery.mjs +3 -3
- package/build/dev-lazy-build.mjs +77 -0
- package/build/dev.mjs +15 -8
- package/build/dynamic.mjs +14 -7
- package/build/edge.mjs +3 -3
- package/build/fonts.mjs +1 -1
- package/build/framework-imports.mjs +15 -5
- package/build/html-only-config.mjs +53 -0
- package/build/html-only-graph.mjs +67 -0
- package/build/incremental.mjs +112 -18
- package/build/index.mjs +90 -28
- package/build/instrumentation.mjs +30 -0
- package/build/lazy-dynamic-imports.mjs +137 -0
- package/build/metadata-routes.mjs +1 -1
- package/build/migrate.mjs +4 -4
- package/build/migration-audit.mjs +19 -31
- package/build/migration-imports.mjs +245 -0
- package/build/migration-scenarios-runner.mjs +73 -0
- package/build/migration-scenarios.mjs +72 -0
- package/build/native-discovery.mjs +65 -5
- package/build/native-packages.mjs +2 -0
- package/build/native-type-config.mjs +37 -0
- package/build/npm-packages.mjs +1 -0
- package/build/persistent-cache.mjs +18 -3
- package/build/precompress.mjs +1 -1
- package/build/prerender-cache.mjs +70 -0
- package/build/prerender-config.mjs +29 -0
- package/build/prerender-pages.mjs +19 -9
- package/build/prerender-pool.mjs +165 -0
- package/build/prerender-process-worker.mjs +58 -0
- package/build/prerender-worker.mjs +5 -5
- package/build/production-builder.mjs +59 -10
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- package/build/standalone.mjs +21 -3
- package/build/static-export.mjs +4 -4
- package/build/styles.mjs +6 -4
- package/build/transform.mjs +1 -1
- package/build/typed-routes.mjs +4 -0
- package/build/typescript-dev.mjs +35 -8
- package/build/typescript-native-worker.mjs +55 -0
- package/build/typescript-native.mjs +18 -0
- package/build/typescript-toolchain.mjs +19 -0
- package/build/typescript.mjs +6 -4
- package/build/webpack-graph.mjs +19 -6
- package/build/worker-memory.mjs +9 -0
- package/cli.mjs +70 -5
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-dynamic.cjs +12 -1
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +15 -3
- package/compat/cache-wire.cjs +28 -0
- package/compat/cache.cjs +2 -1
- package/compat/cache.d.cts +4 -0
- package/compat/cookies.cjs +21 -5
- package/compat/data-cache.cjs +12 -8
- package/compat/flight-delta.cjs +152 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +69 -4
- package/compat/link.cjs +13 -10
- package/compat/navigation-authorization.cjs +9 -0
- package/compat/navigation-stages.cjs +19 -0
- package/compat/offline-store.cjs +11 -0
- package/compat/offline.cjs +5 -0
- package/compat/offline.d.cts +2 -0
- package/compat/redis-cache.cjs +201 -0
- package/compat/redis-cache.d.cts +37 -0
- package/compat/server-primitives.cjs +31 -11
- package/compat/server.d.cts +15 -2
- package/compat/static-generation.cjs +7 -0
- package/compat/use-cache.cjs +3 -0
- package/docs/ai/compatibility.md +248 -2
- package/docs/ai/data.md +165 -5
- package/docs/ai/deployment.md +158 -0
- package/docs/ai/quickstart.md +29 -4
- package/docs/ai/routing.md +93 -0
- package/docs/ai/troubleshooting.md +116 -2
- package/package.json +40 -9
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/app-client.mjs +316 -92
- package/runtime/app-content.mjs +4 -3
- package/runtime/app-partial-client.mjs +3 -0
- package/runtime/app-partial.mjs +1 -1
- package/runtime/app-prefetch-constants.mjs +4 -0
- package/runtime/app-prefetch.mjs +2 -3
- package/runtime/app-render-key.mjs +5 -0
- package/runtime/app-render.mjs +88 -28
- package/runtime/app-routing.mjs +22 -9
- package/runtime/app-static.mjs +18 -8
- package/runtime/app-stream-html.mjs +10 -53
- package/runtime/app-style-selection.mjs +34 -0
- package/runtime/app-styles.mjs +154 -0
- package/runtime/atomic-file.mjs +27 -0
- package/runtime/cache-components-rsc.mjs +7 -2
- package/runtime/cache-request.mjs +2 -1
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-client.mjs +13 -3
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-file-changes.mjs +123 -8
- package/runtime/dev-indicator.mjs +8 -3
- package/runtime/dev-lazy-imports.mjs +121 -0
- package/runtime/dev-lazy-staging.mjs +14 -0
- package/runtime/dev-lazy-tool.mjs +16 -0
- package/runtime/dev-style-retention.mjs +15 -0
- package/runtime/development.mjs +83 -25
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +74 -0
- package/runtime/flight-tap.mjs +28 -0
- package/runtime/fs.mjs +15 -8
- package/runtime/html-only.mjs +27 -0
- package/runtime/http.mjs +2 -0
- package/runtime/incremental-cache.mjs +25 -4
- package/runtime/instrumentation.mjs +49 -0
- package/runtime/lazy-import-client.mjs +38 -0
- package/runtime/navigation-recovery.mjs +36 -0
- package/runtime/navigation.mjs +7 -0
- package/runtime/node-html-stream.mjs +47 -0
- package/runtime/pages-client.mjs +14 -7
- package/runtime/pages-navigation.mjs +48 -63
- package/runtime/prefetch-scheduler.mjs +110 -0
- package/runtime/profile.mjs +14 -0
- package/runtime/profiles.json +2 -2
- package/runtime/render.mjs +3 -0
- package/runtime/route-pattern.mjs +44 -0
- package/runtime/rsc-worker.mjs +34 -12
- package/runtime/trace.mjs +78 -0
- package/runtime/watch-project.mjs +19 -15
- package/runtime/worker.mjs +70 -26
- package/vendor/nft/LICENSE +7 -0
- package/vendor/nft/README.md +16 -0
- package/vendor/nft/out/analyze.d.ts +8 -0
- package/vendor/nft/out/analyze.js +1216 -0
- package/vendor/nft/out/analyze.js.map +1 -0
- package/vendor/nft/out/cli.d.ts +2 -0
- package/vendor/nft/out/cli.js +94 -0
- package/vendor/nft/out/cli.js.map +1 -0
- package/vendor/nft/out/fs.d.ts +22 -0
- package/vendor/nft/out/fs.js +106 -0
- package/vendor/nft/out/fs.js.map +1 -0
- package/vendor/nft/out/index.d.ts +4 -0
- package/vendor/nft/out/index.js +25 -0
- package/vendor/nft/out/index.js.map +1 -0
- package/vendor/nft/out/node-file-trace.d.ts +41 -0
- package/vendor/nft/out/node-file-trace.js +380 -0
- package/vendor/nft/out/node-file-trace.js.map +1 -0
- package/vendor/nft/out/resolve-dependency.d.ts +6 -0
- package/vendor/nft/out/resolve-dependency.js +427 -0
- package/vendor/nft/out/resolve-dependency.js.map +1 -0
- package/vendor/nft/out/types.d.ts +66 -0
- package/vendor/nft/out/types.js +2 -0
- package/vendor/nft/out/types.js.map +1 -0
- package/vendor/nft/out/utils/ast-helpers.js +49 -0
- package/vendor/nft/out/utils/ast-helpers.js.map +1 -0
- package/vendor/nft/out/utils/binary-locators.js +88 -0
- package/vendor/nft/out/utils/binary-locators.js.map +1 -0
- package/vendor/nft/out/utils/get-package-base.js +30 -0
- package/vendor/nft/out/utils/get-package-base.js.map +1 -0
- package/vendor/nft/out/utils/interop-require.js +24 -0
- package/vendor/nft/out/utils/interop-require.js.map +1 -0
- package/vendor/nft/out/utils/node-version.js +10 -0
- package/vendor/nft/out/utils/node-version.js.map +1 -0
- package/vendor/nft/out/utils/sharedlib-emit.js +33 -0
- package/vendor/nft/out/utils/sharedlib-emit.js.map +1 -0
- package/vendor/nft/out/utils/special-cases.js +347 -0
- package/vendor/nft/out/utils/special-cases.js.map +1 -0
- package/vendor/nft/out/utils/static-eval.js +579 -0
- package/vendor/nft/out/utils/static-eval.js.map +1 -0
- package/vendor/nft/out/utils/types.js +2 -0
- package/vendor/nft/out/utils/types.js.map +1 -0
- package/vendor/nft/out/utils/wrappers.js +659 -0
- package/vendor/nft/out/utils/wrappers.js.map +1 -0
- package/vendor/nft/package.json +4 -0
- package/vendor/nft/upstream.json +51 -0
package/build/app.mjs
CHANGED
|
@@ -17,13 +17,18 @@ import { createNativePackages } from './native-packages.mjs';
|
|
|
17
17
|
import { createRefreshTransform, createSourceLocationTransform, devSingletonsPlugin, devClientFile } from './dev.mjs';
|
|
18
18
|
import { npmPackagesPlugin } from './npm-packages.mjs';
|
|
19
19
|
import { transformCacheComponents } from './cache-components.mjs';
|
|
20
|
+
import {createReactCompilerTransform} from './react-compiler.mjs';
|
|
21
|
+
import {createAppStyleGraph, appStyleScopes, planAppStyleEntries} from './app-styles.mjs';
|
|
22
|
+
import {selectHtmlOnlyRoutes} from './html-only-config.mjs';
|
|
23
|
+
import {createHtmlOnlyGraph} from './html-only-graph.mjs';
|
|
24
|
+
import {createLazyDynamicImports} from './lazy-dynamic-imports.mjs';
|
|
20
25
|
|
|
21
26
|
const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
22
|
-
const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx' };
|
|
27
|
+
const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx', '.mts': 'ts', '.cts': 'ts' };
|
|
23
28
|
const assetLoaders = Object.fromEntries(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'].map(ext => [ext, 'file']));
|
|
24
29
|
const builtins = new Set([...builtinModules, ...builtinModules.map(name => `node:${name}`)]);
|
|
25
|
-
const compatibility = new Set(['link', 'image', 'head', 'router', 'compat/router', 'next-router-context', 'next-app-router-context', 'navigation', 'headers', 'server', 'cache', 'dynamic', 'script', 'og']);
|
|
26
|
-
const forcedClient = new Set(['link.cjs', 'image.cjs', 'head.cjs', 'router.cjs', 'compat-router.cjs', 'next-router-context.cjs', 'next-app-router-context.cjs', 'app-context.cjs', 'script.cjs']);
|
|
30
|
+
const compatibility = new Set(['link', 'image', 'head', 'router', 'compat/router', 'next-router-context', 'next-app-router-context', 'navigation', 'headers', 'server', 'cache', 'dynamic', 'script', 'og', 'offline', 'redis-cache']);
|
|
31
|
+
const forcedClient = new Set(['link.cjs', 'image.cjs', 'head.cjs', 'router.cjs', 'compat-router.cjs', 'next-router-context.cjs', 'next-app-router-context.cjs', 'app-context.cjs', 'script.cjs', 'offline.cjs']);
|
|
27
32
|
|
|
28
33
|
export async function validateReactVersions(resolveFromProject) {
|
|
29
34
|
const names = ['react', 'react-dom', 'react-server-dom-webpack'];
|
|
@@ -39,7 +44,7 @@ export async function validateReactVersions(resolveFromProject) {
|
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
function sourceAst(source, filename) {
|
|
42
|
-
return parse(source, { sourceType: 'unambiguous', sourceFilename: filename, plugins: ['jsx', ...(/\.tsx?$/.test(filename) ? ['typescript'] : [])] });
|
|
47
|
+
return parse(source, { sourceType: 'unambiguous', sourceFilename: filename, plugins: ['jsx', ...(/\.[cm]?tsx?$/.test(filename) ? ['typescript'] : [])] });
|
|
43
48
|
}
|
|
44
49
|
function clientDirective(source, filename) {
|
|
45
50
|
if (!source.includes('use client')) return false;
|
|
@@ -140,7 +145,7 @@ function routeEntry(route, globalError, dev) {
|
|
|
140
145
|
if (globalError) imports.push(`export {default as GlobalError} from ${JSON.stringify(globalError)};`);
|
|
141
146
|
imports.push(`export {default as NavigationBoundary} from ${JSON.stringify(path.join(packageRoot, 'compat/app-navigation-boundary.cjs'))};`);
|
|
142
147
|
imports.push(`export {ClientPageRoot} from ${JSON.stringify(path.join(packageRoot, 'compat/app-client-page.cjs'))};`);
|
|
143
|
-
imports.push(`export {LayoutProvider,LayoutSlot} from ${JSON.stringify(path.join(packageRoot, 'compat/app-layout-context.cjs'))};`);
|
|
148
|
+
imports.push(`export {LayoutProvider,LayoutSlot,ActivitySlot} from ${JSON.stringify(path.join(packageRoot, 'compat/app-layout-context.cjs'))};`);
|
|
144
149
|
if (route.routing || route.segments.some(segment => segment.error)) imports.push(`export {default as ErrorBoundary} from ${JSON.stringify(path.join(packageRoot, 'compat/app-error-boundary.cjs'))};`);
|
|
145
150
|
if (route.routing) {
|
|
146
151
|
let index = 0;
|
|
@@ -173,13 +178,18 @@ function routeEntry(route, globalError, dev) {
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
/** Compile independent React Server and client graphs. The RSC graph contains only references to client boundaries. */
|
|
176
|
-
export async function compileApp({ project, stage, manifest, cssModules, fonts, images, dev, defineEnvironment = {}, productionBrowserSourceMaps = false, serverExternalPackages = [], moduleResolution = {} }) {
|
|
181
|
+
export async function compileApp({ project, stage, manifest, cssModules, fonts, images, dev, devActionSecret, reactCompiler = false, defineEnvironment = {}, productionBrowserSourceMaps = false, serverExternalPackages = [], moduleResolution = {},lazyDynamicImports=false,devLazyRequest,devLazyOnly=false,devLazyActivated=[] }) {
|
|
182
|
+
if(devLazyOnly&&(!dev||!lazyDynamicImports||!devLazyRequest))throw Error('Deferred-only compilation requires a registered development import');
|
|
177
183
|
const refresh = dev ? createRefreshTransform(project.root, {generation:manifest.cacheId,forceGeneration:manifest.devForceRefresh,forceFiles:manifest.devAffectedModules}) : undefined;
|
|
178
184
|
const locations = dev ? createSourceLocationTransform(project.root) : undefined;
|
|
185
|
+
const compileReact = reactCompiler ? createReactCompilerTransform(project.root, reactCompiler, {dev}) : undefined;
|
|
179
186
|
const { basePath = '', assetPrefix = '', assetBase = '/_prnext/assets', trailingSlash = false, skipTrailingSlashRedirect = false } = manifest.config || {};
|
|
180
187
|
const publicPath = assetBase + '/';
|
|
181
|
-
const
|
|
182
|
-
|
|
188
|
+
const lazy=dev&&lazyDynamicImports?createLazyDynamicImports({root:project.root,stage,manifest,request:devLazyRequest,activated:devLazyActivated}):undefined;
|
|
189
|
+
const routes = devLazyOnly?[]:project.routes.filter(route => route.router === 'app');
|
|
190
|
+
const htmlOnly = dev ? new Set() : selectHtmlOnlyRoutes(project.routes, manifest.config.htmlOnly, manifest.config);
|
|
191
|
+
const htmlGraph = !dev && htmlOnly.size ? createHtmlOnlyGraph(project.root) : undefined;
|
|
192
|
+
if (project.appNotFound&&!devLazyOnly) {
|
|
183
193
|
const file = path.join(stage, '.entries', 'app-not-found.jsx');
|
|
184
194
|
await writeFile(file, `import {notFound} from 'next/navigation';export default function MissingRoute(){notFound()}`);
|
|
185
195
|
const segments = project.appNotFound.segments.map(segment => ({ ...segment }));
|
|
@@ -192,11 +202,12 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
192
202
|
routes.push({ ...project.appNotFound, segments, id, file, pattern: '/_not-found', kind: 'page', router: 'app', internal: true });
|
|
193
203
|
manifest.appNotFound = id;
|
|
194
204
|
}
|
|
195
|
-
if (!routes.length) return;
|
|
205
|
+
if (!routes.length&&!devLazyOnly) return;
|
|
196
206
|
if (!manifest.config.cacheComponents && routes.some(route => route.cacheConfig?.instant !== undefined || route.pageConfig?.instant !== undefined || route.segments?.some(segment => segment.staticConfig?.instant !== undefined))) throw new Error('instant requires cacheComponents: true.');
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
207
|
+
if (!manifest.config.cacheComponents && routes.some(route => route.cacheConfig?.ensureStatic !== undefined || route.pageConfig?.ensureStatic !== undefined || route.segments?.some(segment => segment.staticConfig?.ensureStatic !== undefined))) throw new Error('ensureStatic requires cacheComponents: true.');
|
|
208
|
+
const actions = createServerActions({ projectRoot: project.root,developmentSession:dev?manifest.devSession:undefined,developmentSecret:dev?devActionSecret:undefined });
|
|
209
|
+
function actionTransform(source, file, mode, runtime = 'nodejs', revisionSource = source) {
|
|
210
|
+
const result = actions.transform(source, file, mode, {revisionSource});
|
|
200
211
|
if (mode !== 'browser') for (const action of actions.actions.values()) if (action.file === file) {
|
|
201
212
|
(action.runtimes ||= new Set()).add(runtime);
|
|
202
213
|
}
|
|
@@ -204,6 +215,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
204
215
|
}
|
|
205
216
|
const boundaries = new Map();
|
|
206
217
|
const cssFiles = new Set();
|
|
218
|
+
const styleGraph = createAppStyleGraph(project.root);
|
|
207
219
|
const globalErrorCssFiles = new Set();
|
|
208
220
|
const globalErrorOnlyCss = new Set();
|
|
209
221
|
const canonicalPaths = new Map();
|
|
@@ -229,30 +241,33 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
229
241
|
throw new Error('app/global-error must be a Client Component. Add the "use client" directive.');
|
|
230
242
|
}
|
|
231
243
|
|
|
232
|
-
function graphPlugin(mode, { styleTarget = cssFiles, omitGlobalStyles = true } = {}) {
|
|
244
|
+
function graphPlugin(mode, { styleTarget = cssFiles, omitGlobalStyles = true, omitAllStyles = false } = {}) {
|
|
233
245
|
const browser = mode === 'browser';
|
|
234
246
|
const rsc = mode === 'rsc';
|
|
235
247
|
return {
|
|
236
248
|
name: `prnext-app-${mode}`,
|
|
237
249
|
setup(esbuild) {
|
|
238
|
-
applicationEntry(esbuild,readCompilerFile);
|
|
250
|
+
applicationEntry(esbuild,readCompilerFile,{transformTypedHelpers:!!compileReact && (browser || mode==='ssr')});
|
|
239
251
|
esbuild.onResolve({ filter: /^prnext-internal:use-cache$/ }, () => browser
|
|
240
252
|
? { errors: [{ text: "'use cache' cannot enter a Client Component graph" }] }
|
|
241
253
|
: { path: '../compat/use-cache.cjs', external: true });
|
|
242
254
|
if (browser && omitGlobalStyles) esbuild.onResolve({ filter: /\.(?:css|scss|sass)$/i }, async args => {
|
|
243
|
-
if (!globalErrorOnlyCss.size || /\.module\.(?:css|scss|sass)$/i.test(args.path) || args.pluginData?.prnextResolvingCss || args.pluginData?.prnextAppCss || args.kind === 'import-rule' || args.path.startsWith('prnext-css:')) return;
|
|
255
|
+
if ((!omitAllStyles && !globalErrorOnlyCss.size) || /\.module\.(?:css|scss|sass)$/i.test(args.path) || args.pluginData?.prnextResolvingCss || args.pluginData?.prnextAppCss || args.kind === 'import-rule' || args.path.startsWith('prnext-css:')) return;
|
|
244
256
|
const resolved = await esbuild.resolve(args.path, { resolveDir: args.resolveDir, kind: args.kind, pluginData: { prnextAppCss: true } });
|
|
245
|
-
if (!resolved.errors.length && !resolved.external && resolved.namespace === 'file' && globalErrorOnlyCss.has(await canonicalFile(resolved.path))) return { path: resolved.path, namespace: 'prnext-app-empty' };
|
|
257
|
+
if (!resolved.errors.length && !resolved.external && resolved.namespace === 'file' && (omitAllStyles || globalErrorOnlyCss.has(await canonicalFile(resolved.path)))) return { path: resolved.path, namespace: 'prnext-app-empty' };
|
|
246
258
|
});
|
|
247
259
|
esbuild.onResolve({ filter: /^prnext-internal:action-(?:crypto|client|ssr)$/ }, args => {
|
|
248
260
|
const helper = args.path.slice('prnext-internal:'.length);
|
|
249
261
|
if (browser && helper !== 'action-client') return { errors: [{ text: 'Server Action implementation cannot enter the browser graph.' }] };
|
|
250
262
|
return browser ? { path: path.join(packageRoot, 'runtime', helper + '.mjs') } : { path: '../runtime/' + helper + '.mjs', external: true };
|
|
251
263
|
});
|
|
264
|
+
esbuild.onResolve({ filter: /^prnext-internal:metadata-route$/ }, () => browser
|
|
265
|
+
? { errors: [{ text: 'Metadata route handlers are server-only.' }] }
|
|
266
|
+
: { path: '../runtime/metadata-route.mjs', external: true });
|
|
252
267
|
esbuild.onResolve({ filter: frameworkRuntimeImportPattern }, args => {
|
|
253
268
|
const name = frameworkImportName(args.path);
|
|
254
269
|
if (!compatibility.has(name)) return { errors: [{ text: `PRNext does not implement ${args.path} yet (imported by ${args.importer}).` }] };
|
|
255
|
-
if (browser && ['headers', 'server', 'cache', 'og'].includes(name)) return { errors: [{ text: `${args.path} is server-only and cannot be imported by a Client Component.` }] };
|
|
270
|
+
if (browser && ['headers', 'server', 'cache', 'og', 'redis-cache'].includes(name)) return { errors: [{ text: `${args.path} is server-only and cannot be imported by a Client Component.` }] };
|
|
256
271
|
const target = path.join(packageRoot, 'compat', (name === 'compat/router' ? 'compat-router' : name === 'dynamic' ? 'app-dynamic' : rsc && name === 'navigation' ? 'navigation-server' : name) + '.cjs');
|
|
257
272
|
if (!browser && (!rsc || !forcedClient.has(path.basename(target)))) return { path: '../compat/' + path.basename(target), external: true };
|
|
258
273
|
return { path: target };
|
|
@@ -296,18 +311,23 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
296
311
|
if (!/\.module\.(?:css|scss|sass)$/i.test(args.path)) return { path: file, namespace: 'prnext-app-empty' };
|
|
297
312
|
});
|
|
298
313
|
}
|
|
299
|
-
esbuild.onLoad({ filter: /\.(?:[cm]?
|
|
314
|
+
esbuild.onLoad({ filter: /\.(?:[cm]?[jt]s|[jt]sx)$/, namespace: 'file' }, async args => {
|
|
300
315
|
if (browser && args.path === project.document) return { errors: [{ text: 'pages/_document is server-only and cannot be imported by a Client Component.' }] };
|
|
301
316
|
const original=await readCompilerFile(args.path,args.prnextSource);
|
|
302
|
-
|
|
317
|
+
htmlGraph?.source(args.path, original);
|
|
318
|
+
// Only Client Component graphs use React's memo-cache runtime. Server
|
|
319
|
+
// Components, route handlers and Server Action graphs remain untouched.
|
|
320
|
+
const compiled=compileReact && (browser || mode==='ssr') ? await compileReact(original,args.path,{loaderOutput:args.prnextSource!==undefined}) : original;
|
|
321
|
+
const source = await fonts.transform(await expandActionExports(locations?await locations(compiled,args.path):compiled, args.path, async (specifier, importer) => {
|
|
303
322
|
const resolved = await esbuild.resolve(specifier, { resolveDir: path.dirname(importer), kind: 'import-statement', pluginData: { prnextExportScan: true } });
|
|
304
323
|
return resolved.errors.length || resolved.external || resolved.namespace !== 'file' ? undefined : resolved.path;
|
|
305
324
|
}), args.path);
|
|
306
325
|
const cacheSegment = cacheSegments.get(await canonicalFile(args.path)) || '';
|
|
307
326
|
const cachedSource = transformCacheComponents(source, args.path, { enabled: manifest.config.cacheComponents, handlers: manifest.config.cacheHandlers, projectRoot: project.root, mode, buildId: manifest.cacheId, segment: cacheSegment });
|
|
308
|
-
const actionSource = actionTransform(cachedSource, args.path, mode);
|
|
327
|
+
const actionSource = actionTransform(cachedSource, args.path, mode, 'nodejs', original);
|
|
309
328
|
const builtin = path.dirname(args.path) === path.join(packageRoot, 'compat') && forcedClient.has(path.basename(args.path));
|
|
310
329
|
if (rsc && (builtin || clientDirective(source, args.path))) {
|
|
330
|
+
htmlGraph?.issue(args.path, 'a Client Component or interactive framework component');
|
|
311
331
|
let boundary = boundaries.get(args.path);
|
|
312
332
|
if (!boundary) {
|
|
313
333
|
const exports = await exportedNames(args.path, esbuild);
|
|
@@ -317,8 +337,9 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
317
337
|
const references = boundary.exports.map((name, index) => builtin && path.basename(args.path) === 'image.cjs' && name === 'getImageProps' ? `export {getImageProps} from ${JSON.stringify(path.join(packageRoot, 'compat/image-shared.cjs'))};` : `const ref${index}=registerClientReference(function(){throw new Error(${JSON.stringify(`Cannot call client export ${name} from a Server Component (${path.relative(project.root, args.path)}).`)});},${JSON.stringify(boundary.id)},${JSON.stringify(name)});export {ref${index} as ${JSON.stringify(name)}};`);
|
|
318
338
|
return { contents: `import {registerClientReference} from 'react-server-dom-webpack/server';\n${references.join('\n')}`, loader: 'js', resolveDir: path.dirname(args.path) };
|
|
319
339
|
}
|
|
320
|
-
let contents = transformDynamicImports(actionSource, args.path, { projectRoot: project.root, mode: rsc ? 'rsc' : browser ? 'browser' : 'server' });
|
|
321
|
-
if
|
|
340
|
+
let contents = transformDynamicImports(actionSource, args.path, { projectRoot: project.root, mode: rsc ? 'rsc' : browser ? 'browser' : 'server',dev });
|
|
341
|
+
if(browser&&lazy)contents=lazy.transform(contents,args.path);
|
|
342
|
+
if (browser && refresh && !actions.isServerModule(cachedSource,args.path,{revisionSource:original})) contents = await refresh(contents, args.path);
|
|
322
343
|
return { contents, loader: extensions[path.extname(args.path)] || 'jsx', resolveDir: path.dirname(args.path) };
|
|
323
344
|
});
|
|
324
345
|
},
|
|
@@ -334,7 +355,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
334
355
|
const rsc = mode === 'rsc';
|
|
335
356
|
const framework = new Set(['link', 'image', 'head', 'router', 'compat-router', 'next-router-context', 'next-app-router-context', 'navigation', 'script', 'app-context', 'app-layout-context', 'app-navigation-boundary', 'app-client-page', 'app-error-boundary', 'app-dynamic']);
|
|
336
357
|
return { name: 'prnext-edge-app', setup(esbuild) {
|
|
337
|
-
applicationEntry(esbuild,readCompilerFile);
|
|
358
|
+
applicationEntry(esbuild,readCompilerFile,{transformTypedHelpers:!!compileReact && mode==='ssr'});
|
|
338
359
|
esbuild.onResolve({filter:/^prnext-internal:action-(?:crypto|ssr)$/}, args => ({path:args.path.slice('prnext-internal:'.length),namespace:'prnext-edge-action'}));
|
|
339
360
|
esbuild.onLoad({filter:/.*/,namespace:'prnext-edge-action'}, args => ({loader:'js',contents:(args.path==='action-crypto'?['encryptBoundArgs','decryptBoundArgs','bindEncryptedReference']:['createServerReference']).map(name=>`export const ${name}=globalThis.__PRNEXT_EDGE_ACTIONS[${JSON.stringify(args.path)}][${JSON.stringify(name)}];`).join('\n')}));
|
|
340
361
|
esbuild.onResolve({ filter: frameworkSubpathPattern }, args => {
|
|
@@ -362,7 +383,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
362
383
|
const names = await exportedNames(args.path, esbuild), name = path.basename(args.path, '.cjs');
|
|
363
384
|
return { loader: 'js', contents: `const module=globalThis.__PRNEXT_EDGE_FRAMEWORK[${JSON.stringify(name)}];\n` + [...names].map((name, index) => `const value${index}=${name === 'default' ? '(module.default||module)' : `module[${JSON.stringify(name)}]`};export {value${index} as ${JSON.stringify(name)}};`).join('\n') };
|
|
364
385
|
});
|
|
365
|
-
if (rsc) esbuild.onLoad({ filter: /\.(?:[cm]?
|
|
386
|
+
if (rsc) esbuild.onLoad({ filter: /\.(?:[cm]?[jt]s|[jt]sx)$/, namespace: 'file' }, async args => {
|
|
366
387
|
const source = args.prnextSource ?? await readFile(args.path, 'utf8');
|
|
367
388
|
const builtin = path.dirname(args.path) === path.join(packageRoot, 'compat') && forcedClient.has(path.basename(args.path));
|
|
368
389
|
if (!builtin && !clientDirective(source, args.path)) return;
|
|
@@ -377,13 +398,18 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
377
398
|
} };
|
|
378
399
|
}
|
|
379
400
|
async function edgeAppBuild(file, outfile, mode, defaultExport = false) {
|
|
380
|
-
const result = await compileEdge({ file, outfile, projectRoot: project.root, dev, defineEnvironment, reactMode: mode, defaultExport,
|
|
401
|
+
const result = await compileEdge({ file, outfile, projectRoot: project.root, dev, defineEnvironment, reactMode: mode, defaultExport, transformTypedHelpers:!!compileReact && mode==='ssr',
|
|
381
402
|
plugins: [moduleResolutionPlugin(moduleResolution, project.root), edgeGraphPlugin(mode), images.plugin(), fonts.plugin(), cssModules.plugin(false)],
|
|
382
|
-
transformSource: async (source, file, builder
|
|
383
|
-
const
|
|
384
|
-
return
|
|
385
|
-
|
|
403
|
+
transformSource: async (source, file, builder, {loaderOutput=false}={}) => {
|
|
404
|
+
const compiled=compileReact && mode==='ssr' ? await compileReact(source,file,{loaderOutput}) : source;
|
|
405
|
+
return actionTransform(await fonts.transform(await expandActionExports(locations?await locations(compiled,file):compiled, file, async (specifier, importer) => {
|
|
406
|
+
const resolved = await builder.resolve(specifier, {resolveDir:path.dirname(importer),kind:'import-statement',pluginData:{prnextExportScan:true}});
|
|
407
|
+
return resolved.errors.length || resolved.external || resolved.namespace !== 'file' ? undefined : resolved.path;
|
|
408
|
+
}), file), file, mode, 'edge', source);
|
|
409
|
+
} });
|
|
386
410
|
fonts.record(result.metafile);
|
|
411
|
+
styleGraph.record(result.metafile);
|
|
412
|
+
htmlGraph?.record(result.metafile);
|
|
387
413
|
}
|
|
388
414
|
async function serverBuild(entryPoints, outdir, mode, styleTarget = cssFiles) {
|
|
389
415
|
if (!Object.keys(entryPoints).length) return;
|
|
@@ -411,6 +437,8 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
411
437
|
}
|
|
412
438
|
}
|
|
413
439
|
fonts.record(output.metafile);
|
|
440
|
+
styleGraph.record(output.metafile);
|
|
441
|
+
htmlGraph?.record(output.metafile);
|
|
414
442
|
for (const file of output.outputFiles) {
|
|
415
443
|
await mkdir(path.dirname(file.path), { recursive: true });
|
|
416
444
|
await writeFile(file.path, file.contents);
|
|
@@ -424,22 +452,25 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
424
452
|
return output;
|
|
425
453
|
}
|
|
426
454
|
|
|
427
|
-
async function collectBrowserStyles(browserEntries) {
|
|
428
|
-
if (!globalErrorId) return;
|
|
455
|
+
async function collectBrowserStyles(browserEntries,force=false) {
|
|
429
456
|
const revision = `${boundaries.size}:${actions.actions.size}:${nativePackages.version}`;
|
|
430
|
-
if (browserStyleRevision === revision) return;
|
|
457
|
+
if (!force&&browserStyleRevision === revision) return;
|
|
431
458
|
// SSR intentionally erases ssr:false loaders. Discover their dependencies
|
|
432
459
|
// in one browser graph, then classify styles by reachability from each root.
|
|
433
460
|
const discovery = await bundle({ ...common, write: false, entryPoints: browserEntries, outdir: path.join(stage, '.browser-styles'),
|
|
434
461
|
platform: 'browser', format: 'esm', target: ['es2022'], splitting: true,
|
|
435
462
|
define: { 'process.env': '{}', ...common.define },
|
|
436
|
-
plugins: [moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), nativePackages.plugin('browser'), cssModules.plugin(true)] });
|
|
463
|
+
plugins: [...(lazy?[lazy.plugin()]:[]),moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), nativePackages.plugin('browser'), cssModules.plugin(true)] });
|
|
464
|
+
await lazy?.discover(discovery.metafile);
|
|
437
465
|
fonts.record(discovery.metafile);
|
|
438
|
-
|
|
466
|
+
styleGraph.record(discovery.metafile);
|
|
467
|
+
const entryIds = new Map((await Promise.all(Object.entries(browserEntries).map(async ([id, file]) => [[path.resolve(file), id], [await canonicalFile(file), id]]))).flat());
|
|
439
468
|
const globalRoots = [], normalRoots = [];
|
|
440
469
|
for (const output of Object.values(discovery.metafile.outputs)) {
|
|
441
470
|
if (!output.entryPoint) continue;
|
|
442
|
-
|
|
471
|
+
// Dynamic entry points can contain a query/fragment identity rather
|
|
472
|
+
// than a physical filename. Only configured roots need classification.
|
|
473
|
+
const id = entryIds.get(path.resolve(project.root, output.entryPoint));
|
|
443
474
|
if (id) (id === globalErrorId ? globalRoots : normalRoots).push(output.entryPoint);
|
|
444
475
|
}
|
|
445
476
|
async function collect(roots, target) {
|
|
@@ -477,6 +508,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
477
508
|
const collectInstant = node => {if(!node)return;instantConfigs.push(...Object.values(node.config || {}));for(const child of [...(node.children || []),...Object.values(node.slots || {})])collectInstant(child);};
|
|
478
509
|
collectInstant(route.routing);
|
|
479
510
|
manifest.routes.push({ id: route.id, pattern: route.pattern, kind: route.kind, router: 'app', module: `server/${route.id}.mjs`, css: [], ...(route.internal ? { internal: true } : {}),
|
|
511
|
+
...(!dev && htmlOnly.has(route.id) ? { htmlOnly:true } : {}),
|
|
480
512
|
...(route.parallel ? { parallel: true, interception: !!route.interception } : {}),
|
|
481
513
|
...(route.cacheConfig ? { cacheConfig: route.cacheConfig } : {}),
|
|
482
514
|
...(route.kind === 'page' ? { instantSamples: [route.pageConfig, ...[...route.segments].reverse().map(segment => segment.staticConfig)].find(config => config?.instant?.unstable_samples)?.instant.unstable_samples, instantBuild: instantConfigs.some(config => config?.instant?.level === 'experimental-error'), hasStaticParams: route.id !== manifest.appNotFound && !!(route.pageConfig?.generateStaticParams || route.segments.some(segment => segment.staticConfig?.generateStaticParams)) }
|
|
@@ -489,12 +521,13 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
489
521
|
const browserRevision = () => `${boundaryRevision()}:${cssFiles.size}:${globalErrorCssFiles.size}:${nativePackages.version}`;
|
|
490
522
|
// Route Handlers have a Node React environment and must not share RSC chunks
|
|
491
523
|
// that import registerServerReference or client-reference registration.
|
|
492
|
-
await serverBuild(handlerEntries, 'server', 'route');
|
|
524
|
+
if (!devLazyOnly) await serverBuild(handlerEntries, 'server', 'route');
|
|
493
525
|
for (;;) {
|
|
494
526
|
// Each graph consumes discoveries from the preceding one. Discovering a
|
|
495
527
|
// client boundary in RSC is normal: SSR and browser below already include
|
|
496
528
|
// it. Only discoveries made too late for a graph require another pass.
|
|
497
529
|
const emittedActions = actionRevision();
|
|
530
|
+
if (!devLazyOnly) {
|
|
498
531
|
for (const action of actions.actions.values()) {
|
|
499
532
|
const entry = path.join(stage, '.entries', 'action-' + action.id + '.mjs');
|
|
500
533
|
await writeFile(entry, `export {${JSON.stringify(action.binding)} as invoke} from ${JSON.stringify(action.file)};`);
|
|
@@ -502,8 +535,10 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
502
535
|
if (!action.runtimes?.size || action.runtimes.has('nodejs')) entries['action-' + action.id] = entry;
|
|
503
536
|
}
|
|
504
537
|
await serverBuild(entries, 'server', 'rsc');
|
|
538
|
+
}
|
|
505
539
|
const serverNativeVersion = nativePackages.version;
|
|
506
540
|
const emittedBoundaries = boundaryRevision();
|
|
541
|
+
if (!devLazyOnly) {
|
|
507
542
|
const ssrEntries = {};
|
|
508
543
|
const globalSsrEntries = {};
|
|
509
544
|
for (const boundary of boundaries.values()) {
|
|
@@ -514,54 +549,95 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
514
549
|
await serverBuild(ssrEntries, 'app-ssr', 'ssr');
|
|
515
550
|
for (const boundary of boundaries.values()) if (boundary.edge) await edgeAppBuild(path.join(stage, '.entries', boundary.id + '.ssr.mjs'), path.join(stage, 'app-ssr-edge', boundary.id + '.mjs'), 'ssr', boundary.exports.includes('default'));
|
|
516
551
|
await serverBuild(globalSsrEntries, 'app-ssr', 'ssr', globalErrorCssFiles);
|
|
552
|
+
}
|
|
517
553
|
const browserEntries = {};
|
|
518
554
|
for (const boundary of boundaries.values()) {
|
|
519
555
|
const entry = path.join(stage, '.entries', boundary.id + '.browser.mjs');
|
|
520
556
|
await writeFile(entry, boundaryEntry(boundary));
|
|
521
557
|
browserEntries[boundary.id] = entry;
|
|
522
558
|
}
|
|
523
|
-
await collectBrowserStyles(browserEntries);
|
|
559
|
+
if(!devLazyRequest)await collectBrowserStyles(browserEntries);
|
|
560
|
+
let lazyEntries={};
|
|
561
|
+
if(lazy)for(;;){
|
|
562
|
+
const next=await lazy.targetEntries();
|
|
563
|
+
if(Object.keys(next).join(',')===Object.keys(lazyEntries).join(','))break;
|
|
564
|
+
lazyEntries=next;
|
|
565
|
+
await collectBrowserStyles(devLazyRequest?lazyEntries:{...browserEntries,...lazyEntries},true);
|
|
566
|
+
}
|
|
524
567
|
globalErrorOnlyCss.clear();
|
|
525
568
|
for (const file of globalErrorCssFiles) if (!cssFiles.has(file)) globalErrorOnlyCss.add(file);
|
|
526
569
|
const emittedBrowserInputs = browserRevision();
|
|
527
|
-
if (routes.some(route => route.kind === 'page')) {
|
|
570
|
+
if (devLazyOnly||routes.some(route => route.kind === 'page')) {
|
|
528
571
|
let globalErrorCss = [];
|
|
529
572
|
if (globalErrorCssFiles.size) {
|
|
530
573
|
const styleEntry = path.join(stage, '.entries', 'app-global-error-style.mjs');
|
|
531
574
|
await writeFile(styleEntry, [...globalErrorCssFiles].map(file => `import ${JSON.stringify(file)};`).join('\n'));
|
|
532
575
|
const styles = await bundle({ ...common, entryPoints: { 'app-global-error-style': styleEntry }, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]',
|
|
533
576
|
platform: 'browser', format: 'esm', target: ['es2022'], minify: !dev, sourcemap: dev || productionBrowserSourceMaps,
|
|
534
|
-
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] });
|
|
577
|
+
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] }, { role: 'styles' });
|
|
535
578
|
globalErrorCss = Object.values(styles.metafile.outputs).filter(output => output.cssBundle).map(output => publicPath + path.basename(output.cssBundle));
|
|
536
579
|
}
|
|
537
580
|
if (globalErrorId) manifest.app.globalError = { id: globalErrorId, name: 'default', css: globalErrorCss };
|
|
538
581
|
const runtimeEntry = path.join(stage, '.entries', 'app-runtime.mjs');
|
|
582
|
+
const htmlOnlyRoutes = !dev && htmlOnly.size ? routes.filter(route => route.kind === 'page' && !route.internal).map(route => ({pattern:route.pattern,...(htmlOnly.has(route.id)?{htmlOnly:true}:{})})) : undefined;
|
|
539
583
|
const importers = Object.entries(browserEntries).map(([id, file]) => `${JSON.stringify(id)}:()=>import(${JSON.stringify(file)})`);
|
|
540
|
-
await writeFile(runtimeEntry, `${dev ? `import ${JSON.stringify(devClientFile)};\n` : ''}
|
|
541
|
-
const clientEntries = { 'app-runtime': runtimeEntry, ...browserEntries };
|
|
584
|
+
if(!devLazyRequest)await writeFile(runtimeEntry, `${dev ? `import ${JSON.stringify(devClientFile)};\n` : ''}\nimport {bootstrapApp} from ${JSON.stringify(path.join(packageRoot, 'runtime/app-client.mjs'))};\nbootstrapApp({clientModules:{${importers.join(',')}},...${JSON.stringify({ basePath, assetPrefix, assetBase, htmlOnlyRoutes, trailingSlash, skipTrailingSlashRedirect, deploymentId: dev ? undefined : manifest.cacheId, strictMode: manifest.config.reactStrictMode !== false, cacheComponents: manifest.config.cacheComponents === true, partialPrefetching: manifest.config.partialPrefetching === true, navigationCache: manifest.config.navigationCache, ...(globalErrorId ? { globalErrorId, globalErrorCss } : {}), ...(dev ? { dev: { buildId: manifest.buildId, clientManifest: manifest.devClient, onDemand:manifest.devOnDemand===true, sequence:manifest.devSequence, session:manifest.devSession, indicator:manifest.devIndicators, ...(manifest.devTools ? {tools:manifest.devTools} : {}) } } : {}) })}});\n`);
|
|
585
|
+
const clientEntries = devLazyRequest?{...lazyEntries,...lazy.bridgeEntries()}:{ 'app-runtime': runtimeEntry, ...browserEntries,...lazyEntries,...(lazy?lazy.bridgeEntries():{}) };
|
|
542
586
|
const browser = await bundle({ ...common, entryPoints: clientEntries, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]', chunkNames: 'app-chunk-[hash]', platform: 'browser', format: 'esm', target: ['es2022'], splitting: true, minify: !dev, sourcemap: dev || productionBrowserSourceMaps,
|
|
543
|
-
define: { 'process.env': '{}', ...common.define }, plugins: [moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser'), nativePackages.plugin('browser'), cssModules.plugin(true, { emitStyles:
|
|
587
|
+
define: { 'process.env': '{}', ...common.define, __PRNEXT_CACHE_COMPONENTS__: JSON.stringify(manifest.config.cacheComponents === true) }, plugins: [...(lazy?[lazy.plugin()]:[]),moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitAllStyles: !lazy }), nativePackages.plugin('browser'), cssModules.plugin(true, { emitStyles: () => !!lazy })] });
|
|
544
588
|
fonts.record(browser.metafile);
|
|
545
589
|
const entryIds = new Map(Object.entries(clientEntries).map(([id, file]) => [file, id]));
|
|
546
590
|
const edgeBoundaries = new Set([...boundaries.values()].filter(boundary => boundary.edge).map(boundary => boundary.id));
|
|
547
|
-
const styles = new Set();
|
|
548
591
|
let client;
|
|
549
592
|
for (const [output, metadata] of Object.entries(browser.metafile.outputs)) {
|
|
550
|
-
if (metadata.cssBundle) styles.add(publicPath + path.basename(metadata.cssBundle));
|
|
551
593
|
if (!metadata.entryPoint || !output.endsWith('.js')) continue;
|
|
594
|
+
if(lazy?.record(metadata,output))continue;
|
|
552
595
|
const id = entryIds.get(path.resolve(project.root, metadata.entryPoint));
|
|
553
596
|
if (id === 'app-runtime') { client = publicPath + path.basename(output); continue; }
|
|
554
597
|
if (!id) continue;
|
|
555
598
|
manifest.app.clientModules[id] = { id, chunks: [id, publicPath + path.basename(output)], name: '*', ssrModule: `app-ssr/${id}.mjs`, ...(edgeBoundaries.has(id) ? { edgeSsrModule: `app-ssr-edge/${id}.mjs` } : {}), browserModule: publicPath + path.basename(output) };
|
|
556
599
|
}
|
|
557
|
-
|
|
600
|
+
const styleBundles = new Map();
|
|
601
|
+
for (const route of routes.filter(route => route.kind === 'page')) {
|
|
602
|
+
// All esbuild graphs canonicalize their inputs, including symlinked
|
|
603
|
+
// convention files. Match those identities before querying reachability.
|
|
604
|
+
for (const files of appStyleScopes(route)) for (const file of files) styleGraph.alias(file, await canonicalFile(file));
|
|
605
|
+
const plan = planAppStyleEntries(route, styleGraph);
|
|
606
|
+
const styles = [];
|
|
607
|
+
const cssScopes = [];
|
|
608
|
+
for (const { styles:files, path:scopePath, kind } of plan) {
|
|
609
|
+
const signature = JSON.stringify(files);
|
|
610
|
+
if (!styleBundles.has(signature)) {
|
|
611
|
+
const id = 'app-style-' + createHash('sha256').update(files.map(file => path.relative(project.root, file).replaceAll(path.sep, '/')).join('\0')).digest('hex').slice(0, 16);
|
|
612
|
+
const styleEntry = path.join(stage, '.entries', id + '.mjs');
|
|
613
|
+
await writeFile(styleEntry, files.map(file => `import ${JSON.stringify(file)};`).join('\n'));
|
|
614
|
+
const output = await bundle({ ...common, entryPoints: { [id]: styleEntry }, outdir: path.join(stage, 'assets'), entryNames: '[name]-[hash]', platform: 'browser', format: 'esm', target: ['es2022'], minify: !dev, sourcemap: dev || productionBrowserSourceMaps,
|
|
615
|
+
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] }, { role: 'styles' });
|
|
616
|
+
styleBundles.set(signature, Object.values(output.metafile.outputs).filter(output => output.cssBundle).map(output => publicPath + path.basename(output.cssBundle)));
|
|
617
|
+
}
|
|
618
|
+
const css = styleBundles.get(signature);
|
|
619
|
+
styles.push(...css);
|
|
620
|
+
if (route.parallel) cssScopes.push({ path:scopePath, kind, css });
|
|
621
|
+
}
|
|
622
|
+
const emitted = manifest.routes.find(item => item.id === route.id);
|
|
623
|
+
if (!emitted.htmlOnly) emitted.client = client;
|
|
624
|
+
emitted.css = [...new Set(styles)];
|
|
625
|
+
if (route.parallel) emitted.cssScopes = cssScopes;
|
|
626
|
+
}
|
|
558
627
|
}
|
|
559
628
|
manifest.app.actions = Object.fromEntries([...actions.actions.values()].map(action => [action.id, {
|
|
560
629
|
module: `server/action-${action.runtimes?.has('nodejs')?'':'edge-'}${action.id}.mjs`, export: 'invoke',
|
|
561
630
|
...(action.runtimes?.has('edge') ? {edgeModule:`server/action-edge-${action.id}.mjs`} : {}),
|
|
562
631
|
}]));
|
|
563
|
-
if (emittedActions === actionRevision() && emittedBoundaries === boundaryRevision() &&
|
|
632
|
+
if ((devLazyOnly || emittedActions === actionRevision() && emittedBoundaries === boundaryRevision()) &&
|
|
564
633
|
serverNativeVersion === nativePackages.version && emittedBrowserInputs === browserRevision()) break;
|
|
565
634
|
}
|
|
566
635
|
|
|
636
|
+
lazy?.finish();
|
|
637
|
+
|
|
638
|
+
if (htmlGraph) for (const route of routes) if (htmlOnly.has(route.id)) {
|
|
639
|
+
for (const file of [...appStyleScopes(route).flat(), ...(route.metadataFiles || []).map(item=>item.file)].filter(Boolean)) htmlGraph.alias(file, await canonicalFile(file));
|
|
640
|
+
htmlGraph.validate(route);
|
|
641
|
+
}
|
|
642
|
+
|
|
567
643
|
}
|
package/build/bundle-runtime.mjs
CHANGED
|
@@ -15,7 +15,7 @@ export const runtimeEntryNames = Object.freeze([
|
|
|
15
15
|
].sort());
|
|
16
16
|
|
|
17
17
|
/** Run once, after compiling application/Edge graphs and before any prerender. */
|
|
18
|
-
export async function bundleRuntime(stage,{projectRoot}={}) {
|
|
18
|
+
export async function bundleRuntime(stage,{projectRoot,sourceDirectory}={}) {
|
|
19
19
|
const resolvedStage = path.resolve(stage);
|
|
20
20
|
if (!path.basename(resolvedStage).startsWith('.prnext-build-')) {
|
|
21
21
|
throw new Error('Runtime bundling requires an unpublished PRNext build stage.');
|
|
@@ -29,20 +29,31 @@ export async function bundleRuntime(stage,{projectRoot}={}) {
|
|
|
29
29
|
if (path.relative(canonicalStage, canonicalRuntime) !== 'runtime') {
|
|
30
30
|
throw new Error('Runtime bundling escaped the build stage.');
|
|
31
31
|
}
|
|
32
|
-
const
|
|
33
|
-
const
|
|
32
|
+
const input=sourceDirectory?await realpath(sourceDirectory):runtime;
|
|
33
|
+
const entries = await readdir(input, { withFileTypes: true });
|
|
34
|
+
const originals = entries.filter(entry => entry.name.endsWith('.mjs')&&!entry.name.endsWith('.test.mjs'));
|
|
34
35
|
if (originals.some(entry => !entry.isFile() || entry.isSymbolicLink())) {
|
|
35
36
|
throw new Error('Runtime source modules must be ordinary files.');
|
|
36
37
|
}
|
|
37
38
|
for (const name of runtimeEntryNames) {
|
|
38
39
|
if (!originals.some(entry => entry.name === `${name}.mjs`)) throw new Error(`Missing runtime entry ${name}.mjs`);
|
|
39
40
|
}
|
|
41
|
+
const sources=new Map();
|
|
42
|
+
// Production compiles the installed sources directly. Only generated stage
|
|
43
|
+
// overrides (currently the bundled env loader) replace an installed module.
|
|
44
|
+
// Hash and compile the same captured bytes; do not trust mtimes or skip app
|
|
45
|
+
// graph callbacks. This avoids copying hundreds of modules into every stage.
|
|
46
|
+
for(const entry of originals){
|
|
47
|
+
let file=path.join(input,entry.name);
|
|
48
|
+
if(sourceDirectory){const override=path.join(runtime,entry.name);const info=await lstat(override).catch(e=>{if(e.code!=='ENOENT')throw e;});if(info){if(!info.isFile()||info.isSymbolicLink())throw Error('Runtime overrides must be ordinary files');file=override;}}
|
|
49
|
+
sources.set(path.join(input,entry.name),await readFile(file));
|
|
50
|
+
}
|
|
40
51
|
let directory,key,cached;
|
|
41
52
|
if(projectRoot){
|
|
42
|
-
directory=await privateCacheDirectory(projectRoot,'runtime-bundle-
|
|
53
|
+
directory=await privateCacheDirectory(projectRoot,'runtime-bundle-v2');
|
|
43
54
|
if(directory){
|
|
44
55
|
const hash=createHash('sha256').update(esbuildVersion).update(JSON.stringify(runtimeEntryNames)).update(await readFile(new URL('./bundle-runtime.mjs',import.meta.url)));
|
|
45
|
-
for(const entry of [...originals].sort((a,b)=>a.name.localeCompare(b.name)))hash.update(JSON.stringify([entry.name,createHash('sha256').update(
|
|
56
|
+
for(const entry of [...originals].sort((a,b)=>a.name.localeCompare(b.name)))hash.update(JSON.stringify([entry.name,createHash('sha256').update(sources.get(path.join(input,entry.name))).digest('hex')]));
|
|
46
57
|
key=hash.digest('hex');cached=await readCacheRecord(directory,key);
|
|
47
58
|
}
|
|
48
59
|
}
|
|
@@ -52,10 +63,10 @@ export async function bundleRuntime(stage,{projectRoot}={}) {
|
|
|
52
63
|
const virtualOutput = path.join(resolvedStage, '.runtime-bundle');
|
|
53
64
|
const validCache=cached&&Array.isArray(cached.files)&&cached.files.length<=128&&cached.files.every(item=>Array.isArray(item)&&item.length===2&&/^[\w-]+\.mjs$/.test(item[0])&&typeof item[1]==='string')&&new Set(cached.files.map(([name])=>name)).size===cached.files.length&&runtimeEntryNames.every(name=>cached.files.some(([file])=>file===name+'.mjs'));
|
|
54
65
|
const result = validCache?null:await build({
|
|
55
|
-
absWorkingDir:
|
|
56
|
-
entryPoints: runtimeEntryNames.map(name => path.join(
|
|
66
|
+
absWorkingDir: input,
|
|
67
|
+
entryPoints: runtimeEntryNames.map(name => path.join(input, `${name}.mjs`)),
|
|
57
68
|
outdir: virtualOutput,
|
|
58
|
-
outbase:
|
|
69
|
+
outbase: input,
|
|
59
70
|
entryNames: '[name]',
|
|
60
71
|
chunkNames: '_shared-[hash]',
|
|
61
72
|
outExtension: { '.js': '.mjs' },
|
|
@@ -73,6 +84,7 @@ export async function bundleRuntime(stage,{projectRoot}={}) {
|
|
|
73
84
|
metafile: true,
|
|
74
85
|
write: false,
|
|
75
86
|
logLevel: 'silent',
|
|
87
|
+
plugins:[{name:'prnext-runtime-snapshot',setup(build){build.onLoad({filter:/\.mjs$/},args=>sources.has(args.path)?{contents:sources.get(args.path),loader:'js',resolveDir:path.dirname(args.path)}:undefined);}}],
|
|
76
88
|
});
|
|
77
89
|
const emitted = new Map();
|
|
78
90
|
if(validCache)for(const [name,contents]of cached.files)emitted.set(name,Buffer.from(contents));
|
|
@@ -87,7 +99,8 @@ export async function bundleRuntime(stage,{projectRoot}={}) {
|
|
|
87
99
|
// This is an unpublished, disposable stage. On any write failure buildProject
|
|
88
100
|
// discards the stage and preserves the previous published build unchanged.
|
|
89
101
|
for (const [name, contents] of emitted) await writeFile(path.join(runtime, name), contents);
|
|
90
|
-
for (const entry of
|
|
102
|
+
for (const entry of await readdir(runtime,{withFileTypes:true})) {
|
|
103
|
+
if(!entry.name.endsWith('.mjs')||!entry.isFile())continue;
|
|
91
104
|
if (!emitted.has(entry.name)) await unlink(path.join(runtime, entry.name));
|
|
92
105
|
}
|
|
93
106
|
// JSON, CJS, license files and the OG assets directory are never traversed or
|
|
@@ -15,7 +15,7 @@ export function transformCacheComponents(source, file, { enabled, projectRoot, m
|
|
|
15
15
|
return cachedTransform('cache-components',source,[file,enabled,projectRoot,mode,buildId,configuredSegment,handlers],()=>transformUncached(source,file,{enabled,projectRoot,mode,buildId,configuredSegment,handlers}));
|
|
16
16
|
}
|
|
17
17
|
function transformUncached(source,file,{enabled,projectRoot,mode,buildId,configuredSegment,handlers}) {
|
|
18
|
-
const ast = parse(source, { sourceType: 'unambiguous', sourceFilename: file, plugins: ['jsx', ...(/\.tsx?$/.test(file) ? ['typescript'] : [])] });
|
|
18
|
+
const ast = parse(source, { sourceType: 'unambiguous', sourceFilename: file, plugins: ['jsx', ...(/\.[cm]?tsx?$/.test(file) ? ['typescript'] : [])] });
|
|
19
19
|
let program;
|
|
20
20
|
traverse(ast, { Program(value) { program = value; value.stop(); } });
|
|
21
21
|
const moduleDirective = ast.program.directives.find(cacheDirective);
|
package/build/cache-handlers.mjs
CHANGED
|
@@ -47,7 +47,7 @@ export async function prepareCacheHandlers({ config, projectRoot, stage, environ
|
|
|
47
47
|
platform: 'node', target: 'node22', format: 'esm', packages: 'external', logLevel: 'silent', define: environment,
|
|
48
48
|
banner: { js: "import {createRequire as __prnextCreateRequire} from 'node:module';const require=__prnextCreateRequire(import.meta.url);" },
|
|
49
49
|
plugins: [{ name: 'cache-handler-compatibility', setup(builder) {
|
|
50
|
-
builder.onResolve({ filter: /^(?:next|prnext|@thomas\.f\/prnext)\/(cache
|
|
50
|
+
builder.onResolve({ filter: /^(?:next|prnext|@thomas\.f\/prnext)\/(cache(?:\/redis)?|headers|server)(?:\.js)?$/ }, ({ path: specifier }) => ({ path: `../compat/${frameworkImportName(specifier)}.cjs`, external: true }));
|
|
51
51
|
} }],
|
|
52
52
|
});
|
|
53
53
|
result[name] = output;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import postcss from 'postcss';
|
|
2
|
+
import selectors from 'postcss-selector-parser';
|
|
3
|
+
import values from 'postcss-value-parser';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
|
|
6
|
+
// Names are independent of compilation order and of which routes are built.
|
|
7
|
+
// A collision fails the build instead of changing one of two published maps.
|
|
8
|
+
export function compactCssModule(css, exports, {globals = new Set(), hash = name => 'p' + createHash('sha256').update(name).digest('base64url').slice(0, 10)} = {}) {
|
|
9
|
+
const names = new Map(), reverse = new Map();
|
|
10
|
+
for (const list of Object.values(exports)) for (const name of list.split(/\s+/).filter(Boolean)) {
|
|
11
|
+
if (names.has(name) || globals.has(name)) continue;
|
|
12
|
+
const short = hash(name);
|
|
13
|
+
if (!/^[a-zA-Z_][\w-]*$/.test(short) || globals.has(short) && short!==name || reverse.has(short) && reverse.get(short) !== name) throw new Error('CSS Module compact-name collision.');
|
|
14
|
+
names.set(name, short); reverse.set(short, name);
|
|
15
|
+
}
|
|
16
|
+
const root = postcss.parse(css);
|
|
17
|
+
root.walkRules(rule => {
|
|
18
|
+
rule.selector = selectors(ast => ast.walkClasses(node => {
|
|
19
|
+
if (names.has(node.value)) node.value = names.get(node.value);
|
|
20
|
+
})).processSync(rule.selector);
|
|
21
|
+
});
|
|
22
|
+
root.walkAtRules(/^(?:-webkit-)?keyframes$/i, rule => {
|
|
23
|
+
const parsed = values(rule.params);
|
|
24
|
+
parsed.walk(node => { if (node.type === 'word' && names.has(node.value)) node.value = names.get(node.value); });
|
|
25
|
+
rule.params = parsed.toString();
|
|
26
|
+
});
|
|
27
|
+
root.walkDecls(/^(?:-webkit-)?animation(?:-name)?$/i, declaration => {
|
|
28
|
+
const parsed = values(declaration.value);
|
|
29
|
+
parsed.walk(node => { if (node.type === 'word' && names.has(node.value)) node.value = names.get(node.value); });
|
|
30
|
+
declaration.value = parsed.toString();
|
|
31
|
+
});
|
|
32
|
+
return { css: root.toString(), names:[...names], mapping: Object.fromEntries(Object.entries(exports).map(([key, list]) => [key, list.split(/\s+/).map(name => names.get(name) || name).join(' ')])) };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function globalCssNames(css, {local = true} = {}, result = new Set()) {
|
|
36
|
+
const root = postcss.parse(css);
|
|
37
|
+
root.walkRules(rule => {
|
|
38
|
+
selectors(ast => ast.each(selector => {
|
|
39
|
+
let global = !local;
|
|
40
|
+
selector.each(node => {
|
|
41
|
+
if (node.type === 'pseudo' && [':global',':local'].includes(node.value)) {
|
|
42
|
+
if (node.nodes?.length) { if (node.value === ':global') node.walkClasses(item=>result.add(item.value)); }
|
|
43
|
+
else global = node.value === ':global';
|
|
44
|
+
} else if (global) { if(node.type==='class') result.add(node.value); node.walkClasses?.(item=>result.add(item.value)); }
|
|
45
|
+
});
|
|
46
|
+
})).processSync(rule.selector);
|
|
47
|
+
});
|
|
48
|
+
root.walkDecls(/^composes$/i, declaration => {
|
|
49
|
+
const words = values(declaration.value).nodes.filter(node=>node.type==='word').map(node=>node.value);
|
|
50
|
+
if (words.at(-1)==='global' && words.at(-2)==='from') for(const name of words.slice(0,-2)) result.add(name);
|
|
51
|
+
});
|
|
52
|
+
return result;
|
|
53
|
+
}
|