@wular/pnext 0.0.1
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 +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// Inline CSS (COMPAT - `experimental.inlineCss`, prod-only).
|
|
2
|
+
//
|
|
3
|
+
// When the flag is on, Next inlines a route's built CSS into a `<style>` in the document instead of
|
|
4
|
+
// shipping a `<link rel=stylesheet>`. This module owns the FLAG read plus the inliner (read the built
|
|
5
|
+
// asset bytes, wrap in a `<style data-pnext-inline-css>` tag). The DECISION of link-vs-style at head
|
|
6
|
+
// assembly is renderer territory: the renderer calls inlineCssEnabled(config) and, when true and not
|
|
7
|
+
// dev, replaces each route/global stylesheet link with inlineCssTag(...).
|
|
8
|
+
|
|
9
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
10
|
+
import { readFileSync } from 'node:fs';
|
|
11
|
+
import type { ResolvedConfig } from '../../config';
|
|
12
|
+
import { getNextConfig } from '../next/config-loader';
|
|
13
|
+
|
|
14
|
+
// Inlining is a DOCUMENT optimization: Next inlines a route's CSS into the HTML
|
|
15
|
+
// it serves for a navigation to the URL, but a flight (`RSC: 1`) response for
|
|
16
|
+
// the same route carries stylesheet references, never the CSS bytes (the
|
|
17
|
+
// inline-css suite asserts the RSC payload of `/a` does NOT contain the page's
|
|
18
|
+
// `font-size` rule while the HTML does). The full-route RSC responder wraps its
|
|
19
|
+
// render in `withoutInlineCss`, which switches head assembly back to `<link>`.
|
|
20
|
+
//
|
|
21
|
+
// A scope (not a module flag) because renders are concurrent: a plain document
|
|
22
|
+
// render running alongside a flight render must keep inlining.
|
|
23
|
+
const INLINE_CSS_SUPPRESSED = Symbol.for('pnext.compat.inlineCssSuppressed');
|
|
24
|
+
const suppressed = ((globalThis as Record<PropertyKey, unknown>)[INLINE_CSS_SUPPRESSED] ??=
|
|
25
|
+
new AsyncLocalStorage<true>()) as AsyncLocalStorage<true>;
|
|
26
|
+
|
|
27
|
+
/** Run `render` with CSS inlining disabled (flight responses — see above). */
|
|
28
|
+
export function withoutInlineCss<T>(render: () => T): T {
|
|
29
|
+
return suppressed.run(true, render);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** True when `experimental.inlineCss` is set in next.config. */
|
|
33
|
+
export function inlineCssEnabled(_config: ResolvedConfig): boolean {
|
|
34
|
+
const config = getNextConfig();
|
|
35
|
+
const experimental = config.experimental as { inlineCss?: unknown } | undefined;
|
|
36
|
+
return experimental?.inlineCss === true;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Wrap a built CSS file's bytes in an inline `<style>` tag. `nonce` (when the
|
|
41
|
+
* request carries a CSP nonce) is stamped so a strict CSP admits the style.
|
|
42
|
+
* Returns '' if the file can't be read (never fail the page over inlining).
|
|
43
|
+
*/
|
|
44
|
+
export function inlineCssTag(assetFile: string, nonce?: string): string {
|
|
45
|
+
const css = readCss(assetFile);
|
|
46
|
+
return css === undefined ? '' : inlineStyleTag(css, nonce);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function readCss(assetFile: string): string | undefined {
|
|
50
|
+
try {
|
|
51
|
+
return readFileSync(assetFile, 'utf8');
|
|
52
|
+
} catch {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function inlineStyleTag(css: string, nonce?: string): string {
|
|
58
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : '';
|
|
59
|
+
return `<style data-pnext-inline-css${nonceAttr}>${css}</style>`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function inlineCssStylesheets(
|
|
63
|
+
config: ResolvedConfig,
|
|
64
|
+
options: { assetNames: string[]; dev: boolean; nonce?: string; prependCss?: string },
|
|
65
|
+
): string[] | undefined {
|
|
66
|
+
if (options.dev || suppressed.getStore() === true || !inlineCssEnabled(config)) return undefined;
|
|
67
|
+
const assets = options.assetNames.map(asset =>
|
|
68
|
+
readCss(`${config.outPath}/public/assets/${asset}`),
|
|
69
|
+
);
|
|
70
|
+
if (assets.some(css => css === undefined)) return undefined;
|
|
71
|
+
const css = [options.prependCss, ...assets].filter(Boolean).join('\n');
|
|
72
|
+
return css ? [inlineStyleTag(css, options.nonce)] : [];
|
|
73
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// experimental.useLightningcss / lightningCssFeatures (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// When next.config enables `experimental.useLightningcss`, CSS chunk sources run through the APP's own
|
|
4
|
+
// `lightningcss` package (resolved from the project root - pnext ships no copy) with the
|
|
5
|
+
// include/exclude feature masks from `experimental.lightningCssFeatures`. Matching Next's
|
|
6
|
+
// lightningcss-loader: Nesting is always in the include mask; user `include` names add flags and user
|
|
7
|
+
// `exclude` names remove them. An included feature is transpiled regardless of browser targets - that
|
|
8
|
+
// forced transpilation is exactly what the suite asserts.
|
|
9
|
+
//
|
|
10
|
+
// Zero-cost when disabled or when the app has no lightningcss install: the source passes through.
|
|
11
|
+
|
|
12
|
+
import { createRequire } from 'node:module';
|
|
13
|
+
import path from 'node:path';
|
|
14
|
+
import { getNextConfig } from '../next/config-loader';
|
|
15
|
+
|
|
16
|
+
interface LightningModule {
|
|
17
|
+
transform(options: {
|
|
18
|
+
filename: string;
|
|
19
|
+
code: Uint8Array;
|
|
20
|
+
include?: number;
|
|
21
|
+
exclude?: number;
|
|
22
|
+
errorRecovery?: boolean;
|
|
23
|
+
}): { code: Uint8Array };
|
|
24
|
+
Features: Record<string, number>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface LightningFeaturesConfig {
|
|
28
|
+
include?: string[];
|
|
29
|
+
exclude?: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const moduleCache = new Map<string, LightningModule | null>();
|
|
33
|
+
|
|
34
|
+
function loadLightningcss(root: string): LightningModule | null {
|
|
35
|
+
const cached = moduleCache.get(root);
|
|
36
|
+
if (cached !== undefined) return cached;
|
|
37
|
+
let loaded: LightningModule | null = null;
|
|
38
|
+
try {
|
|
39
|
+
const require = createRequire(path.join(root, 'package.json'));
|
|
40
|
+
loaded = require('lightningcss') as LightningModule;
|
|
41
|
+
} catch {
|
|
42
|
+
loaded = null;
|
|
43
|
+
}
|
|
44
|
+
moduleCache.set(root, loaded);
|
|
45
|
+
return loaded;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `'light-dark'` -> `LightDark` (lightningcss `Features` enum key). */
|
|
49
|
+
function featureKey(name: string): string {
|
|
50
|
+
return name.replace(/(?:^|-)([a-z0-9])/g, (_match, char: string) => char.toUpperCase());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function featureMask(features: Record<string, number>, names: string[] | undefined): number {
|
|
54
|
+
let mask = 0;
|
|
55
|
+
for (const name of names ?? []) mask |= features[featureKey(name)] ?? 0;
|
|
56
|
+
return mask;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Transform CSS chunk source through the app's lightningcss when
|
|
61
|
+
* `experimental.useLightningcss` is enabled; returns the source unchanged
|
|
62
|
+
* otherwise (or when lightningcss is missing / the transform fails).
|
|
63
|
+
*/
|
|
64
|
+
export function maybeLightningcssTransform(css: string, file: string, root: string): string {
|
|
65
|
+
const experimental = getNextConfig().experimental as
|
|
66
|
+
| { useLightningcss?: boolean; lightningCssFeatures?: LightningFeaturesConfig }
|
|
67
|
+
| undefined;
|
|
68
|
+
if (experimental?.useLightningcss !== true) return css;
|
|
69
|
+
const lightning = loadLightningcss(root);
|
|
70
|
+
if (!lightning) return css;
|
|
71
|
+
|
|
72
|
+
const features = experimental.lightningCssFeatures;
|
|
73
|
+
const userInclude = featureMask(lightning.Features, features?.include);
|
|
74
|
+
const userExclude = featureMask(lightning.Features, features?.exclude);
|
|
75
|
+
// Nesting always transpiles (Next hardcodes it), user excludes win.
|
|
76
|
+
const include = ((lightning.Features.Nesting ?? 1) | userInclude) & ~userExclude;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const result = lightning.transform({
|
|
80
|
+
filename: file,
|
|
81
|
+
code: new TextEncoder().encode(css),
|
|
82
|
+
include,
|
|
83
|
+
exclude: userExclude,
|
|
84
|
+
errorRecovery: true,
|
|
85
|
+
});
|
|
86
|
+
return new TextDecoder().decode(result.code);
|
|
87
|
+
} catch {
|
|
88
|
+
return css;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import { existsSync, lstatSync, readdirSync, readFileSync, symlinkSync } from 'node:fs';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import type { Plugin } from 'esbuild';
|
|
5
|
+
import type { ResolvedConfig } from '../../config';
|
|
6
|
+
import { resolveImport } from '../../resolve/imports';
|
|
7
|
+
import { maybeLightningcssTransform } from './lightningcss';
|
|
8
|
+
|
|
9
|
+
interface ComposeRef {
|
|
10
|
+
names: string[];
|
|
11
|
+
from?: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface CssModuleInfo {
|
|
15
|
+
classes: Set<string>;
|
|
16
|
+
composes: Map<string, ComposeRef[]>;
|
|
17
|
+
dependencies: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const pagesCompatIgnoredSourceEntries = new Set([
|
|
21
|
+
'.next',
|
|
22
|
+
'.pnext',
|
|
23
|
+
'app',
|
|
24
|
+
'node_modules',
|
|
25
|
+
'pages',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
export function materializeNextCssSources(config: ResolvedConfig) {
|
|
29
|
+
const generatedRoot = path.dirname(config.appPath);
|
|
30
|
+
if (path.basename(path.dirname(generatedRoot)) !== 'pnext-pages-compat') return;
|
|
31
|
+
|
|
32
|
+
for (const entry of readdirSync(config.root)) {
|
|
33
|
+
if (pagesCompatIgnoredSourceEntries.has(entry)) continue;
|
|
34
|
+
const source = path.join(config.root, entry);
|
|
35
|
+
const target = path.join(generatedRoot, entry);
|
|
36
|
+
if (existsSync(target)) continue;
|
|
37
|
+
try {
|
|
38
|
+
symlinkSync(source, target, lstatSync(source).isDirectory() ? 'dir' : 'file');
|
|
39
|
+
} catch {
|
|
40
|
+
// A concurrent materialization may have linked the same source already.
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function nextCssModuleChunkPlugin(config: ResolvedConfig): Plugin {
|
|
46
|
+
return {
|
|
47
|
+
name: 'pnext-compat-css-modules',
|
|
48
|
+
setup(build) {
|
|
49
|
+
build.onLoad({ filter: /\.css$/ }, ({ path: file }) => {
|
|
50
|
+
const source = nextCssSource(file, config.root);
|
|
51
|
+
const contents = file.endsWith('.module.css')
|
|
52
|
+
? transformCssModule(readFileSync(source, 'utf8'), source, config.root)
|
|
53
|
+
: rewriteTransparentBodyChildren(readFileSync(source, 'utf8'));
|
|
54
|
+
return {
|
|
55
|
+
// No-op unless next.config enables experimental.useLightningcss.
|
|
56
|
+
contents: maybeLightningcssTransform(contents, source, config.root),
|
|
57
|
+
loader: 'css',
|
|
58
|
+
resolveDir: path.dirname(source),
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function nextCssResolveModule(config: ResolvedConfig) {
|
|
66
|
+
return (file: string): Record<string, string> | undefined =>
|
|
67
|
+
isCssModuleFile(file) ? cssModuleMapping(file, config.root) : undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function nextCssLoadModuleForClient(config: ResolvedConfig) {
|
|
71
|
+
return (file: string): string | undefined =>
|
|
72
|
+
isCssModuleFile(file)
|
|
73
|
+
? `export default ${JSON.stringify(cssModuleMapping(file, config.root))};`
|
|
74
|
+
: undefined;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function nextCssResolveDependency(config: ResolvedConfig) {
|
|
78
|
+
const requires = new Map<string, NodeJS.Require>();
|
|
79
|
+
return (_root: string, fromFile: string, specifier: string): string | undefined => {
|
|
80
|
+
if (specifier.startsWith('.') || path.isAbsolute(specifier)) return undefined;
|
|
81
|
+
const source = nextCssSource(fromFile, config.root);
|
|
82
|
+
let require = requires.get(source);
|
|
83
|
+
if (!require) {
|
|
84
|
+
require = createRequire(source);
|
|
85
|
+
requires.set(source, require);
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
const resolved = require.resolve(specifier);
|
|
89
|
+
return isCssDependency(resolved) ? resolved : undefined;
|
|
90
|
+
} catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isCssModuleFile(file: string) {
|
|
97
|
+
return file.endsWith('.module.css');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function isCssDependency(file: string) {
|
|
101
|
+
return /\.(?:css|scss|sass)$/.test(file);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function cssModuleMapping(file: string, root: string) {
|
|
105
|
+
file = nextCssSource(file, root);
|
|
106
|
+
const cache = new Map<string, CssModuleInfo>();
|
|
107
|
+
const info = moduleInfo(file, root, cache);
|
|
108
|
+
return Object.fromEntries(
|
|
109
|
+
[...info.classes].map(className => [
|
|
110
|
+
className,
|
|
111
|
+
classValue(file, className, root, cache, new Set()),
|
|
112
|
+
]),
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function nextCssSource(file: string, root: string) {
|
|
117
|
+
const resolved = path.resolve(file);
|
|
118
|
+
const parts = resolved.split(path.sep);
|
|
119
|
+
const buildClientIndex = parts.findIndex(
|
|
120
|
+
(part, index) =>
|
|
121
|
+
part === '.pnext' && parts.slice(index + 1, index + 4).join('/') === 'cache/server/build-client',
|
|
122
|
+
);
|
|
123
|
+
if (buildClientIndex !== -1) {
|
|
124
|
+
const source = path.join(root, ...parts.slice(buildClientIndex + 4));
|
|
125
|
+
if (existsSync(source)) return source;
|
|
126
|
+
}
|
|
127
|
+
const generatedIndex = parts.lastIndexOf('pnext-pages-compat');
|
|
128
|
+
const relative = generatedIndex === -1 ? undefined : parts.slice(generatedIndex + 2);
|
|
129
|
+
if (relative?.length) {
|
|
130
|
+
const source = path.join(
|
|
131
|
+
root,
|
|
132
|
+
...(relative[0] === 'source-app'
|
|
133
|
+
? ['app', ...relative.slice(1)]
|
|
134
|
+
: relative[0] === 'source-pages'
|
|
135
|
+
? ['pages', ...relative.slice(1)]
|
|
136
|
+
: relative),
|
|
137
|
+
);
|
|
138
|
+
if (existsSync(source)) return source;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return resolved;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function rewriteTransparentBodyChildren(source: string) {
|
|
145
|
+
return source.replace(
|
|
146
|
+
/body\s*>\s*(-?[_a-zA-Z][\w-]*)/g,
|
|
147
|
+
(_, element: string) =>
|
|
148
|
+
`:is(body>${element},body :where(pnext-layout,#pnext-page)>${element})`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function transformCssModule(source: string, file: string, root: string) {
|
|
153
|
+
const info = moduleInfoFromSource(source, file, root);
|
|
154
|
+
const imports = info.dependencies.map(dep => `@import ${JSON.stringify(dep)};`).join('\n');
|
|
155
|
+
const css = scopeKeyframes(rewriteModuleSelectors(stripComposes(source), file), file);
|
|
156
|
+
return [imports, css].filter(Boolean).join('\n');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function moduleInfo(
|
|
160
|
+
file: string,
|
|
161
|
+
root: string,
|
|
162
|
+
cache: Map<string, CssModuleInfo>,
|
|
163
|
+
): CssModuleInfo {
|
|
164
|
+
const key = path.resolve(file);
|
|
165
|
+
const cached = cache.get(key);
|
|
166
|
+
if (cached) return cached;
|
|
167
|
+
const info = moduleInfoFromSource(readFileSync(key, 'utf8'), key, root);
|
|
168
|
+
cache.set(key, info);
|
|
169
|
+
return info;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function moduleInfoFromSource(source: string, file: string, root: string): CssModuleInfo {
|
|
173
|
+
const classes = cssClassNames(source);
|
|
174
|
+
const composes = new Map<string, ComposeRef[]>();
|
|
175
|
+
const dependencies: string[] = [];
|
|
176
|
+
const seenDependencies = new Set<string>();
|
|
177
|
+
const rulePattern = /(^|})([^{}]+)\{([^{}]*)\}/g;
|
|
178
|
+
let match: RegExpExecArray | null;
|
|
179
|
+
|
|
180
|
+
while ((match = rulePattern.exec(source))) {
|
|
181
|
+
const [, , selector, body] = match;
|
|
182
|
+
if (!selector || !body) continue;
|
|
183
|
+
const localClasses = selectorClassNames(selector).filter(className => classes.has(className));
|
|
184
|
+
if (localClasses.length === 0) continue;
|
|
185
|
+
|
|
186
|
+
for (const ref of composeRefs(body)) {
|
|
187
|
+
for (const localClass of localClasses) {
|
|
188
|
+
const refs = composes.get(localClass) ?? [];
|
|
189
|
+
refs.push(ref);
|
|
190
|
+
composes.set(localClass, refs);
|
|
191
|
+
}
|
|
192
|
+
if (!ref.from || ref.from === 'global') continue;
|
|
193
|
+
const dependency = resolveCssModuleImport(root, file, ref.from);
|
|
194
|
+
if (!dependency || seenDependencies.has(dependency)) continue;
|
|
195
|
+
dependencies.push(dependency);
|
|
196
|
+
seenDependencies.add(dependency);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return { classes, composes, dependencies };
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function classValue(
|
|
204
|
+
file: string,
|
|
205
|
+
className: string,
|
|
206
|
+
root: string,
|
|
207
|
+
cache: Map<string, CssModuleInfo>,
|
|
208
|
+
stack: Set<string>,
|
|
209
|
+
): string {
|
|
210
|
+
const key = `${path.resolve(file)}\0${className}`;
|
|
211
|
+
if (stack.has(key)) return cssModuleClassName(file, className);
|
|
212
|
+
stack.add(key);
|
|
213
|
+
|
|
214
|
+
const info = moduleInfo(file, root, cache);
|
|
215
|
+
const parts = [cssModuleClassName(file, className)];
|
|
216
|
+
for (const ref of info.composes.get(className) ?? []) {
|
|
217
|
+
if (ref.from === 'global') {
|
|
218
|
+
parts.push(...ref.names);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const refFile = ref.from ? resolveCssModuleImport(root, file, ref.from) : file;
|
|
222
|
+
if (!refFile) continue;
|
|
223
|
+
const refInfo = moduleInfo(refFile, root, cache);
|
|
224
|
+
for (const name of ref.names) {
|
|
225
|
+
if (!refInfo.classes.has(name)) continue;
|
|
226
|
+
parts.push(...classValue(refFile, name, root, cache, stack).split(/\s+/));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
stack.delete(key);
|
|
231
|
+
return [...new Set(parts)].join(' ');
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function composeRefs(body: string): ComposeRef[] {
|
|
235
|
+
const refs: ComposeRef[] = [];
|
|
236
|
+
const pattern = /\bcomposes\s*:\s*([^;]+);/g;
|
|
237
|
+
let match: RegExpExecArray | null;
|
|
238
|
+
|
|
239
|
+
while ((match = pattern.exec(body))) {
|
|
240
|
+
const value = match[1]?.trim();
|
|
241
|
+
if (!value) continue;
|
|
242
|
+
const fromMatch = /^(.+?)\s+from\s+(['"]?)([^'"]+)\2$/.exec(value);
|
|
243
|
+
const names = (fromMatch?.[1] ?? value).split(/\s+/).filter(Boolean);
|
|
244
|
+
if (names.length === 0) continue;
|
|
245
|
+
refs.push({ names, ...(fromMatch?.[3] ? { from: fromMatch[3] } : {}) });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
return refs;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function resolveCssModuleImport(root: string, fromFile: string, specifier: string) {
|
|
252
|
+
const resolved = resolveImport(root, fromFile, specifier);
|
|
253
|
+
if (resolved && isCssModuleFile(resolved)) return path.resolve(resolved);
|
|
254
|
+
if (specifier.startsWith('.') || path.isAbsolute(specifier)) return undefined;
|
|
255
|
+
|
|
256
|
+
const sibling = path.resolve(path.dirname(fromFile), specifier);
|
|
257
|
+
return existsSync(sibling) && isCssModuleFile(sibling) ? sibling : undefined;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function stripComposes(source: string) {
|
|
261
|
+
return source.replace(/\s*composes\s*:[^;{}]+;/g, '');
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function cssClassNames(source: string) {
|
|
265
|
+
return new Set(selectorClassNames(source.replace(/\{[^{}]*\}/g, '{')));
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function selectorClassNames(selector: string) {
|
|
269
|
+
const classes: string[] = [];
|
|
270
|
+
const pattern = /(^|[\s,>+~])\.(-?[_a-zA-Z][\w-]*)/g;
|
|
271
|
+
let match: RegExpExecArray | null;
|
|
272
|
+
while ((match = pattern.exec(selector))) {
|
|
273
|
+
if (match[2]) classes.push(match[2]);
|
|
274
|
+
}
|
|
275
|
+
return classes;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function rewriteModuleSelectors(css: string, file: string) {
|
|
279
|
+
// A selector is any brace/semicolon-free run ending at a brace - which also covers NESTED rule
|
|
280
|
+
// openings (postcss-nested / native CSS nesting and rules inside `@media`), whose selectors follow a
|
|
281
|
+
// brace or a declaration's semicolon. A prior version anchored on (^|}) and left nested class
|
|
282
|
+
// selectors unhashed while the JS mapping handed out the hashed name.
|
|
283
|
+
//
|
|
284
|
+
// Scanned by hand rather than with a regex: on a stylesheet whose tail holds no brace, the obvious
|
|
285
|
+
// pattern re-consumes the whole tail from every offset and backtracks it away again, which is
|
|
286
|
+
// quadratic and can cost minutes of build time on a large module.
|
|
287
|
+
let out = '';
|
|
288
|
+
let runStart = 0;
|
|
289
|
+
for (let index = 0; index < css.length; index++) {
|
|
290
|
+
const char = css[index];
|
|
291
|
+
if (char === '{') {
|
|
292
|
+
out += `${rewriteSelectorRun(css.slice(runStart, index), file)}{`;
|
|
293
|
+
runStart = index + 1;
|
|
294
|
+
} else if (char === '}' || char === ';') {
|
|
295
|
+
out += css.slice(runStart, index + 1);
|
|
296
|
+
runStart = index + 1;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return out + css.slice(runStart);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function rewriteSelectorRun(selector: string, file: string) {
|
|
303
|
+
if (selector.trimStart().startsWith('@')) return selector;
|
|
304
|
+
let out = selector;
|
|
305
|
+
for (const className of selectorClassNames(selector)) {
|
|
306
|
+
out = rewriteClassSelector(out, cssModuleClassName(file, className), className);
|
|
307
|
+
}
|
|
308
|
+
return out.replace(/:global\(((?:[^()]|\([^()]*\))*)\)/g, '$1');
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function scopeKeyframes(css: string, file: string) {
|
|
312
|
+
let out = css;
|
|
313
|
+
for (const name of keyframeNames(css)) {
|
|
314
|
+
const scoped = cssModuleClassName(file, name);
|
|
315
|
+
out = out.replace(
|
|
316
|
+
new RegExp(`(@(?:-webkit-)?keyframes\\s+)${escapeRegex(name)}(?![-_a-zA-Z0-9])`, 'g'),
|
|
317
|
+
`$1${scoped}`,
|
|
318
|
+
);
|
|
319
|
+
out = out.replace(
|
|
320
|
+
new RegExp(
|
|
321
|
+
`(animation-name\\s*:\\s*)${escapeRegex(name)}(?![-_a-zA-Z0-9])`,
|
|
322
|
+
'g',
|
|
323
|
+
),
|
|
324
|
+
`$1${scoped}`,
|
|
325
|
+
);
|
|
326
|
+
out = out.replace(
|
|
327
|
+
new RegExp(
|
|
328
|
+
`(animation\\s*:[^;{}]*?(?<![-_a-zA-Z0-9]))${escapeRegex(name)}(?![-_a-zA-Z0-9])`,
|
|
329
|
+
'g',
|
|
330
|
+
),
|
|
331
|
+
`$1${scoped}`,
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
return out;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function keyframeNames(css: string) {
|
|
338
|
+
const names = new Set<string>();
|
|
339
|
+
const pattern = /@(?:-webkit-)?keyframes\s+(-?[_a-zA-Z][\w-]*)/g;
|
|
340
|
+
let match: RegExpExecArray | null;
|
|
341
|
+
while ((match = pattern.exec(css))) {
|
|
342
|
+
if (match[1]) names.add(match[1]);
|
|
343
|
+
}
|
|
344
|
+
return names;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function cssModuleClassName(file: string, className: string) {
|
|
348
|
+
const base = path.basename(file).replace(/\.module\.css$/, '').replace(/[^_a-zA-Z0-9]/g, '_');
|
|
349
|
+
const hash = pathHash(cssModuleScopePath(file));
|
|
350
|
+
return `${base}_${className}_${hash}`;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function cssModuleScopePath(file: string) {
|
|
354
|
+
const normalized = file.split(path.sep).join('/');
|
|
355
|
+
const appIndex = normalized.lastIndexOf('/app/');
|
|
356
|
+
if (appIndex !== -1) return normalized.slice(appIndex + 1);
|
|
357
|
+
return normalized.split('/').slice(-3).join('/');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function rewriteClassSelector(css: string, scopedClassName: string, originalClassName: string) {
|
|
361
|
+
const pattern = new RegExp(`\\.${escapeRegex(originalClassName)}(?![-_a-zA-Z0-9])`, 'g');
|
|
362
|
+
return css.replace(pattern, `.${scopedClassName}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function pathHash(file: string) {
|
|
366
|
+
let hash = 5381;
|
|
367
|
+
for (const char of file) hash = ((hash << 5) + hash) ^ char.charCodeAt(0);
|
|
368
|
+
return (hash >>> 0).toString(36).slice(0, 5);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function escapeRegex(value: string) {
|
|
372
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
373
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// CSP nonce extraction (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// Next reads a `nonce-<value>` token out of the request's Content-Security-Policy header and stamps it
|
|
4
|
+
// onto the script / style / preload tags it emits so a strict CSP admits them. This module owns the
|
|
5
|
+
// EXTRACTION, parsing the header exactly as Next does; the actual stamping onto the emitted tags is
|
|
6
|
+
// renderer territory.
|
|
7
|
+
//
|
|
8
|
+
// Matches Next's parser: scan every directive's sources for a single-quoted `'nonce-<base64>'` token
|
|
9
|
+
// and return the first one found.
|
|
10
|
+
|
|
11
|
+
/** Extract the CSP nonce from a Content-Security-Policy header value, if any. */
|
|
12
|
+
export function nonceFromCsp(csp: string | null | undefined): string | undefined {
|
|
13
|
+
if (!csp) return undefined;
|
|
14
|
+
// A directive looks like `script-src 'self' 'nonce-abc123=='`. Sources are
|
|
15
|
+
// whitespace-separated; the nonce token is single-quoted `'nonce-<value>'`.
|
|
16
|
+
const directives = csp.split(';');
|
|
17
|
+
for (const directive of directives) {
|
|
18
|
+
for (const token of directive.trim().split(/\s+/)) {
|
|
19
|
+
const match = /^'nonce-([^']+)'$/.exec(token);
|
|
20
|
+
if (match?.[1]) return match[1];
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Extract the CSP nonce from a request's headers. */
|
|
27
|
+
export function nonceFromRequest(request: Request | undefined): string | undefined {
|
|
28
|
+
if (!request) return undefined;
|
|
29
|
+
return nonceFromCsp(request.headers.get('content-security-policy'));
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// Sass esbuild plugin + CSS-module resolvers (COMPAT).
|
|
2
|
+
//
|
|
3
|
+
// The concrete CssExtensions implementations wired by register-css-extras.ts:
|
|
4
|
+
// - sassChunkPlugin(config): an esbuild onLoad for `.scss`/`.sass` used in the CSS-chunk builds.
|
|
5
|
+
// Compiles to css text; a `*.module.*` file's selectors are already scoped by compileSassCss.
|
|
6
|
+
// - sassResolveCssModule / sassLoadCssModuleForClient: the class-map resolvers for the Bun
|
|
7
|
+
// render-time loader and the client bundle.
|
|
8
|
+
//
|
|
9
|
+
// includePaths come from next.config `sassOptions.includePaths`. All fields degrade to core's empty
|
|
10
|
+
// behavior when `sass` is not installed.
|
|
11
|
+
|
|
12
|
+
import type { Plugin } from 'esbuild';
|
|
13
|
+
import type { ResolvedConfig } from '../../config';
|
|
14
|
+
import { getNextConfig } from '../next/config-loader';
|
|
15
|
+
import {
|
|
16
|
+
compileSassCss,
|
|
17
|
+
isSassFile,
|
|
18
|
+
isSassModuleFile,
|
|
19
|
+
sassModuleMapping,
|
|
20
|
+
} from './sass';
|
|
21
|
+
|
|
22
|
+
/** Read `sassOptions.includePaths` from the loaded next.config. */
|
|
23
|
+
function sassIncludePaths(): string[] {
|
|
24
|
+
const config = getNextConfig();
|
|
25
|
+
const sassOptions = config.sassOptions as { includePaths?: unknown } | undefined;
|
|
26
|
+
const includePaths = sassOptions?.includePaths;
|
|
27
|
+
return Array.isArray(includePaths) ? includePaths.filter((p): p is string => typeof p === 'string') : [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* esbuild plugin: compile `.scss`/`.sass` to css inside the CSS-chunk build.
|
|
32
|
+
* Returns `loader: 'css'` so esbuild's CSS bundler concatenates the result in
|
|
33
|
+
* import order alongside the `@import`-ed `.css`.
|
|
34
|
+
*/
|
|
35
|
+
export function sassChunkPlugin(config: ResolvedConfig): Plugin {
|
|
36
|
+
return {
|
|
37
|
+
name: 'pnext-compat-sass',
|
|
38
|
+
setup(build) {
|
|
39
|
+
build.onLoad({ filter: /\.(?:scss|sass)$/ }, ({ path: file }) => ({
|
|
40
|
+
contents: compileSassCss(file, config.root, sassIncludePaths()),
|
|
41
|
+
loader: 'css',
|
|
42
|
+
}));
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Bun render-time loader / class-map resolver for `*.module.{scss,sass}`. */
|
|
48
|
+
export function sassResolveCssModule(config: ResolvedConfig) {
|
|
49
|
+
return (file: string): Record<string, string> | undefined =>
|
|
50
|
+
isSassModuleFile(file) ? sassModuleMapping(file, config.root, sassIncludePaths()) : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Client-bundle ESM text for a `.scss`/`.sass` import. */
|
|
54
|
+
export function sassLoadCssModuleForClient(config: ResolvedConfig) {
|
|
55
|
+
return (file: string): string | undefined => {
|
|
56
|
+
if (!isSassFile(file)) return undefined;
|
|
57
|
+
if (isSassModuleFile(file)) {
|
|
58
|
+
const map = sassModuleMapping(file, config.root, sassIncludePaths());
|
|
59
|
+
return `export default ${JSON.stringify(map)};`;
|
|
60
|
+
}
|
|
61
|
+
// Global (non-module) sass carries no class map; the css bytes ship via the
|
|
62
|
+
// CSS chunk, so the client import is a side-effect-only empty module.
|
|
63
|
+
return 'export default undefined;';
|
|
64
|
+
};
|
|
65
|
+
}
|