@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,474 @@
|
|
|
1
|
+
// One oxc-parser pass per unique source, memoized by content: every module fact the scanners need (imports,
|
|
2
|
+
// re-exports, `export *`, own export names, the `'use client'` directive) comes from that single parse
|
|
3
|
+
// instead of the regex passes and transpiler scan they replace. Regexes read comments and strings as code
|
|
4
|
+
// (phantom `type` imports, `'use client'` inside a comment) and never saw `export * from`; the transpiler
|
|
5
|
+
// scan reports its own injected JSX-runtime imports and throws on a shebang.
|
|
6
|
+
//
|
|
7
|
+
// Expression-shaped facts (dynamic() options, segment config, request-API use) stay on their gated string
|
|
8
|
+
// passes - materializing the full AST for them is slower than the passes they would replace.
|
|
9
|
+
//
|
|
10
|
+
// The same parse also backs `rewriteFacts`: the record-shaped rewrite passes (specifier aliasing, namespace
|
|
11
|
+
// imports, import.meta.url) splice byte spans off it instead of re-scanning the source with their own
|
|
12
|
+
// regexes.
|
|
13
|
+
import { parseSync } from 'oxc-parser';
|
|
14
|
+
import { spliceSource } from '../dev/module-transform';
|
|
15
|
+
|
|
16
|
+
export interface ScanEdge {
|
|
17
|
+
specifier: string;
|
|
18
|
+
/** Bound names: `default`, `*` (namespace object), or the imported names. */
|
|
19
|
+
exports: string[];
|
|
20
|
+
/** Byte offset of the statement — edges are consumed in true source order. */
|
|
21
|
+
index: number;
|
|
22
|
+
/** `export * from`: binds every named export of the target, none of them known here. */
|
|
23
|
+
star?: boolean;
|
|
24
|
+
/** `export … from` rather than `import`. */
|
|
25
|
+
reexport?: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** One module specifier literal, with the span of its quoted text. */
|
|
29
|
+
export interface SpecifierEdge {
|
|
30
|
+
specifier: string;
|
|
31
|
+
/** Span of the quoted literal, quotes included. */
|
|
32
|
+
start: number;
|
|
33
|
+
end: number;
|
|
34
|
+
/** `side-effect` is `import 'x'`: no clause, so no `from` for a textual pass to key on. */
|
|
35
|
+
kind: 'import' | 'side-effect' | 'export' | 'dynamic' | 'require';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface ImportBinding {
|
|
39
|
+
/** Name in the target module; `default`/`*` for the default/namespace forms. */
|
|
40
|
+
imported: string;
|
|
41
|
+
local: string;
|
|
42
|
+
/** `import type …` / `{ type X }`: erased, but part of the statement's text. */
|
|
43
|
+
type: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A static `import` statement, spans included — the unit the folded passes splice. */
|
|
47
|
+
export interface ImportStatement {
|
|
48
|
+
specifier: string;
|
|
49
|
+
/** Statement span (`import` … `;`). */
|
|
50
|
+
start: number;
|
|
51
|
+
end: number;
|
|
52
|
+
/** Span of the quoted specifier literal inside the statement. */
|
|
53
|
+
specifierStart: number;
|
|
54
|
+
specifierEnd: number;
|
|
55
|
+
bindings: ImportBinding[];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The module-record projection the rewrite passes splice against: specifier literals, import statements and
|
|
60
|
+
* `import.meta` spans, all exact - never a match inside a string or comment. Shares `scanFacts`' memoized
|
|
61
|
+
* parse, so a folded pass on an already-scanned source costs nothing beyond the lookup.
|
|
62
|
+
*/
|
|
63
|
+
export interface RewriteFacts {
|
|
64
|
+
edges: SpecifierEdge[];
|
|
65
|
+
imports: ImportStatement[];
|
|
66
|
+
importMetas: { start: number; end: number }[];
|
|
67
|
+
/**
|
|
68
|
+
* The parse recovered from errors, so the record may be incomplete — folded
|
|
69
|
+
* passes fall back to their textual form rather than silently under-rewrite.
|
|
70
|
+
*/
|
|
71
|
+
unreliable: boolean;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export interface ScanFacts {
|
|
75
|
+
/** Static value imports and `export … from` re-exports, in source order. */
|
|
76
|
+
imports: ScanEdge[];
|
|
77
|
+
/** `import('literal')` expressions, in source order. */
|
|
78
|
+
dynamicImports: { specifier: string; index: number }[];
|
|
79
|
+
/** `require('literal')` calls — CJS sources still reach these scanners. */
|
|
80
|
+
requires: { specifier: string; index: number; specifierStart: number }[];
|
|
81
|
+
/** This module's own value export names, `default` included. */
|
|
82
|
+
exportNames: string[];
|
|
83
|
+
/** Specifiers of `export * from` (whose names live in the target). */
|
|
84
|
+
exportStars: string[];
|
|
85
|
+
/** A real `'use client'` directive — prologue position only, never a comment. */
|
|
86
|
+
useClient: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const stylesheetOrData = /\.(?:css|scss|sass|less|styl|json)$/;
|
|
90
|
+
|
|
91
|
+
const emptyFacts: ScanFacts = {
|
|
92
|
+
imports: [],
|
|
93
|
+
dynamicImports: [],
|
|
94
|
+
requires: [],
|
|
95
|
+
exportNames: [],
|
|
96
|
+
exportStars: [],
|
|
97
|
+
useClient: false,
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const emptyRewriteFacts: RewriteFacts = {
|
|
101
|
+
edges: [],
|
|
102
|
+
imports: [],
|
|
103
|
+
importMetas: [],
|
|
104
|
+
unreliable: false,
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const memo = new Map<string, { scan: ScanFacts; rewrite: RewriteFacts }>();
|
|
108
|
+
let hits = 0;
|
|
109
|
+
let misses = 0;
|
|
110
|
+
|
|
111
|
+
export function scanFacts(file: string, source: string): ScanFacts {
|
|
112
|
+
// The module walk reaches stylesheets/data as ordinary edges; they hold no
|
|
113
|
+
// module facts and are not parseable as script.
|
|
114
|
+
if (stylesheetOrData.test(file)) return emptyFacts;
|
|
115
|
+
return parsed(file, source).scan;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Every specifier a module pulls in — static, dynamic and `require`, in source order. */
|
|
119
|
+
export function importSpecifiers(source: string, sourcefile = 'source') {
|
|
120
|
+
const facts = scanFacts(sourcefile, source);
|
|
121
|
+
return [
|
|
122
|
+
...facts.imports.map(edge => edge.specifier),
|
|
123
|
+
...facts.dynamicImports.map(entry => entry.specifier),
|
|
124
|
+
...facts.requires.map(entry => entry.specifier),
|
|
125
|
+
];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function moduleExportNames(source: string, sourcefile = 'source.tsx') {
|
|
129
|
+
return scanFacts(sourcefile, source).exportNames;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Specifiers of `export * from` — their names live in the target module. */
|
|
133
|
+
export function moduleExportStars(source: string, sourcefile = 'source.tsx') {
|
|
134
|
+
return scanFacts(sourcefile, source).exportStars;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Module-record spans for the rewrite passes (see `RewriteFacts`). */
|
|
138
|
+
export function rewriteFacts(file: string, source: string): RewriteFacts {
|
|
139
|
+
if (stylesheetOrData.test(file)) return emptyRewriteFacts;
|
|
140
|
+
return parsed(file, source).rewrite;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function parsed(file: string, source: string, lang = langForFile(file)) {
|
|
144
|
+
const key = `${lang}\0${source.length}\0${Bun.hash(source).toString()}`;
|
|
145
|
+
const cached = memo.get(key);
|
|
146
|
+
if (cached) {
|
|
147
|
+
hits += 1;
|
|
148
|
+
return cached;
|
|
149
|
+
}
|
|
150
|
+
misses += 1;
|
|
151
|
+
const facts = parseFacts(file, source, lang);
|
|
152
|
+
memo.set(key, facts);
|
|
153
|
+
return facts;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Splice every module specifier `replace` answers for, in place. The span is
|
|
158
|
+
* the quoted literal, so formatting and comments around it are untouched, and
|
|
159
|
+
* a specifier appearing in a string or a comment is never seen at all.
|
|
160
|
+
* `replace` returns the REPLACEMENT LITERAL (quotes included) or undefined.
|
|
161
|
+
*/
|
|
162
|
+
export function rewriteSpecifierLiterals(
|
|
163
|
+
source: string,
|
|
164
|
+
file: string,
|
|
165
|
+
replace: (specifier: string, kind: SpecifierEdge['kind']) => string | undefined,
|
|
166
|
+
): string {
|
|
167
|
+
const facts = rewriteFacts(file, source);
|
|
168
|
+
// A partially recovered parse has a partial record; the textual scan the
|
|
169
|
+
// folded passes replaced is the safe answer there.
|
|
170
|
+
const edges = facts.unreliable ? textualEdges(source) : facts.edges;
|
|
171
|
+
const edits: { start: number; end: number; value: string }[] = [];
|
|
172
|
+
for (const edge of edges) {
|
|
173
|
+
const value = replace(edge.specifier, edge.kind);
|
|
174
|
+
if (value !== undefined) edits.push({ start: edge.start, end: edge.end, value });
|
|
175
|
+
}
|
|
176
|
+
return edits.length > 0 ? spliceSource(source, edits) : source;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const textualSpecifier = /(\bfrom\s*|\b(?:import|require)\s*\(\s*)(['"])([^'"]+)\2/g;
|
|
180
|
+
|
|
181
|
+
function textualEdges(source: string): SpecifierEdge[] {
|
|
182
|
+
const edges: SpecifierEdge[] = [];
|
|
183
|
+
textualSpecifier.lastIndex = 0;
|
|
184
|
+
let match: RegExpExecArray | null;
|
|
185
|
+
while ((match = textualSpecifier.exec(source))) {
|
|
186
|
+
const start = match.index + match[1]!.length;
|
|
187
|
+
edges.push({
|
|
188
|
+
specifier: match[3]!,
|
|
189
|
+
start,
|
|
190
|
+
end: start + match[3]!.length + 2,
|
|
191
|
+
kind: match[1]!.includes('require') ? 'require' : match[1]!.includes('import') ? 'dynamic' : 'import',
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
return edges;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/** Memo effectiveness, for the dev-boot benches. */
|
|
198
|
+
export function scanFactsStats() {
|
|
199
|
+
return { hits, misses, sources: memo.size };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function parseFacts(file: string, source: string, lang: ParserLang) {
|
|
203
|
+
// Parse errors are recovered, never thrown: a scan of a half-typed file in
|
|
204
|
+
// the dev watcher must still yield whatever the parser did understand.
|
|
205
|
+
const result = parseSync(file, source, { lang });
|
|
206
|
+
const imports: ScanEdge[] = [];
|
|
207
|
+
const exportNames: string[] = [];
|
|
208
|
+
const exportStars: string[] = [];
|
|
209
|
+
const edges: SpecifierEdge[] = [];
|
|
210
|
+
const importStatements: ImportStatement[] = [];
|
|
211
|
+
|
|
212
|
+
for (const statement of result.module.staticImports) {
|
|
213
|
+
const names: string[] = [];
|
|
214
|
+
const bindings: ImportBinding[] = [];
|
|
215
|
+
let bindsValue = statement.entries.length === 0;
|
|
216
|
+
for (const entry of statement.entries) {
|
|
217
|
+
const kind: string = entry.importName.kind;
|
|
218
|
+
bindings.push({
|
|
219
|
+
imported:
|
|
220
|
+
kind === 'Default' ? 'default' : kind === 'NamespaceObject' ? '*' : (entry.importName.name ?? ''),
|
|
221
|
+
local: entry.localName.value ?? '',
|
|
222
|
+
type: entry.isType,
|
|
223
|
+
});
|
|
224
|
+
if (entry.isType) continue;
|
|
225
|
+
bindsValue = true;
|
|
226
|
+
if (kind === 'Default') names.push('default');
|
|
227
|
+
else if (kind === 'NamespaceObject') names.push('*');
|
|
228
|
+
else if (entry.importName.name) names.push(entry.importName.name);
|
|
229
|
+
}
|
|
230
|
+
importStatements.push({
|
|
231
|
+
specifier: statement.moduleRequest.value,
|
|
232
|
+
start: statement.start,
|
|
233
|
+
end: statement.end,
|
|
234
|
+
specifierStart: statement.moduleRequest.start,
|
|
235
|
+
specifierEnd: statement.moduleRequest.end,
|
|
236
|
+
bindings,
|
|
237
|
+
});
|
|
238
|
+
edges.push({
|
|
239
|
+
specifier: statement.moduleRequest.value,
|
|
240
|
+
start: statement.moduleRequest.start,
|
|
241
|
+
end: statement.moduleRequest.end,
|
|
242
|
+
kind: statement.entries.length === 0 ? 'side-effect' : 'import',
|
|
243
|
+
});
|
|
244
|
+
// `import type { … }` binds nothing at runtime: not an edge.
|
|
245
|
+
if (!bindsValue) continue;
|
|
246
|
+
imports.push({
|
|
247
|
+
specifier: statement.moduleRequest.value,
|
|
248
|
+
exports: names,
|
|
249
|
+
index: statement.start,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// One `export { a, b } from 'x'` reports the same moduleRequest per entry.
|
|
254
|
+
const seenExportRequests = new Set<number>();
|
|
255
|
+
for (const statement of result.module.staticExports) {
|
|
256
|
+
for (const entry of statement.entries) {
|
|
257
|
+
const request = entry.moduleRequest;
|
|
258
|
+
if (!request || seenExportRequests.has(request.start)) continue;
|
|
259
|
+
seenExportRequests.add(request.start);
|
|
260
|
+
edges.push({ specifier: request.value, start: request.start, end: request.end, kind: 'export' });
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
for (const statement of result.module.staticExports) {
|
|
265
|
+
const edges = new Map<string, ScanEdge>();
|
|
266
|
+
for (const entry of statement.entries) {
|
|
267
|
+
if (entry.isType) continue;
|
|
268
|
+
const exportKind: string = entry.exportName.kind;
|
|
269
|
+
if (exportKind === 'Default') exportNames.push('default');
|
|
270
|
+
else if (entry.exportName.name) exportNames.push(entry.exportName.name);
|
|
271
|
+
|
|
272
|
+
const specifier = entry.moduleRequest?.value;
|
|
273
|
+
if (!specifier) continue;
|
|
274
|
+
const importKind: string = entry.importName.kind;
|
|
275
|
+
const star = importKind === 'AllButDefault';
|
|
276
|
+
if (star) exportStars.push(specifier);
|
|
277
|
+
let edge = edges.get(specifier);
|
|
278
|
+
if (!edge) {
|
|
279
|
+
edge = { specifier, exports: [], index: statement.start, reexport: true };
|
|
280
|
+
edges.set(specifier, edge);
|
|
281
|
+
imports.push(edge);
|
|
282
|
+
}
|
|
283
|
+
if (star) edge.star = true;
|
|
284
|
+
else if (importKind === 'All') edge.exports.push('*');
|
|
285
|
+
else if (entry.importName.name) edge.exports.push(entry.importName.name);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
imports.sort((a, b) => a.index - b.index);
|
|
290
|
+
|
|
291
|
+
const dynamicImports: { specifier: string; index: number }[] = [];
|
|
292
|
+
for (const entry of result.module.dynamicImports) {
|
|
293
|
+
const raw = source.slice(entry.moduleRequest.start, entry.moduleRequest.end);
|
|
294
|
+
const specifier = literalSpecifier(raw);
|
|
295
|
+
if (specifier === undefined) continue;
|
|
296
|
+
dynamicImports.push({ specifier, index: entry.start });
|
|
297
|
+
// The literal's own span, without the whitespace `moduleRequest` includes.
|
|
298
|
+
const lead = raw.length - raw.trimStart().length;
|
|
299
|
+
edges.push({
|
|
300
|
+
specifier,
|
|
301
|
+
start: entry.moduleRequest.start + lead,
|
|
302
|
+
end: entry.moduleRequest.start + lead + specifier.length + 2,
|
|
303
|
+
kind: 'dynamic',
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const requires = requireCalls(source, result.comments);
|
|
308
|
+
for (const call of requires) {
|
|
309
|
+
edges.push({
|
|
310
|
+
specifier: call.specifier,
|
|
311
|
+
start: call.specifierStart,
|
|
312
|
+
end: call.specifierStart + call.specifier.length + 2,
|
|
313
|
+
kind: 'require',
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
edges.sort((a, b) => a.start - b.start);
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
scan: {
|
|
320
|
+
imports,
|
|
321
|
+
dynamicImports,
|
|
322
|
+
requires,
|
|
323
|
+
exportNames,
|
|
324
|
+
exportStars,
|
|
325
|
+
useClient: hasUseClient(source, result),
|
|
326
|
+
},
|
|
327
|
+
rewrite: {
|
|
328
|
+
edges,
|
|
329
|
+
imports: importStatements,
|
|
330
|
+
importMetas: result.module.importMetas.map(meta => ({ start: meta.start, end: meta.end })),
|
|
331
|
+
unreliable: result.errors.length > 0,
|
|
332
|
+
},
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* `result.program.body` deserializes the whole AST across the NAPI boundary, and the prologue sniff
|
|
338
|
+
* was its only consumer. The raw-source scanner answers it without the AST; the AST is still
|
|
339
|
+
* consulted for the prologue shapes the scanner will not commit to.
|
|
340
|
+
*/
|
|
341
|
+
function hasUseClient(source: string, result: { program: { body: readonly Directive[] } }) {
|
|
342
|
+
const sniffed = sniffDirective(source, 'use client');
|
|
343
|
+
if (sniffed !== undefined) return sniffed;
|
|
344
|
+
// Uncertain, but a directive is its raw text: no occurrence, no directive.
|
|
345
|
+
if (!source.includes('use client')) return false;
|
|
346
|
+
return hasDirective(result.program.body, 'use client');
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
interface Directive {
|
|
350
|
+
type: string;
|
|
351
|
+
directive?: string | null;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// A string literal is only a directive if what follows it ends the statement; a
|
|
355
|
+
// continuation token means it was an expression (`'use client' + x`).
|
|
356
|
+
const continuesExpression = /[+\-*/%.,?:=<>!&|^~([`)\]}]/;
|
|
357
|
+
const wordContinuation = /^(?:in|instanceof)\b/;
|
|
358
|
+
const lineTerminator = /[\n\r\u2028\u2029]/;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* Directive-prologue sniff over raw source. Returns undefined - not a guess - for any shape it cannot decide
|
|
362
|
+
* exactly (escaped literal, unterminated string or comment, ambiguous ASI), so the caller falls back to the
|
|
363
|
+
* AST.
|
|
364
|
+
*/
|
|
365
|
+
function sniffDirective(source: string, directive: string): boolean | undefined {
|
|
366
|
+
let index = 0;
|
|
367
|
+
if (source.startsWith('#!')) {
|
|
368
|
+
const nl = source.search(lineTerminator);
|
|
369
|
+
if (nl < 0) return false;
|
|
370
|
+
index = nl + 1;
|
|
371
|
+
}
|
|
372
|
+
for (;;) {
|
|
373
|
+
index = skipTrivia(source, index);
|
|
374
|
+
if (index < 0) return undefined;
|
|
375
|
+
const quote = source[index];
|
|
376
|
+
if (quote !== '"' && quote !== "'") return false; // prologue is over
|
|
377
|
+
|
|
378
|
+
let end = index + 1;
|
|
379
|
+
while (end < source.length) {
|
|
380
|
+
const char = source[end]!;
|
|
381
|
+
// An escape makes the raw text differ from the directive, but decoding it
|
|
382
|
+
// exactly is the AST's job, not this scanner's.
|
|
383
|
+
if (char === '\\' || lineTerminator.test(char)) return undefined;
|
|
384
|
+
if (char === quote) break;
|
|
385
|
+
end += 1;
|
|
386
|
+
}
|
|
387
|
+
if (end >= source.length) return undefined; // unterminated literal
|
|
388
|
+
const raw = source.slice(index + 1, end);
|
|
389
|
+
|
|
390
|
+
const after = skipTrivia(source, end + 1);
|
|
391
|
+
if (after < 0) return undefined;
|
|
392
|
+
const next = source[after];
|
|
393
|
+
let terminated = after >= source.length || next === ';';
|
|
394
|
+
if (!terminated && lineTerminator.test(source.slice(end + 1, after))) {
|
|
395
|
+
// ASI closes the statement unless the next token continues the expression.
|
|
396
|
+
terminated = !continuesExpression.test(next!) && !wordContinuation.test(source.slice(after, after + 11));
|
|
397
|
+
}
|
|
398
|
+
if (!terminated) return undefined;
|
|
399
|
+
if (raw === directive) return true;
|
|
400
|
+
index = next === ';' ? after + 1 : after;
|
|
401
|
+
if (index >= source.length) return false;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/** Past whitespace and comments; -1 for an unterminated block comment. */
|
|
406
|
+
function skipTrivia(source: string, index: number) {
|
|
407
|
+
for (;;) {
|
|
408
|
+
while (index < source.length && /\s/.test(source[index]!)) index += 1;
|
|
409
|
+
if (source[index] !== '/') return index;
|
|
410
|
+
if (source[index + 1] === '/') {
|
|
411
|
+
let end = index + 2;
|
|
412
|
+
while (end < source.length && !lineTerminator.test(source[end]!)) end += 1;
|
|
413
|
+
index = end;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
if (source[index + 1] === '*') {
|
|
417
|
+
const end = source.indexOf('*/', index + 2);
|
|
418
|
+
if (end < 0) return -1;
|
|
419
|
+
index = end + 2;
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
return index;
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
function hasDirective(body: readonly { type: string; directive?: string | null }[], directive: string) {
|
|
427
|
+
// The prologue ends at the first non-directive statement; comments and other
|
|
428
|
+
// directives before it do not.
|
|
429
|
+
for (const statement of body) {
|
|
430
|
+
if (statement.type !== 'ExpressionStatement' || typeof statement.directive !== 'string') return false;
|
|
431
|
+
if (statement.directive === directive) return true;
|
|
432
|
+
}
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const requirePattern = /\brequire\s*\(\s*(['"])([^'"]+)\1\s*\)/g;
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* CJS `require` is an ordinary call, not a module record entry, so it stays on a regex - but a regex reads
|
|
440
|
+
* comments as code, and a prose `require('x')` in a doc comment became a real graph edge, an unresolvable
|
|
441
|
+
* one aborting the whole module compile. The parse's comment spans are already in hand, so drop every match
|
|
442
|
+
* that lands inside one.
|
|
443
|
+
*/
|
|
444
|
+
function requireCalls(source: string, comments: readonly { start: number; end: number }[]) {
|
|
445
|
+
const calls: { specifier: string; index: number; specifierStart: number }[] = [];
|
|
446
|
+
if (!source.includes('require')) return calls;
|
|
447
|
+
requirePattern.lastIndex = 0;
|
|
448
|
+
let match: RegExpExecArray | null;
|
|
449
|
+
// Both comments and matches ascend, so one shared cursor walks them together.
|
|
450
|
+
let cursor = 0;
|
|
451
|
+
while ((match = requirePattern.exec(source))) {
|
|
452
|
+
const index = match.index;
|
|
453
|
+
while (cursor < comments.length && comments[cursor]!.end <= index) cursor += 1;
|
|
454
|
+
if (cursor < comments.length && index >= comments[cursor]!.start) continue;
|
|
455
|
+
calls.push({
|
|
456
|
+
specifier: match[2]!,
|
|
457
|
+
index,
|
|
458
|
+
specifierStart: index + match[0].indexOf(match[1]!),
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
return calls;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function literalSpecifier(raw: string) {
|
|
465
|
+
return /^\s*(['"])([^'"]*)\1\s*$/.exec(raw)?.[2] || undefined;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
type ParserLang = 'ts' | 'tsx';
|
|
469
|
+
|
|
470
|
+
// `.ts` forbids JSX (`<T>value` is a type assertion there); everything else —
|
|
471
|
+
// including extensionless scan inputs — parses as the tsx superset.
|
|
472
|
+
function langForFile(file: string): ParserLang {
|
|
473
|
+
return /\.[mc]?ts$/.test(file) ? 'ts' : 'tsx';
|
|
474
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// One read per first-party source per build. A build has three consumers of the same app sources -
|
|
2
|
+
// the route-fact walk (sync), action discovery (async) and the client loader (async) - and each used
|
|
3
|
+
// to read the file itself. Whoever asks first pays; everyone after gets the same string. This buys
|
|
4
|
+
// reads, not parses: `scanFacts`' memo is keyed on content, so the three already shared one parse
|
|
5
|
+
// per source.
|
|
6
|
+
//
|
|
7
|
+
// Scoped, not global: `beginSourceScope`/`endSourceScope` bracket a single build, so the map is
|
|
8
|
+
// released when it ends and nothing outside a build is cached at all. That is what keeps the dev
|
|
9
|
+
// watcher out of it - dev reads go straight to the filesystem, so a save is never served a stale
|
|
10
|
+
// string, and content-keyed invalidation downstream is untouched.
|
|
11
|
+
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { readFile } from 'node:fs/promises';
|
|
13
|
+
|
|
14
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
15
|
+
const disabled = process.env.PNEXT_SRC_CACHE === '0';
|
|
16
|
+
|
|
17
|
+
const contents = new Map<string, string>();
|
|
18
|
+
// Reads still in flight: two async consumers asking at once share one read
|
|
19
|
+
// rather than racing two. Resolved entries move to `contents`.
|
|
20
|
+
const inflight = new Map<string, Promise<string>>();
|
|
21
|
+
let scoped = false;
|
|
22
|
+
let reads = 0;
|
|
23
|
+
let hits = 0;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Start caching source reads. Two builds in one process (the test runner) share the map - keyed by absolute
|
|
27
|
+
* path, so a shared entry is the same file - and whichever ends first drops it; the other keeps building,
|
|
28
|
+
* uncached.
|
|
29
|
+
*/
|
|
30
|
+
export function beginSourceScope() {
|
|
31
|
+
if (disabled) return;
|
|
32
|
+
scoped = true;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** End the scope and drop everything it held. */
|
|
36
|
+
export function endSourceScope() {
|
|
37
|
+
scoped = false;
|
|
38
|
+
contents.clear();
|
|
39
|
+
inflight.clear();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function readSourceSync(file: string): string {
|
|
43
|
+
if (!scoped) return readFileSync(file, 'utf8');
|
|
44
|
+
const cached = contents.get(file);
|
|
45
|
+
if (cached !== undefined) {
|
|
46
|
+
hits += 1;
|
|
47
|
+
return cached;
|
|
48
|
+
}
|
|
49
|
+
reads += 1;
|
|
50
|
+
const source = readFileSync(file, 'utf8');
|
|
51
|
+
contents.set(file, source);
|
|
52
|
+
return source;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function readSourceText(file: string): Promise<string> {
|
|
56
|
+
if (!scoped) return readFile(file, 'utf8');
|
|
57
|
+
const cached = contents.get(file);
|
|
58
|
+
if (cached !== undefined) {
|
|
59
|
+
hits += 1;
|
|
60
|
+
return cached;
|
|
61
|
+
}
|
|
62
|
+
const pending = inflight.get(file);
|
|
63
|
+
if (pending) {
|
|
64
|
+
hits += 1;
|
|
65
|
+
return pending;
|
|
66
|
+
}
|
|
67
|
+
reads += 1;
|
|
68
|
+
// A read that fails is dropped from `inflight` too: the next asker retries and
|
|
69
|
+
// sees the error at its own call site, as it would without the cache.
|
|
70
|
+
const read = (async () => {
|
|
71
|
+
try {
|
|
72
|
+
const source = await readFile(file, 'utf8');
|
|
73
|
+
contents.set(file, source);
|
|
74
|
+
return source;
|
|
75
|
+
} finally {
|
|
76
|
+
inflight.delete(file);
|
|
77
|
+
}
|
|
78
|
+
})();
|
|
79
|
+
inflight.set(file, read);
|
|
80
|
+
return read;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Read effectiveness, for the build benches. */
|
|
84
|
+
export function sourceCacheStats() {
|
|
85
|
+
return { reads, hits, files: contents.size };
|
|
86
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// x-forwarded-* request header injection (CORE HTTP correctness).
|
|
2
|
+
//
|
|
3
|
+
// Before route matching / render, the server records the observed host, port,
|
|
4
|
+
// and protocol on the incoming request headers so headers() during render and
|
|
5
|
+
// any request-header reader (middleware, route handlers) see them. Client-sent
|
|
6
|
+
// x-forwarded-* values are never overridden (a trusted proxy already set them),
|
|
7
|
+
// including multi-value forms like "https, https".
|
|
8
|
+
|
|
9
|
+
const FORWARDED_HOST = 'x-forwarded-host';
|
|
10
|
+
const FORWARDED_PORT = 'x-forwarded-port';
|
|
11
|
+
const FORWARDED_PROTO = 'x-forwarded-proto';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Return the request with x-forwarded-host/port/proto injected from the request
|
|
15
|
+
* URL + Host header, preserving any client-sent values. Returns the same
|
|
16
|
+
* request when every value was already present (no allocation on the hot path).
|
|
17
|
+
*/
|
|
18
|
+
export function withForwardedHeaders(request: Request): Request {
|
|
19
|
+
const headers = request.headers;
|
|
20
|
+
const hasHost = headers.has(FORWARDED_HOST);
|
|
21
|
+
const hasPort = headers.has(FORWARDED_PORT);
|
|
22
|
+
const hasProto = headers.has(FORWARDED_PROTO);
|
|
23
|
+
if (hasHost && hasPort && hasProto) return request;
|
|
24
|
+
|
|
25
|
+
let url: URL;
|
|
26
|
+
try {
|
|
27
|
+
url = new URL(request.url);
|
|
28
|
+
} catch {
|
|
29
|
+
return request;
|
|
30
|
+
}
|
|
31
|
+
const host = headers.get('host') ?? url.host;
|
|
32
|
+
const proto = url.protocol.replace(':', '');
|
|
33
|
+
// The server's own listen port (reflected in the request URL by the runtime).
|
|
34
|
+
const port = url.port;
|
|
35
|
+
|
|
36
|
+
const next = new Headers(headers);
|
|
37
|
+
if (!hasHost) next.set(FORWARDED_HOST, host);
|
|
38
|
+
if (!hasPort) next.set(FORWARDED_PORT, port);
|
|
39
|
+
if (!hasProto) next.set(FORWARDED_PROTO, proto);
|
|
40
|
+
return new Request(request, { headers: next });
|
|
41
|
+
}
|