@wular/pnext 0.0.4 → 0.0.7
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 +22 -16
- package/bin/pnext +6 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +87 -22
- package/reference/config.md +40 -93
- package/reference/css.md +42 -33
- package/reference/dev.md +26 -33
- package/reference/env.md +18 -20
- package/reference/getting-started.md +132 -0
- package/reference/metadata.md +31 -46
- package/reference/navigation.md +65 -105
- package/reference/performance.md +21 -142
- package/reference/rendering.md +78 -75
- package/reference/routing.md +97 -107
- package/reference/typegen.md +27 -29
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +121 -114
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +52 -53
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +72 -64
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +94 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +802 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +32 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +2022 -1995
- package/src/{api → client}/router/types.ts +98 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1897
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +86 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2087 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +318 -296
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +87 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +827 -815
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +954 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +428 -406
- package/src/styles.d.ts +9 -0
- package/src/types.ts +324 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/reference/overview.md +0 -35
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Persisted client cache keys - the restart half of
|
|
1
|
+
// Persisted client cache keys - the restart half of runtime/module-cache.ts. A route's client bundle
|
|
2
2
|
// already lives in a content-keyed out-dir, so a restart never has to rebuild it; deriving that key
|
|
3
3
|
// did, by walking every source the route's client graph reaches and hashing the bytes. The bytes
|
|
4
4
|
// cannot have moved while nothing was watching them *unless* the file did, so this persists the
|
|
@@ -10,66 +10,66 @@
|
|
|
10
10
|
// content differs -> full walk, new key, new out-dir
|
|
11
11
|
// so an edit made while the server was not running invalidates, and a touch that changed nothing
|
|
12
12
|
// does not.
|
|
13
|
-
import { readFileSync, writeFileSync } from 'node:fs'
|
|
14
|
-
import { mkdir, readFile, stat } from 'node:fs/promises'
|
|
15
|
-
import path from 'node:path'
|
|
13
|
+
import { readFileSync, writeFileSync } from 'node:fs'
|
|
14
|
+
import { mkdir, readFile, stat } from 'node:fs/promises'
|
|
15
|
+
import path from 'node:path'
|
|
16
16
|
import {
|
|
17
17
|
clientCacheKeyParts,
|
|
18
18
|
clientCacheStaticHash,
|
|
19
19
|
clientSourceHash,
|
|
20
20
|
type ClientCacheSource,
|
|
21
|
-
} from '
|
|
22
|
-
import type { ResolvedConfig } from '
|
|
23
|
-
import type { RouteManifestEntry } from '
|
|
24
|
-
import { writeFileAtomic } from '
|
|
25
|
-
import { restartCacheEnabled } from './
|
|
21
|
+
} from '../../client/build'
|
|
22
|
+
import type { ResolvedConfig } from '../../config'
|
|
23
|
+
import type { RouteManifestEntry } from '../../types'
|
|
24
|
+
import { writeFileAtomic } from '../../utils/fs'
|
|
25
|
+
import { restartCacheEnabled } from './enabled'
|
|
26
26
|
|
|
27
27
|
/** `[file, mtimeMs, size, contentHash]` — the same record shape the module index uses. */
|
|
28
|
-
type SourceEntry = [string, number, number, string]
|
|
28
|
+
type SourceEntry = [string, number, number, string]
|
|
29
29
|
|
|
30
30
|
interface RouteEntry {
|
|
31
31
|
/** Hash of everything in the key that is not a walked source. */
|
|
32
|
-
static: string
|
|
33
|
-
key: string
|
|
34
|
-
files: SourceEntry[]
|
|
32
|
+
static: string
|
|
33
|
+
key: string
|
|
34
|
+
files: SourceEntry[]
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
interface IndexFile {
|
|
38
|
-
version: number
|
|
39
|
-
routes: Record<string, RouteEntry
|
|
38
|
+
version: number
|
|
39
|
+
routes: Record<string, RouteEntry>
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
/** Bumped whenever a record's shape changes; an older index is simply ignored. */
|
|
43
|
-
const INDEX_VERSION = 1
|
|
43
|
+
const INDEX_VERSION = 1
|
|
44
44
|
|
|
45
45
|
interface KeyCache {
|
|
46
|
-
file: string
|
|
47
|
-
routes: Map<string, RouteEntry
|
|
48
|
-
dirty: boolean
|
|
49
|
-
flush?: Timer
|
|
50
|
-
retries: number
|
|
46
|
+
file: string
|
|
47
|
+
routes: Map<string, RouteEntry>
|
|
48
|
+
dirty: boolean
|
|
49
|
+
flush?: Timer
|
|
50
|
+
retries: number
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const caches = new Map<string, KeyCache>()
|
|
53
|
+
const caches = new Map<string, KeyCache>()
|
|
54
54
|
|
|
55
55
|
function keyCache(config: ResolvedConfig): KeyCache {
|
|
56
|
-
const file = path.join(config.outPath, 'cache', 'client', 'keys.json')
|
|
57
|
-
const existing = caches.get(file)
|
|
58
|
-
if (existing) return existing
|
|
59
|
-
const created: KeyCache = { file, routes: readIndex(file), dirty: false, retries: 0 }
|
|
60
|
-
caches.set(file, created)
|
|
56
|
+
const file = path.join(config.outPath, 'cache', 'client', 'keys.json')
|
|
57
|
+
const existing = caches.get(file)
|
|
58
|
+
if (existing) return existing
|
|
59
|
+
const created: KeyCache = { file, routes: readIndex(file), dirty: false, retries: 0 }
|
|
60
|
+
caches.set(file, created)
|
|
61
61
|
// A short-lived process can exit before the debounce fires; without this its
|
|
62
62
|
// walk would not survive to the next boot.
|
|
63
63
|
process.on('exit', () => {
|
|
64
|
-
if (!created.dirty) return
|
|
65
|
-
created.dirty = false
|
|
64
|
+
if (!created.dirty) return
|
|
65
|
+
created.dirty = false
|
|
66
66
|
try {
|
|
67
|
-
writeFileSync(created.file, serializeIndex(created.routes))
|
|
67
|
+
writeFileSync(created.file, serializeIndex(created.routes))
|
|
68
68
|
} catch {
|
|
69
69
|
// Best effort: a missing index only costs the next boot a walk.
|
|
70
70
|
}
|
|
71
|
-
})
|
|
72
|
-
return created
|
|
71
|
+
})
|
|
72
|
+
return created
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
/**
|
|
@@ -81,35 +81,35 @@ export async function devClientCacheKey(
|
|
|
81
81
|
route: RouteManifestEntry,
|
|
82
82
|
nextCompat: boolean,
|
|
83
83
|
): Promise<string> {
|
|
84
|
-
if (!restartCacheEnabled()) return (await clientCacheKeyParts(route, nextCompat, config)).key
|
|
85
|
-
const cache = keyCache(config)
|
|
86
|
-
const staticHash = await clientCacheStaticHash(route, nextCompat, config)
|
|
87
|
-
const entry = cache.routes.get(route.id)
|
|
84
|
+
if (!restartCacheEnabled()) return (await clientCacheKeyParts(route, nextCompat, config)).key
|
|
85
|
+
const cache = keyCache(config)
|
|
86
|
+
const staticHash = await clientCacheStaticHash(route, nextCompat, config)
|
|
87
|
+
const entry = cache.routes.get(route.id)
|
|
88
88
|
if (entry?.static === staticHash) {
|
|
89
|
-
const revalidated = await revalidate(entry.files)
|
|
89
|
+
const revalidated = await revalidate(entry.files)
|
|
90
90
|
if (revalidated) {
|
|
91
91
|
// Only the stats moved (a touch, a checkout): keep the key, record the
|
|
92
92
|
// new stats so the next boot takes the O(1) path again.
|
|
93
93
|
if (revalidated !== entry.files) {
|
|
94
|
-
cache.routes.set(route.id, { ...entry, files: revalidated })
|
|
95
|
-
persist(cache)
|
|
94
|
+
cache.routes.set(route.id, { ...entry, files: revalidated })
|
|
95
|
+
persist(cache)
|
|
96
96
|
}
|
|
97
|
-
return entry.key
|
|
97
|
+
return entry.key
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const parts = await clientCacheKeyParts(route, nextCompat, config, staticHash)
|
|
101
|
+
const parts = await clientCacheKeyParts(route, nextCompat, config, staticHash)
|
|
102
102
|
cache.routes.set(route.id, {
|
|
103
103
|
static: parts.staticHash,
|
|
104
104
|
key: parts.key,
|
|
105
105
|
files: parts.sources.map(sourceEntry),
|
|
106
|
-
})
|
|
107
|
-
persist(cache)
|
|
108
|
-
return parts.key
|
|
106
|
+
})
|
|
107
|
+
persist(cache)
|
|
108
|
+
return parts.key
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
function sourceEntry(source: ClientCacheSource): SourceEntry {
|
|
112
|
-
return [source.file, source.mtimeMs, source.size, source.hash]
|
|
112
|
+
return [source.file, source.mtimeMs, source.size, source.hash]
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
@@ -118,61 +118,61 @@ function sourceEntry(source: ClientCacheSource): SourceEntry {
|
|
|
118
118
|
* walk has to run again.
|
|
119
119
|
*/
|
|
120
120
|
async function revalidate(files: SourceEntry[]): Promise<SourceEntry[] | undefined> {
|
|
121
|
-
let moved = false
|
|
121
|
+
let moved = false
|
|
122
122
|
const checked = await Promise.all(
|
|
123
123
|
files.map(async (entry): Promise<SourceEntry | undefined> => {
|
|
124
|
-
const [file, mtimeMs, size, hash] = entry
|
|
125
|
-
const stats = await stat(file).catch(() => undefined)
|
|
126
|
-
if (!stats) return undefined
|
|
127
|
-
if (stats.mtimeMs === mtimeMs && stats.size === size) return entry
|
|
128
|
-
const source = await readFile(file, 'utf8').catch(() => undefined)
|
|
129
|
-
if (source === undefined || clientSourceHash(source) !== hash) return undefined
|
|
130
|
-
moved = true
|
|
131
|
-
return [file, stats.mtimeMs, stats.size, hash]
|
|
124
|
+
const [file, mtimeMs, size, hash] = entry
|
|
125
|
+
const stats = await stat(file).catch(() => undefined)
|
|
126
|
+
if (!stats) return undefined
|
|
127
|
+
if (stats.mtimeMs === mtimeMs && stats.size === size) return entry
|
|
128
|
+
const source = await readFile(file, 'utf8').catch(() => undefined)
|
|
129
|
+
if (source === undefined || clientSourceHash(source) !== hash) return undefined
|
|
130
|
+
moved = true
|
|
131
|
+
return [file, stats.mtimeMs, stats.size, hash]
|
|
132
132
|
}),
|
|
133
|
-
)
|
|
134
|
-
if (checked.some(entry => entry === undefined)) return undefined
|
|
135
|
-
return moved ? (checked as SourceEntry[]) : files
|
|
133
|
+
)
|
|
134
|
+
if (checked.some(entry => entry === undefined)) return undefined
|
|
135
|
+
return moved ? (checked as SourceEntry[]) : files
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
function persist(cache: KeyCache) {
|
|
139
|
-
cache.dirty = true
|
|
140
|
-
if (cache.flush) return
|
|
139
|
+
cache.dirty = true
|
|
140
|
+
if (cache.flush) return
|
|
141
141
|
cache.flush = setTimeout(async () => {
|
|
142
|
-
cache.flush = undefined
|
|
143
|
-
if (!cache.dirty) return
|
|
144
|
-
const contents = serializeIndex(cache.routes)
|
|
142
|
+
cache.flush = undefined
|
|
143
|
+
if (!cache.dirty) return
|
|
144
|
+
const contents = serializeIndex(cache.routes)
|
|
145
145
|
try {
|
|
146
|
-
await mkdir(path.dirname(cache.file), { recursive: true })
|
|
147
|
-
await writeFileAtomic(cache.file, contents)
|
|
148
|
-
cache.dirty = false
|
|
146
|
+
await mkdir(path.dirname(cache.file), { recursive: true })
|
|
147
|
+
await writeFileAtomic(cache.file, contents)
|
|
148
|
+
cache.dirty = false
|
|
149
149
|
} catch {
|
|
150
150
|
// A write that lost a race with a cache wipe must not look persisted.
|
|
151
|
-
if (++cache.retries < 3) persist(cache)
|
|
151
|
+
if (++cache.retries < 3) persist(cache)
|
|
152
152
|
}
|
|
153
|
-
}, 50)
|
|
153
|
+
}, 50)
|
|
154
154
|
// The index is a pure optimization; never hold the process open for it.
|
|
155
|
-
cache.flush.unref?.()
|
|
155
|
+
cache.flush.unref?.()
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
function readIndex(file: string) {
|
|
159
|
-
const routes = new Map<string, RouteEntry>()
|
|
160
|
-
let parsed: IndexFile | undefined
|
|
159
|
+
const routes = new Map<string, RouteEntry>()
|
|
160
|
+
let parsed: IndexFile | undefined
|
|
161
161
|
try {
|
|
162
|
-
parsed = JSON.parse(readFileSync(file, 'utf8')) as IndexFile
|
|
162
|
+
parsed = JSON.parse(readFileSync(file, 'utf8')) as IndexFile
|
|
163
163
|
} catch {
|
|
164
|
-
return routes
|
|
164
|
+
return routes
|
|
165
165
|
}
|
|
166
|
-
if (parsed?.version !== INDEX_VERSION) return routes
|
|
166
|
+
if (parsed?.version !== INDEX_VERSION) return routes
|
|
167
167
|
for (const [id, entry] of Object.entries(parsed.routes ?? {})) {
|
|
168
|
-
if (entry?.key && entry.static && Array.isArray(entry.files)) routes.set(id, entry)
|
|
168
|
+
if (entry?.key && entry.static && Array.isArray(entry.files)) routes.set(id, entry)
|
|
169
169
|
}
|
|
170
|
-
return routes
|
|
170
|
+
return routes
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
function serializeIndex(routes: Map<string, RouteEntry>) {
|
|
174
174
|
return JSON.stringify({
|
|
175
175
|
version: INDEX_VERSION,
|
|
176
176
|
routes: Object.fromEntries(routes),
|
|
177
|
-
} satisfies IndexFile)
|
|
177
|
+
} satisfies IndexFile)
|
|
178
178
|
}
|
|
@@ -4,34 +4,34 @@
|
|
|
4
4
|
// first page: the warm tier builds global.css and the render asks `globalCssHref` whether there is
|
|
5
5
|
// any.
|
|
6
6
|
//
|
|
7
|
-
// The same three-step ladder route-facts
|
|
7
|
+
// The same three-step ladder restart/route-facts.ts uses:
|
|
8
8
|
// stat matches -> reuse (the common case, and the only O(1) one)
|
|
9
9
|
// stat moved -> read + hash; equal content still reuses the answer
|
|
10
10
|
// content differs -> discard, walk again, record the new answer
|
|
11
11
|
// Paths the walk found ABSENT are re-checked too: a stylesheet restored while the server was not
|
|
12
12
|
// running adds an edge that no importer's bytes record.
|
|
13
|
-
import { mkdirSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
14
|
-
import path from 'node:path'
|
|
15
|
-
import type { ResolvedConfig } from '
|
|
16
|
-
import { setGlobalCssSourceStore, type GlobalCssSourceStore } from '
|
|
17
|
-
import { restartCacheEnabled } from './
|
|
13
|
+
import { mkdirSync, existsSync, readFileSync, statSync, writeFileSync } from 'node:fs'
|
|
14
|
+
import path from 'node:path'
|
|
15
|
+
import type { ResolvedConfig } from '../../config'
|
|
16
|
+
import { setGlobalCssSourceStore, type GlobalCssSourceStore } from '../../css/build'
|
|
17
|
+
import { restartCacheEnabled } from './enabled'
|
|
18
18
|
|
|
19
19
|
/** `[file, mtimeMs, size, contentHash]` — the shape the other restart caches use. */
|
|
20
|
-
type SourceEntry = [string, number, number, string]
|
|
20
|
+
type SourceEntry = [string, number, number, string]
|
|
21
21
|
|
|
22
22
|
interface RecordFile {
|
|
23
|
-
version: number
|
|
23
|
+
version: number
|
|
24
24
|
/** Everything the answer depends on that is not a walked source (see cacheKey). */
|
|
25
|
-
key: string
|
|
26
|
-
files: SourceEntry[]
|
|
27
|
-
missing: string[]
|
|
28
|
-
sources: string[]
|
|
25
|
+
key: string
|
|
26
|
+
files: SourceEntry[]
|
|
27
|
+
missing: string[]
|
|
28
|
+
sources: string[]
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
/** Bumped whenever a record's shape changes; an older record is simply ignored. */
|
|
32
|
-
const RECORD_VERSION = 1
|
|
32
|
+
const RECORD_VERSION = 1
|
|
33
33
|
|
|
34
|
-
const hash = (value: string) => Bun.hash(value).toString(36)
|
|
34
|
+
const hash = (value: string) => Bun.hash(value).toString(36)
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* The walk's answer can change without any app file moving, when the thing that
|
|
@@ -43,83 +43,91 @@ function cacheKey(config: ResolvedConfig): string | undefined {
|
|
|
43
43
|
try {
|
|
44
44
|
const shape = JSON.stringify(config, (_key, value: unknown) =>
|
|
45
45
|
typeof value === 'function' ? '[fn]' : value,
|
|
46
|
-
)
|
|
47
|
-
return hash(`${RECORD_VERSION}\n${pnextBuildStamp()}\n${shape}`)
|
|
46
|
+
)
|
|
47
|
+
return hash(`${RECORD_VERSION}\n${pnextBuildStamp()}\n${shape}`)
|
|
48
48
|
} catch {
|
|
49
|
-
return undefined
|
|
49
|
+
return undefined // a config that will not serialize simply opts out
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/** mtime+size of the walk this process is running (see route-facts
|
|
53
|
+
/** mtime+size of the walk this process is running (see restart/route-facts.ts). */
|
|
54
54
|
function pnextBuildStamp() {
|
|
55
55
|
try {
|
|
56
|
-
const self = Bun.fileURLToPath(import.meta.url)
|
|
57
|
-
const walk = path.join(path.dirname(path.dirname(self)), 'css', 'build.ts')
|
|
58
|
-
const info = statSync(walk, { throwIfNoEntry: false })
|
|
59
|
-
return info ? `${info.mtimeMs}:${info.size}` : self
|
|
56
|
+
const self = Bun.fileURLToPath(import.meta.url)
|
|
57
|
+
const walk = path.join(path.dirname(path.dirname(self)), 'css', 'build.ts')
|
|
58
|
+
const info = statSync(walk, { throwIfNoEntry: false })
|
|
59
|
+
return info ? `${info.mtimeMs}:${info.size}` : self
|
|
60
60
|
} catch {
|
|
61
|
-
return 'unknown'
|
|
61
|
+
return 'unknown'
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
export function installGlobalCssCache(config: ResolvedConfig) {
|
|
66
|
-
if (!restartCacheEnabled()) return
|
|
66
|
+
if (!restartCacheEnabled()) return
|
|
67
67
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
68
|
-
if (process.env.PNEXT_DEV_GLOBAL_CSS_CACHE === '0') return
|
|
69
|
-
const key = cacheKey(config)
|
|
70
|
-
if (!key) return
|
|
71
|
-
setGlobalCssSourceStore(createStore(path.join(config.outPath, 'cache', 'global-css'), key))
|
|
68
|
+
if (process.env.PNEXT_DEV_GLOBAL_CSS_CACHE === '0') return
|
|
69
|
+
const key = cacheKey(config)
|
|
70
|
+
if (!key) return
|
|
71
|
+
setGlobalCssSourceStore(createStore(path.join(config.outPath, 'cache', 'global-css'), key))
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
function createStore(dir: string, key: string): GlobalCssSourceStore {
|
|
75
75
|
// Last payload written per walk, so a save that re-runs the walk with an
|
|
76
76
|
// unchanged answer costs no write, and one with a changed answer does.
|
|
77
|
-
const written = new Map<string, string>()
|
|
78
|
-
const recordFile = (walkKey: string) => path.join(dir, `${hash(walkKey)}.json`)
|
|
77
|
+
const written = new Map<string, string>()
|
|
78
|
+
const recordFile = (walkKey: string) => path.join(dir, `${hash(walkKey)}.json`)
|
|
79
79
|
|
|
80
80
|
return {
|
|
81
81
|
load(walkKey) {
|
|
82
|
-
const record = readRecord(recordFile(walkKey), key)
|
|
82
|
+
const record = readRecord(recordFile(walkKey), key)
|
|
83
83
|
if (!record) {
|
|
84
|
-
stats.misses += 1
|
|
85
|
-
return undefined
|
|
84
|
+
stats.misses += 1
|
|
85
|
+
return undefined
|
|
86
86
|
}
|
|
87
87
|
if (record.missing.some(file => existsSync(file))) {
|
|
88
|
-
stats.misses += 1
|
|
89
|
-
return undefined
|
|
88
|
+
stats.misses += 1
|
|
89
|
+
return undefined
|
|
90
90
|
}
|
|
91
|
-
const revalidated = revalidate(record.files)
|
|
91
|
+
const revalidated = revalidate(record.files)
|
|
92
92
|
if (!revalidated) {
|
|
93
|
-
stats.misses += 1
|
|
94
|
-
return undefined
|
|
93
|
+
stats.misses += 1
|
|
94
|
+
return undefined
|
|
95
95
|
}
|
|
96
|
-
stats.hits += 1
|
|
96
|
+
stats.hits += 1
|
|
97
97
|
// Only the stats moved (a touch, a checkout): keep the answer, record the
|
|
98
98
|
// new stats so the next boot takes the O(1) path again.
|
|
99
99
|
if (revalidated !== record.files) {
|
|
100
|
-
write(
|
|
100
|
+
write(
|
|
101
|
+
recordFile(walkKey),
|
|
102
|
+
key,
|
|
103
|
+
written,
|
|
104
|
+
walkKey,
|
|
105
|
+
revalidated,
|
|
106
|
+
record.missing,
|
|
107
|
+
record.sources,
|
|
108
|
+
)
|
|
101
109
|
}
|
|
102
|
-
return record.sources
|
|
110
|
+
return record.sources
|
|
103
111
|
},
|
|
104
112
|
save(walkKey, sources, visited, missing) {
|
|
105
|
-
const files = sourceEntries(visited)
|
|
106
|
-
if (!files) return
|
|
107
|
-
write(recordFile(walkKey), key, written, walkKey, files, missing, sources)
|
|
113
|
+
const files = sourceEntries(visited)
|
|
114
|
+
if (!files) return
|
|
115
|
+
write(recordFile(walkKey), key, written, walkKey, files, missing, sources)
|
|
108
116
|
},
|
|
109
|
-
}
|
|
117
|
+
}
|
|
110
118
|
}
|
|
111
119
|
|
|
112
120
|
function readRecord(file: string, key: string): RecordFile | undefined {
|
|
113
|
-
let parsed: RecordFile | undefined
|
|
121
|
+
let parsed: RecordFile | undefined
|
|
114
122
|
try {
|
|
115
|
-
parsed = JSON.parse(readFileSync(file, 'utf8')) as RecordFile
|
|
123
|
+
parsed = JSON.parse(readFileSync(file, 'utf8')) as RecordFile
|
|
116
124
|
} catch {
|
|
117
|
-
return undefined
|
|
125
|
+
return undefined
|
|
118
126
|
}
|
|
119
|
-
if (parsed?.version !== RECORD_VERSION || parsed.key !== key) return undefined
|
|
120
|
-
if (!Array.isArray(parsed.files) || !Array.isArray(parsed.sources)) return undefined
|
|
121
|
-
if (!Array.isArray(parsed.missing)) return undefined
|
|
122
|
-
return parsed
|
|
127
|
+
if (parsed?.version !== RECORD_VERSION || parsed.key !== key) return undefined
|
|
128
|
+
if (!Array.isArray(parsed.files) || !Array.isArray(parsed.sources)) return undefined
|
|
129
|
+
if (!Array.isArray(parsed.missing)) return undefined
|
|
130
|
+
return parsed
|
|
123
131
|
}
|
|
124
132
|
|
|
125
133
|
/**
|
|
@@ -141,37 +149,37 @@ function write(
|
|
|
141
149
|
files,
|
|
142
150
|
missing,
|
|
143
151
|
sources,
|
|
144
|
-
} satisfies RecordFile)
|
|
145
|
-
if (written.get(walkKey) === contents) return
|
|
146
|
-
written.set(walkKey, contents)
|
|
152
|
+
} satisfies RecordFile)
|
|
153
|
+
if (written.get(walkKey) === contents) return
|
|
154
|
+
written.set(walkKey, contents)
|
|
147
155
|
try {
|
|
148
|
-
mkdirSync(path.dirname(file), { recursive: true })
|
|
149
|
-
writeFileSync(file, contents)
|
|
156
|
+
mkdirSync(path.dirname(file), { recursive: true })
|
|
157
|
+
writeFileSync(file, contents)
|
|
150
158
|
} catch {
|
|
151
159
|
// Best effort: a missing record only costs the next boot a walk.
|
|
152
160
|
}
|
|
153
161
|
}
|
|
154
162
|
|
|
155
163
|
function sourceEntries(files: string[]): SourceEntry[] | undefined {
|
|
156
|
-
const entries: SourceEntry[] = []
|
|
164
|
+
const entries: SourceEntry[] = []
|
|
157
165
|
for (const file of files) {
|
|
158
|
-
const entry = sourceEntry(file)
|
|
159
|
-
if (!entry) return undefined
|
|
160
|
-
entries.push(entry)
|
|
166
|
+
const entry = sourceEntry(file)
|
|
167
|
+
if (!entry) return undefined
|
|
168
|
+
entries.push(entry)
|
|
161
169
|
}
|
|
162
|
-
return entries
|
|
170
|
+
return entries
|
|
163
171
|
}
|
|
164
172
|
|
|
165
173
|
function sourceEntry(file: string): SourceEntry | undefined {
|
|
166
174
|
try {
|
|
167
|
-
const source = readFileSync(file, 'utf8')
|
|
175
|
+
const source = readFileSync(file, 'utf8')
|
|
168
176
|
// Stat AFTER the read: a file that changes between the two records the
|
|
169
177
|
// older stat, so the next boot re-hashes rather than trusting a stale one.
|
|
170
|
-
const info = statSync(file, { throwIfNoEntry: false })
|
|
171
|
-
if (!info) return undefined
|
|
172
|
-
return [file, info.mtimeMs, info.size, hash(source)]
|
|
178
|
+
const info = statSync(file, { throwIfNoEntry: false })
|
|
179
|
+
if (!info) return undefined
|
|
180
|
+
return [file, info.mtimeMs, info.size, hash(source)]
|
|
173
181
|
} catch {
|
|
174
|
-
return undefined
|
|
182
|
+
return undefined
|
|
175
183
|
}
|
|
176
184
|
}
|
|
177
185
|
|
|
@@ -181,32 +189,32 @@ function sourceEntry(file: string): SourceEntry | undefined {
|
|
|
181
189
|
* walk has to run again.
|
|
182
190
|
*/
|
|
183
191
|
function revalidate(files: SourceEntry[]): SourceEntry[] | undefined {
|
|
184
|
-
let moved = false
|
|
185
|
-
const checked: SourceEntry[] = []
|
|
192
|
+
let moved = false
|
|
193
|
+
const checked: SourceEntry[] = []
|
|
186
194
|
for (const entry of files) {
|
|
187
|
-
const [file, mtimeMs, size, contentHash] = entry
|
|
188
|
-
const info = statSync(file, { throwIfNoEntry: false })
|
|
189
|
-
if (!info) return undefined
|
|
195
|
+
const [file, mtimeMs, size, contentHash] = entry
|
|
196
|
+
const info = statSync(file, { throwIfNoEntry: false })
|
|
197
|
+
if (!info) return undefined
|
|
190
198
|
if (info.mtimeMs === mtimeMs && info.size === size) {
|
|
191
|
-
checked.push(entry)
|
|
192
|
-
continue
|
|
199
|
+
checked.push(entry)
|
|
200
|
+
continue
|
|
193
201
|
}
|
|
194
|
-
let source: string
|
|
202
|
+
let source: string
|
|
195
203
|
try {
|
|
196
|
-
source = readFileSync(file, 'utf8')
|
|
204
|
+
source = readFileSync(file, 'utf8')
|
|
197
205
|
} catch {
|
|
198
|
-
return undefined
|
|
206
|
+
return undefined
|
|
199
207
|
}
|
|
200
|
-
if (hash(source) !== contentHash) return undefined
|
|
201
|
-
moved = true
|
|
202
|
-
checked.push([file, info.mtimeMs, info.size, contentHash])
|
|
208
|
+
if (hash(source) !== contentHash) return undefined
|
|
209
|
+
moved = true
|
|
210
|
+
checked.push([file, info.mtimeMs, info.size, contentHash])
|
|
203
211
|
}
|
|
204
|
-
return moved ? checked : files
|
|
212
|
+
return moved ? checked : files
|
|
205
213
|
}
|
|
206
214
|
|
|
207
215
|
/** Bisect seam: `PNEXT_DEV_GLOBAL_CSS_STATS=1` prints the hit/miss split at exit. */
|
|
208
|
-
const stats = { hits: 0, misses: 0 }
|
|
216
|
+
const stats = { hits: 0, misses: 0 }
|
|
209
217
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
210
218
|
if (process.env.PNEXT_DEV_GLOBAL_CSS_STATS === '1') {
|
|
211
|
-
process.on('exit', () => console.log(`global-css hits=${stats.hits} misses=${stats.misses}`))
|
|
219
|
+
process.on('exit', () => console.log(`global-css hits=${stats.hits} misses=${stats.misses}`))
|
|
212
220
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// Persisted node_modules bundles - the restart half of runtime/
|
|
1
|
+
// Persisted node_modules bundles - the restart half of runtime/loader.ts' `transformNodeModuleSource`.
|
|
2
2
|
// The node_modules load plugin esbuild-bundles each ESM package file it matches and remembers the
|
|
3
3
|
// result in memory only, so every restart re-runs those builds on the request path while the route
|
|
4
4
|
// bundle it is evaluating waits.
|
|
@@ -8,37 +8,37 @@
|
|
|
8
8
|
// Anything else - a missing input, an unparseable record, a vanished file - is a miss, never a
|
|
9
9
|
// stale hit: the bundle it names is imported directly, and Bun caches a bad import for the life of
|
|
10
10
|
// the process.
|
|
11
|
-
import { mkdir, readFile, stat } from 'node:fs/promises'
|
|
12
|
-
import path from 'node:path'
|
|
13
|
-
import { writeFileAtomic } from '
|
|
11
|
+
import { mkdir, readFile, stat } from 'node:fs/promises'
|
|
12
|
+
import path from 'node:path'
|
|
13
|
+
import { writeFileAtomic } from '../../utils/fs'
|
|
14
14
|
|
|
15
15
|
/** `[file, mtimeMs, size]` for one esbuild input of the bundle. */
|
|
16
|
-
type InputEntry = [string, number, number]
|
|
16
|
+
type InputEntry = [string, number, number]
|
|
17
17
|
|
|
18
18
|
interface BundleRecord {
|
|
19
|
-
inputs: InputEntry[]
|
|
20
|
-
code: string
|
|
19
|
+
inputs: InputEntry[]
|
|
20
|
+
code: string
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/** The recorded bundle when every input it was built from is unchanged. */
|
|
24
24
|
export async function readNodeModuleBundle(recordFile: string): Promise<string | undefined> {
|
|
25
|
-
const contents = await readFile(recordFile, 'utf8').catch(() => undefined)
|
|
26
|
-
if (contents === undefined) return undefined
|
|
27
|
-
let record: BundleRecord | undefined
|
|
25
|
+
const contents = await readFile(recordFile, 'utf8').catch(() => undefined)
|
|
26
|
+
if (contents === undefined) return undefined
|
|
27
|
+
let record: BundleRecord | undefined
|
|
28
28
|
try {
|
|
29
|
-
record = JSON.parse(contents) as BundleRecord
|
|
29
|
+
record = JSON.parse(contents) as BundleRecord
|
|
30
30
|
} catch {
|
|
31
|
-
return undefined
|
|
31
|
+
return undefined
|
|
32
32
|
}
|
|
33
|
-
if (typeof record?.code !== 'string' || !Array.isArray(record.inputs)) return undefined
|
|
34
|
-
if (record.inputs.length === 0) return undefined
|
|
33
|
+
if (typeof record?.code !== 'string' || !Array.isArray(record.inputs)) return undefined
|
|
34
|
+
if (record.inputs.length === 0) return undefined
|
|
35
35
|
const held = await Promise.all(
|
|
36
36
|
record.inputs.map(async ([input, mtimeMs, size]) => {
|
|
37
|
-
const stats = await stat(input).catch(() => undefined)
|
|
38
|
-
return stats?.mtimeMs === mtimeMs && stats.size === size
|
|
37
|
+
const stats = await stat(input).catch(() => undefined)
|
|
38
|
+
return stats?.mtimeMs === mtimeMs && stats.size === size
|
|
39
39
|
}),
|
|
40
|
-
)
|
|
41
|
-
return held.every(Boolean) ? record.code : undefined
|
|
40
|
+
)
|
|
41
|
+
return held.every(Boolean) ? record.code : undefined
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
export async function writeNodeModuleBundle(
|
|
@@ -48,16 +48,16 @@ export async function writeNodeModuleBundle(
|
|
|
48
48
|
): Promise<void> {
|
|
49
49
|
const stated = await Promise.all(
|
|
50
50
|
inputs.map(async (input): Promise<InputEntry | undefined> => {
|
|
51
|
-
const stats = await stat(input).catch(() => undefined)
|
|
52
|
-
return stats ? [input, stats.mtimeMs, stats.size] : undefined
|
|
51
|
+
const stats = await stat(input).catch(() => undefined)
|
|
52
|
+
return stats ? [input, stats.mtimeMs, stats.size] : undefined
|
|
53
53
|
}),
|
|
54
|
-
)
|
|
54
|
+
)
|
|
55
55
|
// An input that vanished between the build and here would make the record
|
|
56
56
|
// unvalidatable forever; leaving it unwritten costs the next boot one bundle.
|
|
57
|
-
if (stated.length === 0 || stated.some(entry => entry === undefined)) return
|
|
58
|
-
await mkdir(path.dirname(recordFile), { recursive: true }).catch(() => undefined)
|
|
57
|
+
if (stated.length === 0 || stated.some(entry => entry === undefined)) return
|
|
58
|
+
await mkdir(path.dirname(recordFile), { recursive: true }).catch(() => undefined)
|
|
59
59
|
await writeFileAtomic(
|
|
60
60
|
recordFile,
|
|
61
61
|
JSON.stringify({ inputs: stated as InputEntry[], code } satisfies BundleRecord),
|
|
62
|
-
).catch(() => undefined)
|
|
62
|
+
).catch(() => undefined)
|
|
63
63
|
}
|