@thomas.f/prnext 0.1.17-alpha → 0.1.18-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 +26 -869
- package/build/actions.mjs +1 -1
- 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 +81 -26
- 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 +31 -8
- package/build/config.mjs +30 -1
- 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-update.mjs +4 -2
- package/build/dev-dependencies.mjs +1 -1
- package/build/dev-discovery.mjs +3 -3
- package/build/dev.mjs +15 -8
- package/build/dynamic.mjs +1 -1
- 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/index.mjs +59 -26
- package/build/instrumentation.mjs +30 -0
- package/build/metadata-routes.mjs +1 -1
- package/build/migration-audit.mjs +1 -1
- package/build/native-type-config.mjs +37 -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 +58 -9
- package/build/react-compiler-report.mjs +225 -0
- package/build/react-compiler.mjs +139 -0
- package/build/runtime-contract.mjs +2 -2
- 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 +6 -3
- package/build/worker-memory.mjs +9 -0
- package/cli.mjs +60 -4
- package/compat/activity-cache.cjs +26 -0
- package/compat/after.cjs +6 -4
- package/compat/app-layout-context.cjs +52 -2
- package/compat/base64.cjs +10 -0
- package/compat/cache-handlers.cjs +12 -2
- 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 +150 -0
- package/compat/headers.cjs +4 -2
- package/compat/incremental-cache.cjs +20 -7
- package/compat/index.d.ts +68 -3
- package/compat/link.cjs +12 -9
- 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 +171 -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 +82 -0
- package/docs/ai/troubleshooting.md +37 -2
- package/package.json +26 -7
- package/runtime/action-client.mjs +10 -4
- package/runtime/action-server.mjs +2 -0
- package/runtime/app-client.mjs +212 -65
- 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/cache-components-rsc.mjs +4 -1
- package/runtime/cache-request.mjs +2 -1
- package/runtime/deployment-client.mjs +19 -0
- package/runtime/dev-client.mjs +7 -2
- package/runtime/dev-compiler-broker.mjs +42 -0
- package/runtime/dev-indicator.mjs +8 -3
- package/runtime/dev-lazy-tool.mjs +16 -0
- package/runtime/development.mjs +29 -12
- package/runtime/document.mjs +1 -1
- package/runtime/edge.mjs +6 -5
- package/runtime/fetch-cache.mjs +10 -8
- package/runtime/flight-delta.mjs +50 -0
- package/runtime/flight-tap.mjs +28 -0
- 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/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/build/app.mjs
CHANGED
|
@@ -17,13 +17,17 @@ 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';
|
|
20
24
|
|
|
21
25
|
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' };
|
|
26
|
+
const extensions = { '.js': 'jsx', '.jsx': 'jsx', '.mjs': 'jsx', '.cjs': 'jsx', '.ts': 'ts', '.tsx': 'tsx', '.mts': 'ts', '.cts': 'ts' };
|
|
23
27
|
const assetLoaders = Object.fromEntries(['.png', '.jpg', '.jpeg', '.gif', '.webp', '.avif', '.svg', '.ico', '.woff', '.woff2', '.ttf', '.eot'].map(ext => [ext, 'file']));
|
|
24
28
|
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']);
|
|
29
|
+
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']);
|
|
30
|
+
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
31
|
|
|
28
32
|
export async function validateReactVersions(resolveFromProject) {
|
|
29
33
|
const names = ['react', 'react-dom', 'react-server-dom-webpack'];
|
|
@@ -39,7 +43,7 @@ export async function validateReactVersions(resolveFromProject) {
|
|
|
39
43
|
}
|
|
40
44
|
|
|
41
45
|
function sourceAst(source, filename) {
|
|
42
|
-
return parse(source, { sourceType: 'unambiguous', sourceFilename: filename, plugins: ['jsx', ...(/\.tsx?$/.test(filename) ? ['typescript'] : [])] });
|
|
46
|
+
return parse(source, { sourceType: 'unambiguous', sourceFilename: filename, plugins: ['jsx', ...(/\.[cm]?tsx?$/.test(filename) ? ['typescript'] : [])] });
|
|
43
47
|
}
|
|
44
48
|
function clientDirective(source, filename) {
|
|
45
49
|
if (!source.includes('use client')) return false;
|
|
@@ -140,7 +144,7 @@ function routeEntry(route, globalError, dev) {
|
|
|
140
144
|
if (globalError) imports.push(`export {default as GlobalError} from ${JSON.stringify(globalError)};`);
|
|
141
145
|
imports.push(`export {default as NavigationBoundary} from ${JSON.stringify(path.join(packageRoot, 'compat/app-navigation-boundary.cjs'))};`);
|
|
142
146
|
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'))};`);
|
|
147
|
+
imports.push(`export {LayoutProvider,LayoutSlot,ActivitySlot} from ${JSON.stringify(path.join(packageRoot, 'compat/app-layout-context.cjs'))};`);
|
|
144
148
|
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
149
|
if (route.routing) {
|
|
146
150
|
let index = 0;
|
|
@@ -173,12 +177,15 @@ function routeEntry(route, globalError, dev) {
|
|
|
173
177
|
}
|
|
174
178
|
|
|
175
179
|
/** 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 = {} }) {
|
|
180
|
+
export async function compileApp({ project, stage, manifest, cssModules, fonts, images, dev, reactCompiler = false, defineEnvironment = {}, productionBrowserSourceMaps = false, serverExternalPackages = [], moduleResolution = {} }) {
|
|
177
181
|
const refresh = dev ? createRefreshTransform(project.root, {generation:manifest.cacheId,forceGeneration:manifest.devForceRefresh,forceFiles:manifest.devAffectedModules}) : undefined;
|
|
178
182
|
const locations = dev ? createSourceLocationTransform(project.root) : undefined;
|
|
183
|
+
const compileReact = reactCompiler ? createReactCompilerTransform(project.root, reactCompiler, {dev}) : undefined;
|
|
179
184
|
const { basePath = '', assetPrefix = '', assetBase = '/_prnext/assets', trailingSlash = false, skipTrailingSlashRedirect = false } = manifest.config || {};
|
|
180
185
|
const publicPath = assetBase + '/';
|
|
181
186
|
const routes = project.routes.filter(route => route.router === 'app');
|
|
187
|
+
const htmlOnly = dev ? new Set() : selectHtmlOnlyRoutes(project.routes, manifest.config.htmlOnly, manifest.config);
|
|
188
|
+
const htmlGraph = !dev && htmlOnly.size ? createHtmlOnlyGraph(project.root) : undefined;
|
|
182
189
|
if (project.appNotFound) {
|
|
183
190
|
const file = path.join(stage, '.entries', 'app-not-found.jsx');
|
|
184
191
|
await writeFile(file, `import {notFound} from 'next/navigation';export default function MissingRoute(){notFound()}`);
|
|
@@ -194,6 +201,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
194
201
|
}
|
|
195
202
|
if (!routes.length) return;
|
|
196
203
|
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.');
|
|
204
|
+
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.');
|
|
197
205
|
const actions = createServerActions({ projectRoot: project.root });
|
|
198
206
|
function actionTransform(source, file, mode, runtime = 'nodejs') {
|
|
199
207
|
const result = actions.transform(source, file, mode);
|
|
@@ -204,6 +212,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
204
212
|
}
|
|
205
213
|
const boundaries = new Map();
|
|
206
214
|
const cssFiles = new Set();
|
|
215
|
+
const styleGraph = createAppStyleGraph(project.root);
|
|
207
216
|
const globalErrorCssFiles = new Set();
|
|
208
217
|
const globalErrorOnlyCss = new Set();
|
|
209
218
|
const canonicalPaths = new Map();
|
|
@@ -229,30 +238,33 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
229
238
|
throw new Error('app/global-error must be a Client Component. Add the "use client" directive.');
|
|
230
239
|
}
|
|
231
240
|
|
|
232
|
-
function graphPlugin(mode, { styleTarget = cssFiles, omitGlobalStyles = true } = {}) {
|
|
241
|
+
function graphPlugin(mode, { styleTarget = cssFiles, omitGlobalStyles = true, omitAllStyles = false } = {}) {
|
|
233
242
|
const browser = mode === 'browser';
|
|
234
243
|
const rsc = mode === 'rsc';
|
|
235
244
|
return {
|
|
236
245
|
name: `prnext-app-${mode}`,
|
|
237
246
|
setup(esbuild) {
|
|
238
|
-
applicationEntry(esbuild,readCompilerFile);
|
|
247
|
+
applicationEntry(esbuild,readCompilerFile,{transformTypedHelpers:!!compileReact && (browser || mode==='ssr')});
|
|
239
248
|
esbuild.onResolve({ filter: /^prnext-internal:use-cache$/ }, () => browser
|
|
240
249
|
? { errors: [{ text: "'use cache' cannot enter a Client Component graph" }] }
|
|
241
250
|
: { path: '../compat/use-cache.cjs', external: true });
|
|
242
251
|
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;
|
|
252
|
+
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
253
|
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' };
|
|
254
|
+
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
255
|
});
|
|
247
256
|
esbuild.onResolve({ filter: /^prnext-internal:action-(?:crypto|client|ssr)$/ }, args => {
|
|
248
257
|
const helper = args.path.slice('prnext-internal:'.length);
|
|
249
258
|
if (browser && helper !== 'action-client') return { errors: [{ text: 'Server Action implementation cannot enter the browser graph.' }] };
|
|
250
259
|
return browser ? { path: path.join(packageRoot, 'runtime', helper + '.mjs') } : { path: '../runtime/' + helper + '.mjs', external: true };
|
|
251
260
|
});
|
|
261
|
+
esbuild.onResolve({ filter: /^prnext-internal:metadata-route$/ }, () => browser
|
|
262
|
+
? { errors: [{ text: 'Metadata route handlers are server-only.' }] }
|
|
263
|
+
: { path: '../runtime/metadata-route.mjs', external: true });
|
|
252
264
|
esbuild.onResolve({ filter: frameworkRuntimeImportPattern }, args => {
|
|
253
265
|
const name = frameworkImportName(args.path);
|
|
254
266
|
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.` }] };
|
|
267
|
+
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
268
|
const target = path.join(packageRoot, 'compat', (name === 'compat/router' ? 'compat-router' : name === 'dynamic' ? 'app-dynamic' : rsc && name === 'navigation' ? 'navigation-server' : name) + '.cjs');
|
|
257
269
|
if (!browser && (!rsc || !forcedClient.has(path.basename(target)))) return { path: '../compat/' + path.basename(target), external: true };
|
|
258
270
|
return { path: target };
|
|
@@ -296,10 +308,14 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
296
308
|
if (!/\.module\.(?:css|scss|sass)$/i.test(args.path)) return { path: file, namespace: 'prnext-app-empty' };
|
|
297
309
|
});
|
|
298
310
|
}
|
|
299
|
-
esbuild.onLoad({ filter: /\.(?:[cm]?
|
|
311
|
+
esbuild.onLoad({ filter: /\.(?:[cm]?[jt]s|[jt]sx)$/, namespace: 'file' }, async args => {
|
|
300
312
|
if (browser && args.path === project.document) return { errors: [{ text: 'pages/_document is server-only and cannot be imported by a Client Component.' }] };
|
|
301
313
|
const original=await readCompilerFile(args.path,args.prnextSource);
|
|
302
|
-
|
|
314
|
+
htmlGraph?.source(args.path, original);
|
|
315
|
+
// Only Client Component graphs use React's memo-cache runtime. Server
|
|
316
|
+
// Components, route handlers and Server Action graphs remain untouched.
|
|
317
|
+
const compiled=compileReact && (browser || mode==='ssr') ? await compileReact(original,args.path,{loaderOutput:args.prnextSource!==undefined}) : original;
|
|
318
|
+
const source = await fonts.transform(await expandActionExports(locations?await locations(compiled,args.path):compiled, args.path, async (specifier, importer) => {
|
|
303
319
|
const resolved = await esbuild.resolve(specifier, { resolveDir: path.dirname(importer), kind: 'import-statement', pluginData: { prnextExportScan: true } });
|
|
304
320
|
return resolved.errors.length || resolved.external || resolved.namespace !== 'file' ? undefined : resolved.path;
|
|
305
321
|
}), args.path);
|
|
@@ -308,6 +324,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
308
324
|
const actionSource = actionTransform(cachedSource, args.path, mode);
|
|
309
325
|
const builtin = path.dirname(args.path) === path.join(packageRoot, 'compat') && forcedClient.has(path.basename(args.path));
|
|
310
326
|
if (rsc && (builtin || clientDirective(source, args.path))) {
|
|
327
|
+
htmlGraph?.issue(args.path, 'a Client Component or interactive framework component');
|
|
311
328
|
let boundary = boundaries.get(args.path);
|
|
312
329
|
if (!boundary) {
|
|
313
330
|
const exports = await exportedNames(args.path, esbuild);
|
|
@@ -334,7 +351,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
334
351
|
const rsc = mode === 'rsc';
|
|
335
352
|
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
353
|
return { name: 'prnext-edge-app', setup(esbuild) {
|
|
337
|
-
applicationEntry(esbuild,readCompilerFile);
|
|
354
|
+
applicationEntry(esbuild,readCompilerFile,{transformTypedHelpers:!!compileReact && mode==='ssr'});
|
|
338
355
|
esbuild.onResolve({filter:/^prnext-internal:action-(?:crypto|ssr)$/}, args => ({path:args.path.slice('prnext-internal:'.length),namespace:'prnext-edge-action'}));
|
|
339
356
|
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
357
|
esbuild.onResolve({ filter: frameworkSubpathPattern }, args => {
|
|
@@ -362,7 +379,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
362
379
|
const names = await exportedNames(args.path, esbuild), name = path.basename(args.path, '.cjs');
|
|
363
380
|
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
381
|
});
|
|
365
|
-
if (rsc) esbuild.onLoad({ filter: /\.(?:[cm]?
|
|
382
|
+
if (rsc) esbuild.onLoad({ filter: /\.(?:[cm]?[jt]s|[jt]sx)$/, namespace: 'file' }, async args => {
|
|
366
383
|
const source = args.prnextSource ?? await readFile(args.path, 'utf8');
|
|
367
384
|
const builtin = path.dirname(args.path) === path.join(packageRoot, 'compat') && forcedClient.has(path.basename(args.path));
|
|
368
385
|
if (!builtin && !clientDirective(source, args.path)) return;
|
|
@@ -377,13 +394,18 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
377
394
|
} };
|
|
378
395
|
}
|
|
379
396
|
async function edgeAppBuild(file, outfile, mode, defaultExport = false) {
|
|
380
|
-
const result = await compileEdge({ file, outfile, projectRoot: project.root, dev, defineEnvironment, reactMode: mode, defaultExport,
|
|
397
|
+
const result = await compileEdge({ file, outfile, projectRoot: project.root, dev, defineEnvironment, reactMode: mode, defaultExport, transformTypedHelpers:!!compileReact && mode==='ssr',
|
|
381
398
|
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
|
-
|
|
399
|
+
transformSource: async (source, file, builder, {loaderOutput=false}={}) => {
|
|
400
|
+
const compiled=compileReact && mode==='ssr' ? await compileReact(source,file,{loaderOutput}) : source;
|
|
401
|
+
return actionTransform(await fonts.transform(await expandActionExports(locations?await locations(compiled,file):compiled, file, async (specifier, importer) => {
|
|
402
|
+
const resolved = await builder.resolve(specifier, {resolveDir:path.dirname(importer),kind:'import-statement',pluginData:{prnextExportScan:true}});
|
|
403
|
+
return resolved.errors.length || resolved.external || resolved.namespace !== 'file' ? undefined : resolved.path;
|
|
404
|
+
}), file), file, mode, 'edge');
|
|
405
|
+
} });
|
|
386
406
|
fonts.record(result.metafile);
|
|
407
|
+
styleGraph.record(result.metafile);
|
|
408
|
+
htmlGraph?.record(result.metafile);
|
|
387
409
|
}
|
|
388
410
|
async function serverBuild(entryPoints, outdir, mode, styleTarget = cssFiles) {
|
|
389
411
|
if (!Object.keys(entryPoints).length) return;
|
|
@@ -411,6 +433,8 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
411
433
|
}
|
|
412
434
|
}
|
|
413
435
|
fonts.record(output.metafile);
|
|
436
|
+
styleGraph.record(output.metafile);
|
|
437
|
+
htmlGraph?.record(output.metafile);
|
|
414
438
|
for (const file of output.outputFiles) {
|
|
415
439
|
await mkdir(path.dirname(file.path), { recursive: true });
|
|
416
440
|
await writeFile(file.path, file.contents);
|
|
@@ -425,7 +449,6 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
425
449
|
}
|
|
426
450
|
|
|
427
451
|
async function collectBrowserStyles(browserEntries) {
|
|
428
|
-
if (!globalErrorId) return;
|
|
429
452
|
const revision = `${boundaries.size}:${actions.actions.size}:${nativePackages.version}`;
|
|
430
453
|
if (browserStyleRevision === revision) return;
|
|
431
454
|
// SSR intentionally erases ssr:false loaders. Discover their dependencies
|
|
@@ -435,6 +458,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
435
458
|
define: { 'process.env': '{}', ...common.define },
|
|
436
459
|
plugins: [moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), nativePackages.plugin('browser'), cssModules.plugin(true)] });
|
|
437
460
|
fonts.record(discovery.metafile);
|
|
461
|
+
styleGraph.record(discovery.metafile);
|
|
438
462
|
const entryIds = new Map(await Promise.all(Object.entries(browserEntries).map(async ([id, file]) => [await canonicalFile(file), id])));
|
|
439
463
|
const globalRoots = [], normalRoots = [];
|
|
440
464
|
for (const output of Object.values(discovery.metafile.outputs)) {
|
|
@@ -477,6 +501,7 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
477
501
|
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
502
|
collectInstant(route.routing);
|
|
479
503
|
manifest.routes.push({ id: route.id, pattern: route.pattern, kind: route.kind, router: 'app', module: `server/${route.id}.mjs`, css: [], ...(route.internal ? { internal: true } : {}),
|
|
504
|
+
...(!dev && htmlOnly.has(route.id) ? { htmlOnly:true } : {}),
|
|
480
505
|
...(route.parallel ? { parallel: true, interception: !!route.interception } : {}),
|
|
481
506
|
...(route.cacheConfig ? { cacheConfig: route.cacheConfig } : {}),
|
|
482
507
|
...(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)) }
|
|
@@ -536,25 +561,50 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
536
561
|
}
|
|
537
562
|
if (globalErrorId) manifest.app.globalError = { id: globalErrorId, name: 'default', css: globalErrorCss };
|
|
538
563
|
const runtimeEntry = path.join(stage, '.entries', 'app-runtime.mjs');
|
|
564
|
+
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
565
|
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` : ''}
|
|
566
|
+
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`);
|
|
541
567
|
const clientEntries = { 'app-runtime': runtimeEntry, ...browserEntries };
|
|
542
568
|
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:
|
|
569
|
+
define: { 'process.env': '{}', ...common.define, __PRNEXT_CACHE_COMPONENTS__: JSON.stringify(manifest.config.cacheComponents === true) }, plugins: [moduleResolutionPlugin(moduleResolution, project.root), ...(dev ? [devSingletonsPlugin()] : []), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitAllStyles: true }), nativePackages.plugin('browser'), cssModules.plugin(true, { emitStyles: () => false })] });
|
|
544
570
|
fonts.record(browser.metafile);
|
|
545
571
|
const entryIds = new Map(Object.entries(clientEntries).map(([id, file]) => [file, id]));
|
|
546
572
|
const edgeBoundaries = new Set([...boundaries.values()].filter(boundary => boundary.edge).map(boundary => boundary.id));
|
|
547
|
-
const styles = new Set();
|
|
548
573
|
let client;
|
|
549
574
|
for (const [output, metadata] of Object.entries(browser.metafile.outputs)) {
|
|
550
|
-
if (metadata.cssBundle) styles.add(publicPath + path.basename(metadata.cssBundle));
|
|
551
575
|
if (!metadata.entryPoint || !output.endsWith('.js')) continue;
|
|
552
576
|
const id = entryIds.get(path.resolve(project.root, metadata.entryPoint));
|
|
553
577
|
if (id === 'app-runtime') { client = publicPath + path.basename(output); continue; }
|
|
554
578
|
if (!id) continue;
|
|
555
579
|
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
580
|
}
|
|
557
|
-
|
|
581
|
+
const styleBundles = new Map();
|
|
582
|
+
for (const route of routes.filter(route => route.kind === 'page')) {
|
|
583
|
+
// All esbuild graphs canonicalize their inputs, including symlinked
|
|
584
|
+
// convention files. Match those identities before querying reachability.
|
|
585
|
+
for (const files of appStyleScopes(route)) for (const file of files) styleGraph.alias(file, await canonicalFile(file));
|
|
586
|
+
const plan = planAppStyleEntries(route, styleGraph);
|
|
587
|
+
const styles = [];
|
|
588
|
+
const cssScopes = [];
|
|
589
|
+
for (const { styles:files, path:scopePath, kind } of plan) {
|
|
590
|
+
const signature = JSON.stringify(files);
|
|
591
|
+
if (!styleBundles.has(signature)) {
|
|
592
|
+
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);
|
|
593
|
+
const styleEntry = path.join(stage, '.entries', id + '.mjs');
|
|
594
|
+
await writeFile(styleEntry, files.map(file => `import ${JSON.stringify(file)};`).join('\n'));
|
|
595
|
+
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,
|
|
596
|
+
plugins: [moduleResolutionPlugin(moduleResolution, project.root), images.plugin(), fonts.plugin(), graphPlugin('browser', { omitGlobalStyles: false }), cssModules.plugin(true)] });
|
|
597
|
+
styleBundles.set(signature, Object.values(output.metafile.outputs).filter(output => output.cssBundle).map(output => publicPath + path.basename(output.cssBundle)));
|
|
598
|
+
}
|
|
599
|
+
const css = styleBundles.get(signature);
|
|
600
|
+
styles.push(...css);
|
|
601
|
+
if (route.parallel) cssScopes.push({ path:scopePath, kind, css });
|
|
602
|
+
}
|
|
603
|
+
const emitted = manifest.routes.find(item => item.id === route.id);
|
|
604
|
+
if (!emitted.htmlOnly) emitted.client = client;
|
|
605
|
+
emitted.css = [...new Set(styles)];
|
|
606
|
+
if (route.parallel) emitted.cssScopes = cssScopes;
|
|
607
|
+
}
|
|
558
608
|
}
|
|
559
609
|
manifest.app.actions = Object.fromEntries([...actions.actions.values()].map(action => [action.id, {
|
|
560
610
|
module: `server/action-${action.runtimes?.has('nodejs')?'':'edge-'}${action.id}.mjs`, export: 'invoke',
|
|
@@ -564,4 +614,9 @@ export async function compileApp({ project, stage, manifest, cssModules, fonts,
|
|
|
564
614
|
serverNativeVersion === nativePackages.version && emittedBrowserInputs === browserRevision()) break;
|
|
565
615
|
}
|
|
566
616
|
|
|
617
|
+
if (htmlGraph) for (const route of routes) if (htmlOnly.has(route.id)) {
|
|
618
|
+
for (const file of [...appStyleScopes(route).flat(), ...(route.metadataFiles || []).map(item=>item.file)].filter(Boolean)) htmlGraph.alias(file, await canonicalFile(file));
|
|
619
|
+
htmlGraph.validate(route);
|
|
620
|
+
}
|
|
621
|
+
|
|
567
622
|
}
|
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
|
+
}
|