@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,65 +1,54 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Vendor package bundling: compiling a server module's node_modules externals into artifacts
|
|
3
|
+
* loadable at request/build time. Covers the vendor artifact plan and cache-key scheme, the
|
|
4
|
+
* content/link/share layers used to reuse compiled bytes across the RSC and client-compat
|
|
5
|
+
* targets, preplan and native-Bun-loadable fast paths for packages that need no rewriting, the
|
|
6
|
+
* plugin-driven esbuild bundling of a vendor group (and its CommonJS-export-recovery/native
|
|
7
|
+
* fallback rounds), and the resolve-time plugins (external dependency, package-shape) that feed
|
|
8
|
+
* those builds. The demand-scheduling pipeline itself lives in `./vendor`; the ESM loader - and
|
|
9
|
+
* the alias/asset helpers this module reuses from it - lives in `./server`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { builtinModules, createRequire } from 'node:module'
|
|
13
|
+
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
14
|
+
import { copyFile, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'
|
|
15
|
+
import path from 'node:path'
|
|
16
|
+
import { fileURLToPath } from 'node:url'
|
|
17
|
+
import type { Loader, Metafile, OnLoadResult, OnResolveResult, Plugin } from 'esbuild'
|
|
18
|
+
import { build } from '../utils/esbuild'
|
|
9
19
|
import {
|
|
10
20
|
applyBundledSourceTransforms,
|
|
11
|
-
applyServerSourcePreTransforms,
|
|
12
|
-
applyServerSourceTransforms,
|
|
13
|
-
extraLoadableExtensions,
|
|
14
21
|
getAssetExtensions,
|
|
15
22
|
getBundlerExtensions,
|
|
16
23
|
getCssExtensions,
|
|
17
24
|
getImportAliasExtensions,
|
|
18
|
-
onLoadableExtensionsChanged,
|
|
19
25
|
serverDefineOptions,
|
|
20
|
-
sourceNeedsServerTransforms,
|
|
21
26
|
type ServerEsbuildPluginOptions,
|
|
22
|
-
} from '../extensions'
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
pathToFileHref,
|
|
26
|
-
pnextAliases,
|
|
27
|
-
type CompatAliasTarget,
|
|
28
|
-
type ResolvedConfig,
|
|
29
|
-
} from '../config';
|
|
30
|
-
import {
|
|
31
|
-
rewriteDynamicCallTargets,
|
|
32
|
-
rewriteLiteralDynamicCalls,
|
|
33
|
-
sourceHasDynamicImport,
|
|
34
|
-
} from '../dynamic/source';
|
|
35
|
-
import { cacheRoot, devArtifactUsable, noteDevArtifactWritten } from '../dev/module-cache';
|
|
36
|
-
import { readNodeModuleBundle, writeNodeModuleBundle } from '../dev/node-module-bundle-cache';
|
|
37
|
-
import { setBasePathPrefix, setTrailingSlashUrls } from '../routing/href';
|
|
27
|
+
} from '../extensions'
|
|
28
|
+
import { pathToFileHref, type CompatAliasTarget, type ResolvedConfig } from '../config'
|
|
29
|
+
import { devArtifactUsable, noteDevArtifactWritten } from './module-cache'
|
|
38
30
|
import {
|
|
39
31
|
getExternalPackagePolicy,
|
|
40
32
|
isEsmModuleEntry,
|
|
41
33
|
isEsmModuleFile,
|
|
42
34
|
isPackageSubpathUnexported,
|
|
43
|
-
resolveExternalLoadTarget,
|
|
44
|
-
resolveImport,
|
|
45
35
|
resolveLinkedPackageSpecifier,
|
|
46
36
|
resolveNestedPackageFromImporter,
|
|
47
37
|
resolveVendorPackageSpecifier,
|
|
48
38
|
clearVendorPackageResolutions,
|
|
49
39
|
clearProvidedEntryResolutions,
|
|
50
40
|
provideEntryResolution,
|
|
51
|
-
|
|
52
|
-
} from '../resolve/
|
|
53
|
-
import {
|
|
54
|
-
import { cachedExistsSync, clearNodeModulesFsCache } from '../utils/fs-cache';
|
|
41
|
+
} from '../resolve/imports'
|
|
42
|
+
import { clearNodeModulesResolutionCache } from '../resolve/engine'
|
|
43
|
+
import { cachedExistsSync, clearNodeModulesFsCache } from '../utils/fs-cache'
|
|
55
44
|
import {
|
|
56
45
|
clientReferenceExportNames,
|
|
57
46
|
clientReferenceModuleSource,
|
|
58
47
|
hasUseClientDirective,
|
|
59
|
-
} from '../client/reference-stub'
|
|
60
|
-
import { reactCompatEnabled } from '../render/hooks'
|
|
61
|
-
import {
|
|
62
|
-
import {
|
|
48
|
+
} from '../client/reference-stub'
|
|
49
|
+
import { reactCompatEnabled } from '../render/hooks'
|
|
50
|
+
import { writeFileAtomic } from '../utils/fs'
|
|
51
|
+
import { formatDuration } from '../utils/verbose'
|
|
63
52
|
import {
|
|
64
53
|
addCommonJsNamedExports,
|
|
65
54
|
canonicalVendorCode,
|
|
@@ -84,652 +73,44 @@ import {
|
|
|
84
73
|
type VendorBuildPlan,
|
|
85
74
|
type VendorGroupMember,
|
|
86
75
|
type VendorGroupPlan,
|
|
87
|
-
} from './vendor'
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/** Install compat-driven extra vendor-bundle conditions (B5 `next-js`). */
|
|
107
|
-
export function setServerBundleConditions(
|
|
108
|
-
factory: ((target: ServerBundleTarget) => string[]) | undefined,
|
|
109
|
-
): void {
|
|
110
|
-
extraServerBundleConditions = factory ?? (() => []);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function serverBundleTargetForRuntime(runtime: string | undefined): ServerBundleTarget {
|
|
114
|
-
return runtime === 'edge' || runtime === 'experimental-edge' ? 'edge' : 'server';
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export function pagesApiBundleTargetForRuntime(runtime: string | undefined): ServerBundleTarget {
|
|
118
|
-
return runtime === 'edge' || runtime === 'experimental-edge' ? 'pages-api-edge' : 'pages-api';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Pages-router PAGE SSR layer: Next compiles it in the server bundle with `node` (or
|
|
123
|
-
* `edge-light`/`browser` for edge) export conditions but WITHOUT `react-server` - a pages page is
|
|
124
|
-
* not part of the RSC layer, so `library/react`-style react-server splits must resolve to `default`.
|
|
125
|
-
*/
|
|
126
|
-
export function pagesBundleTargetForRuntime(runtime: string | undefined): ServerBundleTarget {
|
|
127
|
-
return runtime === 'edge' || runtime === 'experimental-edge' ? 'pages-edge' : 'pages';
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
function serverBundleConditions(target: ServerBundleTarget): string[] {
|
|
131
|
-
// Preserve the original client-target conditions (module/import) to avoid
|
|
132
|
-
// regressing the browser SSR vendor bundle; only the RSC/server layer gains
|
|
133
|
-
// `react-server`. Compat-added conditions (next-js) prepend for either target.
|
|
134
|
-
const base =
|
|
135
|
-
target === 'client'
|
|
136
|
-
? ['module', 'import']
|
|
137
|
-
: target === 'edge'
|
|
138
|
-
? ['react-server', 'edge-light', 'browser', 'module', 'import']
|
|
139
|
-
: target === 'pages-api-edge'
|
|
140
|
-
? ['edge-light', 'browser', 'module', 'import']
|
|
141
|
-
: target === 'pages-api'
|
|
142
|
-
? ['node', 'require', 'import']
|
|
143
|
-
: target === 'pages-edge'
|
|
144
|
-
? ['edge-light', 'browser', 'module', 'import']
|
|
145
|
-
: target === 'pages'
|
|
146
|
-
? ['node', 'module', 'import']
|
|
147
|
-
: ['react-server', 'node', 'module', 'import'];
|
|
148
|
-
return [...new Set([...extraServerBundleConditions(target), ...base])];
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function serverBundleRequireConditions(target: ServerBundleTarget): string[] {
|
|
152
|
-
const base =
|
|
153
|
-
target === 'client'
|
|
154
|
-
? ['module', 'require']
|
|
155
|
-
: target === 'edge'
|
|
156
|
-
? ['react-server', 'edge-light', 'browser', 'module', 'require']
|
|
157
|
-
: target === 'pages-api-edge'
|
|
158
|
-
? ['edge-light', 'browser', 'module', 'require']
|
|
159
|
-
: target === 'pages-api'
|
|
160
|
-
? ['node', 'require']
|
|
161
|
-
: target === 'pages-edge'
|
|
162
|
-
? ['edge-light', 'browser', 'module', 'require']
|
|
163
|
-
: target === 'pages'
|
|
164
|
-
? ['node', 'module', 'require']
|
|
165
|
-
: ['react-server', 'node', 'module', 'require'];
|
|
166
|
-
return [...new Set([...extraServerBundleConditions(target), ...base])];
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const runtimeConfigs = new Map<string, RuntimeConfig>();
|
|
170
|
-
const aliasCache = new WeakMap<ResolvedConfig, Map<string, string>>();
|
|
171
|
-
const packageRootCache = new WeakMap<ResolvedConfig, string[]>();
|
|
172
|
-
const registeredSignatures = new WeakMap<ResolvedConfig, Set<string>>();
|
|
173
|
-
const rootPathCache = new Map<string, string[]>();
|
|
174
|
-
const runtimeConfigLookupCache = new Map<string, RuntimeConfig | null>();
|
|
175
|
-
const firstAliasCache = new Map<string, string | null>();
|
|
176
|
-
const transformCache = new Map<string, TransformCacheEntry>();
|
|
177
|
-
const nodeModuleTransformCache = new Map<string, TransformCacheEntry>();
|
|
178
|
-
// Freshness of generated `.pnext-require.cjs` sidecars (see inlineModuleScopeRequires).
|
|
179
|
-
const requireSidecarCache = new Map<string, { mtimeMs: number; size: number }>();
|
|
180
|
-
const registeredLoadRoots = new Set<string>();
|
|
181
|
-
let resolveRegistered = false;
|
|
182
|
-
let nodeModulesLoadRegistered = false;
|
|
183
|
-
// A live Bun.plugin forces every later import through the plugin pipeline (and
|
|
184
|
-
// pnext's own src through transformSource), so importing the compat graph with
|
|
185
|
-
// the plugins already up costs ~5x. build/start load compat before they ever
|
|
186
|
-
// register a runtime; dev arms the plugins on the first request instead.
|
|
187
|
-
let pluginsDeferred = false;
|
|
188
|
-
const deferredPluginRoots = new Set<string>();
|
|
189
|
-
|
|
190
|
-
const packageJsxLoaders = {
|
|
191
|
-
'.js': 'jsx',
|
|
192
|
-
'.mjs': 'jsx',
|
|
193
|
-
'.cjs': 'jsx',
|
|
194
|
-
} satisfies Record<string, Loader>;
|
|
195
|
-
|
|
196
|
-
interface RuntimeConfig {
|
|
197
|
-
root: string;
|
|
198
|
-
roots: Set<string>;
|
|
199
|
-
aliases: Map<string, string>;
|
|
200
|
-
/** Full resolved config, so compile sites can invoke config-aware extensions. */
|
|
201
|
-
resolved: ResolvedConfig;
|
|
202
|
-
missingImportError: (specifier: string) => string | undefined;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
interface TransformCacheEntry {
|
|
206
|
-
mtimeMs: number;
|
|
207
|
-
size: number;
|
|
208
|
-
code: string;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
export function registerServerRuntime(config: ResolvedConfig, sourceFiles: string[] = []) {
|
|
212
|
-
// Server-rendered Links emit canonical (slashed) hrefs under trailingSlash.
|
|
213
|
-
setTrailingSlashUrls(Boolean(config.trailingSlash));
|
|
214
|
-
// File-convention metadata asset hrefs (og-image, manifest) carry the
|
|
215
|
-
// basePath prefix; core render reads it through the href seam.
|
|
216
|
-
setBasePathPrefix(typeof config.basePath === 'string' ? config.basePath : '');
|
|
217
|
-
if (typeof Bun === 'undefined') return;
|
|
218
|
-
const sourceRoots = [...new Set(sourceFiles.map(file => sourceRootForFile(config, file)))].sort();
|
|
219
|
-
const signature = sourceRoots.join('\0');
|
|
220
|
-
let registered = registeredSignatures.get(config);
|
|
221
|
-
if (!registered) {
|
|
222
|
-
registered = new Set();
|
|
223
|
-
registeredSignatures.set(config, registered);
|
|
224
|
-
}
|
|
225
|
-
if (registered.has(signature)) return;
|
|
226
|
-
registered.add(signature);
|
|
227
|
-
|
|
228
|
-
const aliases = aliasesForConfig(config);
|
|
229
|
-
const roots = [
|
|
230
|
-
...rootPaths(config.appPath),
|
|
231
|
-
...rootPaths(path.join(config.outPath, 'cache', 'server')),
|
|
232
|
-
// The pnext src root, so the framework's own source transforms too.
|
|
233
|
-
...rootPaths(path.join(import.meta.dirname, '..')),
|
|
234
|
-
...sourceRoots.flatMap(rootPaths),
|
|
235
|
-
];
|
|
236
|
-
const key = rootPaths(config.appPath).join('\0');
|
|
237
|
-
const missingImportError = (specifier: string) =>
|
|
238
|
-
getImportAliasExtensions().missingImportError(config, specifier);
|
|
239
|
-
const existing = runtimeConfigs.get(key);
|
|
240
|
-
if (existing) {
|
|
241
|
-
for (const root of roots) existing.roots.add(root);
|
|
242
|
-
for (const [specifier, target] of aliases) existing.aliases.set(specifier, target);
|
|
243
|
-
existing.resolved = config;
|
|
244
|
-
existing.missingImportError = missingImportError;
|
|
245
|
-
} else {
|
|
246
|
-
runtimeConfigs.set(key, {
|
|
247
|
-
root: config.root,
|
|
248
|
-
roots: new Set(roots),
|
|
249
|
-
aliases: new Map(aliases),
|
|
250
|
-
resolved: config,
|
|
251
|
-
missingImportError,
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
runtimeConfigLookupCache.clear();
|
|
255
|
-
firstAliasCache.clear();
|
|
256
|
-
if (pluginsDeferred) {
|
|
257
|
-
for (const root of roots) deferredPluginRoots.add(root);
|
|
258
|
-
return;
|
|
259
|
-
}
|
|
260
|
-
registerResolvePlugin();
|
|
261
|
-
registerNodeModulesLoadPlugin();
|
|
262
|
-
for (const root of roots) registerLoadPlugin(root);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Hold the Bun module plugins back until installServerRuntimePlugins() runs, so
|
|
267
|
-
* whatever the process still has to import of its own code loads natively.
|
|
268
|
-
*/
|
|
269
|
-
export function deferServerRuntimePlugins(): void {
|
|
270
|
-
pluginsDeferred = true;
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
/** Arm the plugins deferServerRuntimePlugins() held back. Idempotent, cheap. */
|
|
274
|
-
export function installServerRuntimePlugins(): void {
|
|
275
|
-
if (!pluginsDeferred) return;
|
|
276
|
-
pluginsDeferred = false;
|
|
277
|
-
registerResolvePlugin();
|
|
278
|
-
registerNodeModulesLoadPlugin();
|
|
279
|
-
for (const root of deferredPluginRoots) registerLoadPlugin(root);
|
|
280
|
-
deferredPluginRoots.clear();
|
|
281
|
-
}
|
|
76
|
+
} from './vendor'
|
|
77
|
+
import {
|
|
78
|
+
aliasSpecifierFilter,
|
|
79
|
+
coreAliases,
|
|
80
|
+
firstAliasForSpecifier,
|
|
81
|
+
hashBundleSpecifier,
|
|
82
|
+
packageJsxLoaders,
|
|
83
|
+
runtimeAliasBuildPlugin,
|
|
84
|
+
serverAssetPlugin,
|
|
85
|
+
serverBundleConditions,
|
|
86
|
+
serverBundleRequireConditions,
|
|
87
|
+
serverIgnoredAssetFilter,
|
|
88
|
+
serverRequireAlias,
|
|
89
|
+
vendorLoadsNatively,
|
|
90
|
+
type ServerBundleTarget,
|
|
91
|
+
} from './loader'
|
|
92
|
+
|
|
93
|
+
const require = createRequire(import.meta.url)
|
|
282
94
|
|
|
283
95
|
export function clearServerRuntimeCaches() {
|
|
284
|
-
clearVendorPipeline()
|
|
285
|
-
clearVendorNativeCaches()
|
|
286
|
-
vendorEntryResolutions.clear()
|
|
287
|
-
clearVendorPackageResolutions()
|
|
288
|
-
vendorLayerRecords.clear()
|
|
289
|
-
vendorSharedEntries.clear()
|
|
290
|
-
vendorSharedLoaded.clear()
|
|
291
|
-
vendorReuseVerdicts.clear()
|
|
292
|
-
vendorReuseLoaded.clear()
|
|
293
|
-
vendorReuseProbes.clear()
|
|
294
|
-
vendorLayerProbeMemo.clear()
|
|
96
|
+
clearVendorPipeline()
|
|
97
|
+
clearVendorNativeCaches()
|
|
98
|
+
vendorEntryResolutions.clear()
|
|
99
|
+
clearVendorPackageResolutions()
|
|
100
|
+
vendorLayerRecords.clear()
|
|
101
|
+
vendorSharedEntries.clear()
|
|
102
|
+
vendorSharedLoaded.clear()
|
|
103
|
+
vendorReuseVerdicts.clear()
|
|
104
|
+
vendorReuseLoaded.clear()
|
|
105
|
+
vendorReuseProbes.clear()
|
|
106
|
+
vendorLayerProbeMemo.clear()
|
|
295
107
|
// The marker memo tracks files under `.pnext`; a wipe takes them with it.
|
|
296
|
-
esmDistPackages.clear()
|
|
297
|
-
clearProvidedEntryResolutions()
|
|
298
|
-
clearNodeModulesResolutionCache()
|
|
299
|
-
clearNodeModulesFsCache()
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function resolveRuntimeTarget(target: string) {
|
|
303
|
-
return path.isAbsolute(target) ? target : require.resolve(target);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
function aliasesForConfig(config: ResolvedConfig) {
|
|
307
|
-
let aliases = aliasCache.get(config);
|
|
308
|
-
if (!aliases) {
|
|
309
|
-
aliases = new Map<string, string>();
|
|
310
|
-
for (const [specifier, target] of Object.entries(coreAliases(config, 'server'))) {
|
|
311
|
-
aliases.set(specifier, resolveRuntimeTarget(target));
|
|
312
|
-
}
|
|
313
|
-
aliasCache.set(config, aliases);
|
|
314
|
-
}
|
|
315
|
-
return aliases;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
function coreAliases(config: ResolvedConfig, target: CompatAliasTarget): Record<string, string> {
|
|
319
|
-
return {
|
|
320
|
-
...frameworkRuntimeAliasEntries(),
|
|
321
|
-
...pnextAliases(target),
|
|
322
|
-
...getImportAliasExtensions().aliases(config, target),
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
const aliasSpecifierFilter =
|
|
327
|
-
/^(?:@wular\/pnext\/cache|preact(?:\/|$)|react(?:-dom)?(?:\/|$)|react-compiler-runtime$|next(?:\/|$)|server-only$|client-only$|@vercel\/og$)/;
|
|
328
|
-
|
|
329
|
-
function registerResolvePlugin() {
|
|
330
|
-
if (resolveRegistered) return;
|
|
331
|
-
resolveRegistered = true;
|
|
332
|
-
Bun.plugin({
|
|
333
|
-
name: 'pnext-server-runtime-resolve',
|
|
334
|
-
setup(plugin) {
|
|
335
|
-
plugin.onResolve({ filter: aliasSpecifierFilter }, args => {
|
|
336
|
-
const runtime = runtimeConfigForFile(args.importer);
|
|
337
|
-
const resolved = runtime?.aliases.get(args.path) ?? firstAliasForSpecifier(args.path);
|
|
338
|
-
const target = resolved ? serverRequireAlias(args.path, resolved, args.kind) : undefined;
|
|
339
|
-
if (target) return { path: target };
|
|
340
|
-
const message = (runtime ?? firstRuntimeConfig())?.missingImportError(args.path);
|
|
341
|
-
if (message) throw new Error(message);
|
|
342
|
-
return undefined;
|
|
343
|
-
});
|
|
344
|
-
plugin.onResolve({ filter: /^[^./].*/ }, ({ path: specifier, importer }) => {
|
|
345
|
-
const runtime = runtimeConfigForFile(importer);
|
|
346
|
-
const target = runtime && resolveExternalLoadTarget({
|
|
347
|
-
root: runtime.root,
|
|
348
|
-
fromFile: importer,
|
|
349
|
-
specifier,
|
|
350
|
-
target: 'server',
|
|
351
|
-
});
|
|
352
|
-
if (!target) return undefined;
|
|
353
|
-
return { path: target };
|
|
354
|
-
});
|
|
355
|
-
},
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function registerLoadPlugin(root: string) {
|
|
360
|
-
if (registeredLoadRoots.has(root)) return;
|
|
361
|
-
registeredLoadRoots.add(root);
|
|
362
|
-
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
363
|
-
if (process.env.PNEXT_DEBUG_LOADPLUGIN) {
|
|
364
|
-
console.error(`[loadplugin] register root=${root} extras=${currentLoadExtras().join(',')}`);
|
|
365
|
-
}
|
|
366
|
-
loadPluginExtras.set(root, currentLoadExtras());
|
|
367
|
-
Bun.plugin({
|
|
368
|
-
name: `pnext-server-runtime-load-${hashRoot(root)}`,
|
|
369
|
-
setup(plugin) {
|
|
370
|
-
plugin.onLoad({ filter: rootFilter(root) }, async ({ path: file }) => ({
|
|
371
|
-
contents: await transformSource(file),
|
|
372
|
-
loader: 'js',
|
|
373
|
-
}));
|
|
374
|
-
},
|
|
375
|
-
});
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// The load plugin's filter regex latches the page extensions known at registration time. A root registered
|
|
379
|
-
// BEFORE compat adds `mdx`/`md` (boot order shifts under load) would let those modules fall through to Bun's
|
|
380
|
-
// file loader, whose default export is the file path and renders as a bogus JSX tag - so a late registration
|
|
381
|
-
// extends every latched root with an extras-only plugin.
|
|
382
|
-
const loadPluginExtras = new Map<string, string[]>();
|
|
383
|
-
|
|
384
|
-
function currentLoadExtras(): string[] {
|
|
385
|
-
return extraLoadableExtensions().filter(ext => /^[a-z0-9]+$/i.test(ext));
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
onLoadableExtensionsChanged(() => {
|
|
389
|
-
for (const [root, seen] of loadPluginExtras) {
|
|
390
|
-
const fresh = currentLoadExtras().filter(ext => !seen.includes(ext));
|
|
391
|
-
if (fresh.length === 0) continue;
|
|
392
|
-
loadPluginExtras.set(root, [...seen, ...fresh]);
|
|
393
|
-
const exts = fresh.map(escapeRegex).join('|');
|
|
394
|
-
const filter = new RegExp(
|
|
395
|
-
`^${escapeRegex(root)}(?!.*\\/node_modules\\/)(?:/.*)?\\.(?:${exts})$`,
|
|
396
|
-
);
|
|
397
|
-
Bun.plugin({
|
|
398
|
-
name: `pnext-server-runtime-load-extras-${hashRoot(root)}-${fresh.join('-')}`,
|
|
399
|
-
setup(plugin) {
|
|
400
|
-
plugin.onLoad({ filter }, async ({ path: file }) => ({
|
|
401
|
-
contents: await transformSource(file),
|
|
402
|
-
loader: 'js',
|
|
403
|
-
}));
|
|
404
|
-
},
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
function registerNodeModulesLoadPlugin() {
|
|
410
|
-
if (nodeModulesLoadRegistered) return;
|
|
411
|
-
nodeModulesLoadRegistered = true;
|
|
412
|
-
Bun.plugin({
|
|
413
|
-
name: 'pnext-server-node-modules-load',
|
|
414
|
-
setup(plugin) {
|
|
415
|
-
plugin.onLoad(
|
|
416
|
-
{
|
|
417
|
-
filter:
|
|
418
|
-
/\/node_modules\/.*(?:\.mjs|\.esm\.js|\/(?:es|esm|dist\/esm|build\/modern)\/.*\.js)$/,
|
|
419
|
-
},
|
|
420
|
-
async ({ path: file }) => {
|
|
421
|
-
const code = await transformNodeModuleSource(file);
|
|
422
|
-
return { contents: code, loader: 'js' };
|
|
423
|
-
},
|
|
424
|
-
);
|
|
425
|
-
},
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
function runtimeConfigForFile(file: string | undefined) {
|
|
430
|
-
if (!file) return undefined;
|
|
431
|
-
const key = path.resolve(file);
|
|
432
|
-
if (runtimeConfigLookupCache.has(key)) {
|
|
433
|
-
return runtimeConfigLookupCache.get(key) ?? undefined;
|
|
434
|
-
}
|
|
435
|
-
for (const config of runtimeConfigs.values()) {
|
|
436
|
-
for (const root of config.roots) {
|
|
437
|
-
if (isInside(root, file)) {
|
|
438
|
-
runtimeConfigLookupCache.set(key, config);
|
|
439
|
-
return config;
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
}
|
|
443
|
-
runtimeConfigLookupCache.set(key, null);
|
|
444
|
-
return undefined;
|
|
108
|
+
esmDistPackages.clear()
|
|
109
|
+
clearProvidedEntryResolutions()
|
|
110
|
+
clearNodeModulesResolutionCache()
|
|
111
|
+
clearNodeModulesFsCache()
|
|
445
112
|
}
|
|
446
113
|
|
|
447
|
-
function sourceRootForFile(config: ResolvedConfig, file: string) {
|
|
448
|
-
const dir = path.dirname(file);
|
|
449
|
-
if (isInside(config.appPath, dir)) return config.appPath;
|
|
450
|
-
return packageRootsForConfig(config).find(root => isInside(root, dir)) ?? dir;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
function packageRootsForConfig(config: ResolvedConfig) {
|
|
454
|
-
let roots = packageRootCache.get(config);
|
|
455
|
-
if (!roots) {
|
|
456
|
-
roots = workspacePackageRoots(config.workspaceRoot);
|
|
457
|
-
packageRootCache.set(config, roots);
|
|
458
|
-
}
|
|
459
|
-
return roots;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
function firstRuntimeConfig() {
|
|
463
|
-
for (const config of runtimeConfigs.values()) return config;
|
|
464
|
-
return undefined;
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
function firstAliasForSpecifier(specifier: string) {
|
|
468
|
-
if (firstAliasCache.has(specifier)) return firstAliasCache.get(specifier) ?? undefined;
|
|
469
|
-
for (const config of runtimeConfigs.values()) {
|
|
470
|
-
const target = config.aliases.get(specifier);
|
|
471
|
-
if (target) {
|
|
472
|
-
firstAliasCache.set(specifier, target);
|
|
473
|
-
return target;
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
firstAliasCache.set(specifier, null);
|
|
477
|
-
return undefined;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
async function transformSource(file: string) {
|
|
481
|
-
const fileStat = await stat(file);
|
|
482
|
-
const cached = transformCache.get(file);
|
|
483
|
-
if (cached?.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) {
|
|
484
|
-
return cached.code;
|
|
485
|
-
}
|
|
486
|
-
// The in-memory cache is empty on every restart, so a restarted server
|
|
487
|
-
// re-transforms the whole materialized module set on the request path.
|
|
488
|
-
const diskCache = transformCacheFile(file, fileStat);
|
|
489
|
-
if (diskCache) {
|
|
490
|
-
const code = await readFile(diskCache, 'utf8').catch(() => undefined);
|
|
491
|
-
if (code !== undefined) {
|
|
492
|
-
transformCache.set(file, { mtimeMs: fileStat.mtimeMs, size: fileStat.size, code });
|
|
493
|
-
return code;
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const runtime = runtimeConfigForFile(file);
|
|
498
|
-
let raw = await readFile(file, 'utf8');
|
|
499
|
-
// Raw markdown compiles through the MDX pipeline first; materialized
|
|
500
|
-
// cache/server copies were already compiled in place (dev/imports) and would
|
|
501
|
-
// be corrupted by a second pass.
|
|
502
|
-
if (/\.mdx?$/.test(file) && !file.includes(`${path.sep}cache${path.sep}server${path.sep}`)) {
|
|
503
|
-
const { compileMdx } = await import('../compat/mdx/compile');
|
|
504
|
-
raw = (await compileMdx(raw, file, runtime?.root ?? path.dirname(file))).code;
|
|
505
|
-
}
|
|
506
|
-
const source = await inlineModuleScopeRequires(
|
|
507
|
-
rewriteServerSource(raw, file, {
|
|
508
|
-
root: runtime?.root,
|
|
509
|
-
}),
|
|
510
|
-
file,
|
|
511
|
-
runtime?.root,
|
|
512
|
-
);
|
|
513
|
-
const result = await transform(source, {
|
|
514
|
-
loader: esbuildLoader(file),
|
|
515
|
-
jsx: 'automatic',
|
|
516
|
-
jsxImportSource: 'preact',
|
|
517
|
-
sourcefile: file,
|
|
518
|
-
target: 'es2022',
|
|
519
|
-
...serverDefineOptions(),
|
|
520
|
-
});
|
|
521
|
-
transformCache.set(file, {
|
|
522
|
-
mtimeMs: fileStat.mtimeMs,
|
|
523
|
-
size: fileStat.size,
|
|
524
|
-
code: result.code,
|
|
525
|
-
});
|
|
526
|
-
if (diskCache) {
|
|
527
|
-
await mkdir(path.dirname(diskCache), { recursive: true }).catch(() => undefined);
|
|
528
|
-
void writeFileAtomic(diskCache, result.code).catch(() => undefined);
|
|
529
|
-
}
|
|
530
|
-
return result.code;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
534
|
-
const transformDiskCacheDisabled = process.env.PNEXT_TRANSFORM_DISK_CACHE === '0';
|
|
535
|
-
const materializedSegment = `${path.sep}cache${path.sep}server${path.sep}`;
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* Where a transform of `file` is remembered across restarts. Only for the
|
|
539
|
-
* materialized `cache/server` modules: they are hash-named build output under
|
|
540
|
-
* our own cache root, so the key (path + mtime + size + the define/extension
|
|
541
|
-
* inputs of the transform) is complete, and the whole tree is discarded when
|
|
542
|
-
* a build or a config change rewrites it.
|
|
543
|
-
*/
|
|
544
|
-
function transformCacheFile(file: string, fileStat: { mtimeMs: number; size: number }) {
|
|
545
|
-
if (transformDiskCacheDisabled) return undefined;
|
|
546
|
-
const at = file.lastIndexOf(materializedSegment);
|
|
547
|
-
if (at === -1) return undefined;
|
|
548
|
-
const cacheRoot = file.slice(0, at + materializedSegment.length);
|
|
549
|
-
const key = `${file}\0${fileStat.mtimeMs}\0${fileStat.size}\0${currentLoadExtras().join(',')}\0${JSON.stringify(serverDefineOptions())}`;
|
|
550
|
-
return path.join(cacheRoot, 'transform', `${hashBundleSpecifier(key)}.js`);
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// A synchronous `require('./x')` of an app source module fails at runtime with `require() async
|
|
554
|
-
// module ... is unsupported`: the load plugin's onLoad hook makes every app `[jt]sx?` module an
|
|
555
|
-
// ASYNC Bun module, and Bun cannot `require` one. Bun does not route relative `require-call`s
|
|
556
|
-
// through onResolve plugins (only bare specifiers reach them), so the alias trick cannot intercept
|
|
557
|
-
// them at resolve time. Instead each such require is rewritten to a sibling
|
|
558
|
-
// `<x>.pnext-require.cjs` compiled to CommonJS: a real `.cjs` on disk is deliberately excluded by
|
|
559
|
-
// rootFilter, so Bun loads it natively and synchronously.
|
|
560
|
-
//
|
|
561
|
-
// Only requires that resolve to a rootFilter-matched app module are rewritten (a
|
|
562
|
-
// `.cjs`/`.json`/node_modules require already loads synchronously). The sidecar is a distinct module
|
|
563
|
-
// instance from the module's ESM copy - acceptable here, since the require path was an outright
|
|
564
|
-
// crash before and such modules are typically leaf value modules.
|
|
565
|
-
//
|
|
566
|
-
// Both the source form and the BUILT form are matched: the server bundle keeps such a require
|
|
567
|
-
// unbundled and emits esbuild's `__require("file:///abs/path/...")` shim call, which loads the very
|
|
568
|
-
// same async app module at runtime.
|
|
569
|
-
const REQUIRE_CALL = /\b(?:__)?require\(\s*(['"])((?:\.\.?\/|\/|file:\/\/)[^'"]+)\1\s*\)/g;
|
|
570
|
-
|
|
571
|
-
async function inlineModuleScopeRequires(
|
|
572
|
-
source: string,
|
|
573
|
-
file: string,
|
|
574
|
-
root: string | undefined,
|
|
575
|
-
): Promise<string> {
|
|
576
|
-
if (!root || !source.includes('require(')) return source;
|
|
577
|
-
const filter = rootFilter(root);
|
|
578
|
-
const sidecars = new Map<string, string>();
|
|
579
|
-
const pending: Promise<void>[] = [];
|
|
580
|
-
const seen = new Set<string>();
|
|
581
|
-
for (const match of source.matchAll(REQUIRE_CALL)) {
|
|
582
|
-
const specifier = match[2]!;
|
|
583
|
-
if (seen.has(specifier)) continue;
|
|
584
|
-
seen.add(specifier);
|
|
585
|
-
const target = requireCallTarget(root, file, specifier);
|
|
586
|
-
if (!target || !filter.test(target)) continue;
|
|
587
|
-
pending.push(
|
|
588
|
-
requireCjsSidecar(target).then(sidecar => {
|
|
589
|
-
if (sidecar) sidecars.set(specifier, sidecar);
|
|
590
|
-
}),
|
|
591
|
-
);
|
|
592
|
-
}
|
|
593
|
-
if (pending.length === 0) return source;
|
|
594
|
-
await Promise.all(pending);
|
|
595
|
-
if (sidecars.size === 0) return source;
|
|
596
|
-
return source.replace(REQUIRE_CALL, (whole, quote: string, specifier: string) => {
|
|
597
|
-
const sidecar = sidecars.get(specifier);
|
|
598
|
-
return sidecar ? `require(${quote}${sidecar}${quote})` : whole;
|
|
599
|
-
});
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
/** The module a rewritable `require(...)` call resolves to, if any. */
|
|
603
|
-
function requireCallTarget(root: string, file: string, specifier: string): string | undefined {
|
|
604
|
-
if (specifier.startsWith('file://')) {
|
|
605
|
-
try {
|
|
606
|
-
return resolveImport(root, file, fileURLToPath(specifier));
|
|
607
|
-
} catch {
|
|
608
|
-
return undefined;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return resolveImport(root, file, specifier);
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// Compile an app module to a sibling `.pnext-require.cjs` (CommonJS), reusing a
|
|
615
|
-
// mtime/size-cached copy. Returns the sidecar path, or undefined on any failure
|
|
616
|
-
// (the original require is then left as-is). The module runs through the same
|
|
617
|
-
// server source pipeline as a normal load so the CJS copy behaves like the ESM
|
|
618
|
-
// one; nested module-scope requires inside it are NOT recursively inlined.
|
|
619
|
-
async function requireCjsSidecar(target: string): Promise<string | undefined> {
|
|
620
|
-
try {
|
|
621
|
-
const sidecar = requireCjsSidecarPath(target);
|
|
622
|
-
const fileStat = await stat(target);
|
|
623
|
-
const cached = requireSidecarCache.get(target);
|
|
624
|
-
if (cached?.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) return sidecar;
|
|
625
|
-
const runtime = runtimeConfigForFile(target);
|
|
626
|
-
const source = rewriteServerSource(await readFile(target, 'utf8'), target, {
|
|
627
|
-
root: runtime?.root,
|
|
628
|
-
});
|
|
629
|
-
const result = await transform(source, {
|
|
630
|
-
loader: esbuildLoader(target),
|
|
631
|
-
jsx: 'automatic',
|
|
632
|
-
jsxImportSource: 'preact',
|
|
633
|
-
sourcefile: target,
|
|
634
|
-
target: 'es2022',
|
|
635
|
-
format: 'cjs',
|
|
636
|
-
...serverDefineOptions(),
|
|
637
|
-
});
|
|
638
|
-
await writeFile(sidecar, result.code);
|
|
639
|
-
requireSidecarCache.set(target, { mtimeMs: fileStat.mtimeMs, size: fileStat.size });
|
|
640
|
-
return sidecar;
|
|
641
|
-
} catch {
|
|
642
|
-
return undefined;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function requireCjsSidecarPath(target: string): string {
|
|
647
|
-
return `${target.replace(/\.[^./\\]+$/, '')}.pnext-require.cjs`;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
|
-
async function transformNodeModuleSource(file: string) {
|
|
651
|
-
const fileStat = await stat(file);
|
|
652
|
-
const cached = nodeModuleTransformCache.get(file);
|
|
653
|
-
if (cached?.mtimeMs === fileStat.mtimeMs && cached.size === fileStat.size) {
|
|
654
|
-
return cached.code;
|
|
655
|
-
}
|
|
656
|
-
// The in-memory cache is empty on every restart, so a restarted server
|
|
657
|
-
// re-BUNDLES each of these packages on the request path.
|
|
658
|
-
const record = nodeModuleBundleRecordFile(file, fileStat);
|
|
659
|
-
const reused = record ? await readNodeModuleBundle(record) : undefined;
|
|
660
|
-
if (reused !== undefined) {
|
|
661
|
-
nodeModuleTransformCache.set(file, { mtimeMs: fileStat.mtimeMs, size: fileStat.size, code: reused });
|
|
662
|
-
return reused;
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
const { code, inputs } = await bundleNodeModuleSource(file);
|
|
666
|
-
nodeModuleTransformCache.set(file, {
|
|
667
|
-
mtimeMs: fileStat.mtimeMs,
|
|
668
|
-
size: fileStat.size,
|
|
669
|
-
code,
|
|
670
|
-
});
|
|
671
|
-
if (record) void writeNodeModuleBundle(record, inputs, code);
|
|
672
|
-
return code;
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
/**
|
|
676
|
-
* Where a bundle of `file` is remembered across restarts. The entry's own stat is in the name, but
|
|
677
|
-
* the build inlines the package's relative graph too, so the record carries every esbuild input and
|
|
678
|
-
* is only reused while all of them still stat the same. A record that cannot be validated is a miss.
|
|
679
|
-
*/
|
|
680
|
-
function nodeModuleBundleRecordFile(file: string, fileStat: { mtimeMs: number; size: number }) {
|
|
681
|
-
if (transformDiskCacheDisabled) return undefined;
|
|
682
|
-
const outPath = (runtimeConfigForFile(file) ?? firstRuntimeConfig())?.resolved.outPath;
|
|
683
|
-
if (!outPath) return undefined;
|
|
684
|
-
const key = `${file}\0${fileStat.mtimeMs}\0${fileStat.size}\0${currentLoadExtras().join(',')}\0${JSON.stringify(serverDefineOptions())}`;
|
|
685
|
-
return path.join(cacheRoot(outPath), 'node-module-bundle', `${hashBundleSpecifier(key)}.json`);
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
async function bundleNodeModuleSource(file: string) {
|
|
689
|
-
const resolved = runtimeConfigForFile(file)?.resolved;
|
|
690
|
-
const extensionPlugins = resolved ? getBundlerExtensions().serverEsbuildPlugins(resolved) : [];
|
|
691
|
-
const result = await build({
|
|
692
|
-
// The marker entry only resolves when the extension chain is present.
|
|
693
|
-
entryPoints: [
|
|
694
|
-
(resolved
|
|
695
|
-
? getBundlerExtensions().serverBundleEntry(file, path.dirname(file), file)
|
|
696
|
-
: undefined) ?? file,
|
|
697
|
-
],
|
|
698
|
-
bundle: true,
|
|
699
|
-
write: false,
|
|
700
|
-
format: 'esm',
|
|
701
|
-
platform: 'neutral',
|
|
702
|
-
target: 'es2022',
|
|
703
|
-
loader: packageJsxLoaders,
|
|
704
|
-
jsx: 'automatic',
|
|
705
|
-
jsxImportSource: 'preact',
|
|
706
|
-
packages: 'external',
|
|
707
|
-
logLevel: 'silent',
|
|
708
|
-
metafile: true,
|
|
709
|
-
...serverDefineOptions(),
|
|
710
|
-
plugins: [
|
|
711
|
-
serverAssetPlugin(),
|
|
712
|
-
...extensionPlugins,
|
|
713
|
-
runtimeAliasBuildPlugin(undefined, 'server', { bundleRequireAliases: true }),
|
|
714
|
-
],
|
|
715
|
-
});
|
|
716
|
-
const output = result.outputFiles[0];
|
|
717
|
-
if (!output) throw new Error(`Failed to bundle ${file}`);
|
|
718
|
-
// Packages can ship 'use cache' functions too (Next allows it); everything
|
|
719
|
-
// else in the source chain was already settled by the bundle above (imports
|
|
720
|
-
// resolved by the plugins, constants inlined by esbuild `define`).
|
|
721
|
-
return {
|
|
722
|
-
code: applyBundledSourceTransforms(output.text, file),
|
|
723
|
-
inputs: Object.keys(result.metafile.inputs).map(input => path.resolve(input)),
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
/**
|
|
728
|
-
* The one vendor-compile entry point. Every demand - a module compile's import scan, an esbuild
|
|
729
|
-
* resolve callback, a transitive dependency of another vendor bundle - arrives here and is scheduled
|
|
730
|
-
* by the pipeline in `./vendor`. `nested` marks demands raised from inside a running vendor build;
|
|
731
|
-
* see `withVendorSlot` for why they must not queue.
|
|
732
|
-
*/
|
|
733
114
|
export async function externalServerPackageHref(
|
|
734
115
|
config: ResolvedConfig,
|
|
735
116
|
specifier: string,
|
|
@@ -738,9 +119,16 @@ export async function externalServerPackageHref(
|
|
|
738
119
|
conditionTarget: ServerBundleTarget = target,
|
|
739
120
|
nested = false,
|
|
740
121
|
) {
|
|
741
|
-
let plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
|
|
742
|
-
const aliased = await crossLayerReusePlan(
|
|
743
|
-
|
|
122
|
+
let plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
|
|
123
|
+
const aliased = await crossLayerReusePlan(
|
|
124
|
+
config,
|
|
125
|
+
specifier,
|
|
126
|
+
resolveDir,
|
|
127
|
+
target,
|
|
128
|
+
conditionTarget,
|
|
129
|
+
plan,
|
|
130
|
+
)
|
|
131
|
+
if (aliased) plan = aliased
|
|
744
132
|
if (vendorTraceEnabled()) {
|
|
745
133
|
vendorTraceRow({
|
|
746
134
|
kind: 'request',
|
|
@@ -750,17 +138,17 @@ export async function externalServerPackageHref(
|
|
|
750
138
|
conditionTarget,
|
|
751
139
|
memHit: vendorBundleMemHit(plan.key),
|
|
752
140
|
atMs: performance.now(),
|
|
753
|
-
})
|
|
141
|
+
})
|
|
754
142
|
}
|
|
755
|
-
let file = await vendorBundle(plan, nested)
|
|
143
|
+
let file = await vendorBundle(plan, nested)
|
|
756
144
|
// A `pnext build` wiping the output directory under a running server
|
|
757
145
|
// deletes vendor bundles; re-write instead of handing out a dead path.
|
|
758
146
|
if (!cachedExistsSync(file) || !(await compiledModuleUsable(file))) {
|
|
759
|
-
dropVendorBundle(plan.key, file)
|
|
760
|
-
if (plan.group) dropVendorGroup(plan.group.key)
|
|
761
|
-
file = await vendorBundle(plan, nested)
|
|
147
|
+
dropVendorBundle(plan.key, file)
|
|
148
|
+
if (plan.group) dropVendorGroup(plan.group.key)
|
|
149
|
+
file = await vendorBundle(plan, nested)
|
|
762
150
|
}
|
|
763
|
-
return pathToFileHref(file)
|
|
151
|
+
return pathToFileHref(file)
|
|
764
152
|
}
|
|
765
153
|
|
|
766
154
|
/**
|
|
@@ -774,11 +162,11 @@ function vendorArtifactKey(
|
|
|
774
162
|
conditionTarget: ServerBundleTarget,
|
|
775
163
|
source: string,
|
|
776
164
|
) {
|
|
777
|
-
return `${config.outPath}\0${target}\0${conditionTarget}\0${source}
|
|
165
|
+
return `${config.outPath}\0${target}\0${conditionTarget}\0${source}`
|
|
778
166
|
}
|
|
779
167
|
|
|
780
168
|
function vendorArtifactFile(config: ResolvedConfig, key: string) {
|
|
781
|
-
return path.join(config.outPath, 'cache', 'server', 'vendor', `${hashBundleSpecifier(key)}.mjs`)
|
|
169
|
+
return path.join(config.outPath, 'cache', 'server', 'vendor', `${hashBundleSpecifier(key)}.mjs`)
|
|
782
170
|
}
|
|
783
171
|
|
|
784
172
|
/**
|
|
@@ -793,23 +181,23 @@ function resolveVendorEntry(
|
|
|
793
181
|
resolveDir: string,
|
|
794
182
|
conditionTarget: ServerBundleTarget,
|
|
795
183
|
) {
|
|
796
|
-
if (path.isAbsolute(specifier)) return path.resolve(specifier)
|
|
184
|
+
if (path.isAbsolute(specifier)) return path.resolve(specifier)
|
|
797
185
|
// Every demand builds a plan before dedup can see it, so this resolve runs
|
|
798
186
|
// once per DEMAND, not once per artifact — and the same specifier arrives
|
|
799
187
|
// from hundreds of importers. The answer is pure in its inputs.
|
|
800
|
-
const key = `${config.root}\0${conditionTarget}\0${resolveDir}\0${specifier}
|
|
801
|
-
if (vendorEntryResolutions.has(key)) return vendorEntryResolutions.get(key)
|
|
188
|
+
const key = `${config.root}\0${conditionTarget}\0${resolveDir}\0${specifier}`
|
|
189
|
+
if (vendorEntryResolutions.has(key)) return vendorEntryResolutions.get(key)
|
|
802
190
|
const entry = resolveVendorPackageSpecifier(
|
|
803
191
|
config.root,
|
|
804
192
|
path.join(resolveDir, 'pnext-resolve.ts'),
|
|
805
193
|
specifier,
|
|
806
194
|
[...serverBundleConditions(conditionTarget), 'import'],
|
|
807
|
-
)
|
|
808
|
-
vendorEntryResolutions.set(key, entry)
|
|
809
|
-
return entry
|
|
195
|
+
)
|
|
196
|
+
vendorEntryResolutions.set(key, entry)
|
|
197
|
+
return entry
|
|
810
198
|
}
|
|
811
199
|
|
|
812
|
-
const vendorEntryResolutions = new Map<string, string | undefined>()
|
|
200
|
+
const vendorEntryResolutions = new Map<string, string | undefined>()
|
|
813
201
|
|
|
814
202
|
function vendorBuildPlan(
|
|
815
203
|
config: ResolvedConfig,
|
|
@@ -818,7 +206,7 @@ function vendorBuildPlan(
|
|
|
818
206
|
resolveDir: string,
|
|
819
207
|
conditionTarget: ServerBundleTarget,
|
|
820
208
|
): VendorBuildPlan {
|
|
821
|
-
const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
209
|
+
const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
822
210
|
// An unresolvable specifier has no artifact identity to key on; keep the old
|
|
823
211
|
// importer-scoped key so the demand still compiles (and still dedups per dir).
|
|
824
212
|
const key = vendorArtifactKey(
|
|
@@ -826,8 +214,8 @@ function vendorBuildPlan(
|
|
|
826
214
|
target,
|
|
827
215
|
conditionTarget,
|
|
828
216
|
entry ?? `${path.resolve(resolveDir)}\0${specifier}`,
|
|
829
|
-
)
|
|
830
|
-
const file = vendorArtifactFile(config, key)
|
|
217
|
+
)
|
|
218
|
+
const file = vendorArtifactFile(config, key)
|
|
831
219
|
return {
|
|
832
220
|
key,
|
|
833
221
|
file,
|
|
@@ -846,7 +234,7 @@ function vendorBuildPlan(
|
|
|
846
234
|
group: entry
|
|
847
235
|
? vendorGroupPlan(config, { specifier, entry, file }, target, resolveDir, conditionTarget)
|
|
848
236
|
: undefined,
|
|
849
|
-
}
|
|
237
|
+
}
|
|
850
238
|
}
|
|
851
239
|
|
|
852
240
|
/**
|
|
@@ -856,12 +244,12 @@ function vendorBuildPlan(
|
|
|
856
244
|
* liveness check rebuilds every bundle a second time.
|
|
857
245
|
*/
|
|
858
246
|
async function writeVendorArtifact(file: string, code: string) {
|
|
859
|
-
const id = await publishVendorContent(file, code)
|
|
247
|
+
const id = await publishVendorContent(file, code)
|
|
860
248
|
if (id === undefined) {
|
|
861
|
-
await writeFileAtomic(file, code)
|
|
862
|
-
noteDevArtifactWritten(file)
|
|
249
|
+
await writeFileAtomic(file, code)
|
|
250
|
+
noteDevArtifactWritten(file)
|
|
863
251
|
}
|
|
864
|
-
return id
|
|
252
|
+
return id
|
|
865
253
|
}
|
|
866
254
|
|
|
867
255
|
// --------------------------------------------------------------------------
|
|
@@ -869,79 +257,82 @@ async function writeVendorArtifact(file: string, code: string) {
|
|
|
869
257
|
// --------------------------------------------------------------------------
|
|
870
258
|
|
|
871
259
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
872
|
-
const vendorContentDisabled = () => process.env.PNEXT_VENDOR_CONTENT === '0'
|
|
260
|
+
const vendorContentDisabled = () => process.env.PNEXT_VENDOR_CONTENT === '0'
|
|
873
261
|
|
|
874
262
|
function vendorContentFile(vendorDir: string, id: string) {
|
|
875
|
-
return path.join(vendorDir, `${id}.content.mjs`)
|
|
263
|
+
return path.join(vendorDir, `${id}.content.mjs`)
|
|
876
264
|
}
|
|
877
265
|
|
|
878
266
|
/** Entries only: a chunk reaches its entry by relative path, so it stays put. */
|
|
879
267
|
async function publishVendorContent(file: string, code: string) {
|
|
880
|
-
const vendorDir = path.dirname(file)
|
|
881
|
-
if (vendorContentDisabled() || path.basename(vendorDir) !== 'vendor') return undefined
|
|
882
|
-
const id = hashBundleSpecifier(canonicalVendorCode(code))
|
|
883
|
-
const content = vendorContentFile(vendorDir, id)
|
|
884
|
-
if (!cachedExistsSync(content)) await writeFileAtomic(content, code)
|
|
885
|
-
noteDevArtifactWritten(content)
|
|
886
|
-
await linkVendorArtifact(file, content)
|
|
887
|
-
noteVendorContentId(file, id)
|
|
888
|
-
noteDevArtifactWritten(file)
|
|
889
|
-
return id
|
|
268
|
+
const vendorDir = path.dirname(file)
|
|
269
|
+
if (vendorContentDisabled() || path.basename(vendorDir) !== 'vendor') return undefined
|
|
270
|
+
const id = hashBundleSpecifier(canonicalVendorCode(code))
|
|
271
|
+
const content = vendorContentFile(vendorDir, id)
|
|
272
|
+
if (!cachedExistsSync(content)) await writeFileAtomic(content, code)
|
|
273
|
+
noteDevArtifactWritten(content)
|
|
274
|
+
await linkVendorArtifact(file, content)
|
|
275
|
+
noteVendorContentId(file, id)
|
|
276
|
+
noteDevArtifactWritten(file)
|
|
277
|
+
return id
|
|
890
278
|
}
|
|
891
279
|
|
|
892
280
|
/** The layer-keyed path becomes an alias to the shared bytes. */
|
|
893
281
|
async function linkVendorArtifact(file: string, content: string) {
|
|
894
|
-
await rm(file, { force: true }).catch(() => undefined)
|
|
282
|
+
await rm(file, { force: true }).catch(() => undefined)
|
|
895
283
|
try {
|
|
896
|
-
await symlink(path.basename(content), file)
|
|
284
|
+
await symlink(path.basename(content), file)
|
|
897
285
|
} catch {
|
|
898
|
-
await copyFile(content, file)
|
|
286
|
+
await copyFile(content, file)
|
|
899
287
|
}
|
|
900
288
|
}
|
|
901
289
|
|
|
902
290
|
/** What one layer's build of an entry saw, for the cross-layer comparison. */
|
|
903
291
|
interface VendorLayerRecord {
|
|
904
|
-
inputs: string
|
|
905
|
-
externals: string
|
|
906
|
-
blocked: boolean
|
|
907
|
-
id: string
|
|
292
|
+
inputs: string
|
|
293
|
+
externals: string
|
|
294
|
+
blocked: boolean
|
|
295
|
+
id: string
|
|
908
296
|
}
|
|
909
297
|
|
|
910
298
|
// Keyed by output path exactly as `vendorArtifactKey` is: a verdict holds for
|
|
911
299
|
// ONE app's conditions, aliases and defines, and several run in one process.
|
|
912
|
-
const vendorLayerRecords = new Map<string, Map<string, VendorLayerRecord>>()
|
|
300
|
+
const vendorLayerRecords = new Map<string, Map<string, VendorLayerRecord>>()
|
|
913
301
|
/** Entries proven layer-independent -> the content id both layers resolve to. */
|
|
914
|
-
const vendorSharedEntries = new Map<string, Map<string, string>>()
|
|
915
|
-
const vendorSharedLoaded = new Set<string>()
|
|
302
|
+
const vendorSharedEntries = new Map<string, Map<string, string>>()
|
|
303
|
+
const vendorSharedLoaded = new Set<string>()
|
|
916
304
|
|
|
917
305
|
function vendorSharedFor(config: ResolvedConfig) {
|
|
918
|
-
let shared = vendorSharedEntries.get(config.outPath)
|
|
306
|
+
let shared = vendorSharedEntries.get(config.outPath)
|
|
919
307
|
if (!shared) {
|
|
920
|
-
shared = new Map()
|
|
921
|
-
vendorSharedEntries.set(config.outPath, shared)
|
|
308
|
+
shared = new Map()
|
|
309
|
+
vendorSharedEntries.set(config.outPath, shared)
|
|
922
310
|
}
|
|
923
|
-
return shared
|
|
311
|
+
return shared
|
|
924
312
|
}
|
|
925
313
|
|
|
926
314
|
/** Only these two layers were ever the duplicated half. */
|
|
927
315
|
function vendorLayerKey(target: CompatAliasTarget, conditionTarget: ServerBundleTarget) {
|
|
928
|
-
if (target === 'server' && conditionTarget === 'server') return 'server'
|
|
929
|
-
if (target === 'client' && conditionTarget === 'client') return 'client'
|
|
930
|
-
return undefined
|
|
316
|
+
if (target === 'server' && conditionTarget === 'server') return 'server'
|
|
317
|
+
if (target === 'client' && conditionTarget === 'client') return 'client'
|
|
318
|
+
return undefined
|
|
931
319
|
}
|
|
932
320
|
|
|
933
321
|
function vendorSharedFile(config: ResolvedConfig) {
|
|
934
|
-
return path.join(config.outPath, 'cache', 'server', 'vendor', 'layer-shared.json')
|
|
322
|
+
return path.join(config.outPath, 'cache', 'server', 'vendor', 'layer-shared.json')
|
|
935
323
|
}
|
|
936
324
|
|
|
937
325
|
/** Verdicts from earlier runs, so reuse lands on the first demand of this one. */
|
|
938
326
|
function loadVendorSharedEntries(config: ResolvedConfig) {
|
|
939
|
-
if (vendorSharedLoaded.has(config.outPath)) return
|
|
940
|
-
vendorSharedLoaded.add(config.outPath)
|
|
327
|
+
if (vendorSharedLoaded.has(config.outPath)) return
|
|
328
|
+
vendorSharedLoaded.add(config.outPath)
|
|
941
329
|
try {
|
|
942
|
-
const stored = JSON.parse(readFileSync(vendorSharedFile(config), 'utf8')) as Record<
|
|
943
|
-
|
|
944
|
-
|
|
330
|
+
const stored = JSON.parse(readFileSync(vendorSharedFile(config), 'utf8')) as Record<
|
|
331
|
+
string,
|
|
332
|
+
string
|
|
333
|
+
>
|
|
334
|
+
const shared = vendorSharedFor(config)
|
|
335
|
+
for (const [entry, id] of Object.entries(stored)) shared.set(entry, id)
|
|
945
336
|
} catch {
|
|
946
337
|
// No verdicts yet; they are written as builds settle.
|
|
947
338
|
}
|
|
@@ -951,7 +342,7 @@ function persistVendorSharedEntries(config: ResolvedConfig) {
|
|
|
951
342
|
void writeFileAtomic(
|
|
952
343
|
vendorSharedFile(config),
|
|
953
344
|
JSON.stringify(Object.fromEntries(vendorSharedFor(config))),
|
|
954
|
-
).catch(() => undefined)
|
|
345
|
+
).catch(() => undefined)
|
|
955
346
|
}
|
|
956
347
|
|
|
957
348
|
/** Offline verdict: externals compare as content ids, so this is a fixpoint. */
|
|
@@ -964,21 +355,19 @@ function recordVendorLayerBuild(
|
|
|
964
355
|
code: string,
|
|
965
356
|
id: string | undefined,
|
|
966
357
|
) {
|
|
967
|
-
const layer = entry && id && metafile && vendorLayerKey(target, conditionTarget)
|
|
968
|
-
if (!layer || !entry || !id || !metafile) return
|
|
969
|
-
loadVendorSharedEntries(config)
|
|
970
|
-
const externals = new Set<string>()
|
|
971
|
-
let blocked = /['"]use (?:client|server|cache)['"]/.test(code)
|
|
358
|
+
const layer = entry && id && metafile && vendorLayerKey(target, conditionTarget)
|
|
359
|
+
if (!layer || !entry || !id || !metafile) return
|
|
360
|
+
loadVendorSharedEntries(config)
|
|
361
|
+
const externals = new Set<string>()
|
|
362
|
+
let blocked = /['"]use (?:client|server|cache)['"]/.test(code)
|
|
972
363
|
for (const meta of Object.values(metafile.inputs)) {
|
|
973
364
|
for (const reference of meta.imports) {
|
|
974
|
-
if (!reference.external) continue
|
|
975
|
-
const target_ = reference.path.startsWith('file:')
|
|
976
|
-
? fileURLToPath(reference.path)
|
|
977
|
-
: undefined;
|
|
365
|
+
if (!reference.external) continue
|
|
366
|
+
const target_ = reference.path.startsWith('file:') ? fileURLToPath(reference.path) : undefined
|
|
978
367
|
// A shim outside the vendor tree is this layer's own alias target: the
|
|
979
368
|
// artifact is layer-specific whatever else matches.
|
|
980
|
-
if (target_ && path.basename(path.dirname(target_)) !== 'vendor') blocked = true
|
|
981
|
-
externals.add(target_ ? (vendorContentId(target_) ?? target_) : reference.path)
|
|
369
|
+
if (target_ && path.basename(path.dirname(target_)) !== 'vendor') blocked = true
|
|
370
|
+
externals.add(target_ ? (vendorContentId(target_) ?? target_) : reference.path)
|
|
982
371
|
}
|
|
983
372
|
}
|
|
984
373
|
const record: VendorLayerRecord = {
|
|
@@ -986,20 +375,20 @@ function recordVendorLayerBuild(
|
|
|
986
375
|
externals: [...externals].sort().join('\n'),
|
|
987
376
|
blocked,
|
|
988
377
|
id,
|
|
989
|
-
}
|
|
990
|
-
const recordKey = `${config.outPath}\0${entry}
|
|
991
|
-
let byLayer = vendorLayerRecords.get(recordKey)
|
|
378
|
+
}
|
|
379
|
+
const recordKey = `${config.outPath}\0${entry}`
|
|
380
|
+
let byLayer = vendorLayerRecords.get(recordKey)
|
|
992
381
|
if (!byLayer) {
|
|
993
|
-
byLayer = new Map()
|
|
994
|
-
vendorLayerRecords.set(recordKey, byLayer)
|
|
382
|
+
byLayer = new Map()
|
|
383
|
+
vendorLayerRecords.set(recordKey, byLayer)
|
|
995
384
|
}
|
|
996
|
-
byLayer.set(layer, record)
|
|
997
|
-
const server = byLayer.get('server')
|
|
998
|
-
const client = byLayer.get('client')
|
|
999
|
-
if (!server || !client || server.blocked || client.blocked) return
|
|
1000
|
-
if (server.inputs !== client.inputs || server.externals !== client.externals) return
|
|
1001
|
-
vendorSharedFor(config).set(entry, server.id)
|
|
1002
|
-
persistVendorSharedEntries(config)
|
|
385
|
+
byLayer.set(layer, record)
|
|
386
|
+
const server = byLayer.get('server')
|
|
387
|
+
const client = byLayer.get('client')
|
|
388
|
+
if (!server || !client || server.blocked || client.blocked) return
|
|
389
|
+
if (server.inputs !== client.inputs || server.externals !== client.externals) return
|
|
390
|
+
vendorSharedFor(config).set(entry, server.id)
|
|
391
|
+
persistVendorSharedEntries(config)
|
|
1003
392
|
}
|
|
1004
393
|
|
|
1005
394
|
/** A proven-shared entry's content file, or undefined to compile as usual. */
|
|
@@ -1009,12 +398,13 @@ function reusableVendorContent(
|
|
|
1009
398
|
target: CompatAliasTarget,
|
|
1010
399
|
conditionTarget: ServerBundleTarget,
|
|
1011
400
|
) {
|
|
1012
|
-
if (vendorContentDisabled() || !entry || !vendorLayerKey(target, conditionTarget))
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
401
|
+
if (vendorContentDisabled() || !entry || !vendorLayerKey(target, conditionTarget))
|
|
402
|
+
return undefined
|
|
403
|
+
loadVendorSharedEntries(config)
|
|
404
|
+
const id = vendorSharedFor(config).get(entry)
|
|
405
|
+
if (!id) return undefined
|
|
406
|
+
const content = vendorContentFile(path.join(config.outPath, 'cache', 'server', 'vendor'), id)
|
|
407
|
+
return existsSync(content) ? content : undefined
|
|
1018
408
|
}
|
|
1019
409
|
|
|
1020
410
|
// --------------------------------------------------------------------------
|
|
@@ -1022,11 +412,11 @@ function reusableVendorContent(
|
|
|
1022
412
|
// --------------------------------------------------------------------------
|
|
1023
413
|
|
|
1024
414
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1025
|
-
const vendorReuseEnabled = () => process.env.PNEXT_VENDOR_REUSE !== '0'
|
|
415
|
+
const vendorReuseEnabled = () => process.env.PNEXT_VENDOR_REUSE !== '0'
|
|
1026
416
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1027
|
-
const vendorReuseExtEnabled = () => process.env.PNEXT_VENDOR_REUSE_EXT !== '0'
|
|
417
|
+
const vendorReuseExtEnabled = () => process.env.PNEXT_VENDOR_REUSE_EXT !== '0'
|
|
1028
418
|
|
|
1029
|
-
/** Alias targets that differ by layer (compat/
|
|
419
|
+
/** Alias targets that differ by layer (compat/aliases.ts); prefix-matches subpaths. */
|
|
1030
420
|
const VENDOR_LAYER_ALIASES = new Set([
|
|
1031
421
|
'react',
|
|
1032
422
|
'react-dom',
|
|
@@ -1036,50 +426,50 @@ const VENDOR_LAYER_ALIASES = new Set([
|
|
|
1036
426
|
'next/server',
|
|
1037
427
|
'server-only',
|
|
1038
428
|
'client-only',
|
|
1039
|
-
])
|
|
429
|
+
])
|
|
1040
430
|
|
|
1041
431
|
function isVendorLayerAlias(external: string) {
|
|
1042
432
|
return (
|
|
1043
433
|
VENDOR_LAYER_ALIASES.has(external) ||
|
|
1044
434
|
VENDOR_LAYER_ALIASES.has(external.split('/').slice(0, 2).join('/'))
|
|
1045
|
-
)
|
|
435
|
+
)
|
|
1046
436
|
}
|
|
1047
437
|
|
|
1048
438
|
interface VendorReuseVerdict {
|
|
1049
|
-
stat: string
|
|
1050
|
-
conds: string
|
|
1051
|
-
reusable: boolean
|
|
1052
|
-
reason?: string
|
|
439
|
+
stat: string
|
|
440
|
+
conds: string
|
|
441
|
+
reusable: boolean
|
|
442
|
+
reason?: string
|
|
1053
443
|
}
|
|
1054
444
|
|
|
1055
|
-
const vendorReuseVerdicts = new Map<string, Map<string, VendorReuseVerdict>>()
|
|
1056
|
-
const vendorReuseLoaded = new Set<string>()
|
|
1057
|
-
const vendorReuseProbes = new Map<string, Promise<boolean>>()
|
|
445
|
+
const vendorReuseVerdicts = new Map<string, Map<string, VendorReuseVerdict>>()
|
|
446
|
+
const vendorReuseLoaded = new Set<string>()
|
|
447
|
+
const vendorReuseProbes = new Map<string, Promise<boolean>>()
|
|
1058
448
|
|
|
1059
449
|
function vendorReuseFile(config: ResolvedConfig) {
|
|
1060
|
-
return path.join(config.outPath, 'cache', 'server', 'vendor', 'reuse-verdicts.json')
|
|
450
|
+
return path.join(config.outPath, 'cache', 'server', 'vendor', 'reuse-verdicts.json')
|
|
1061
451
|
}
|
|
1062
452
|
|
|
1063
453
|
function vendorReuseFor(config: ResolvedConfig) {
|
|
1064
|
-
let verdicts = vendorReuseVerdicts.get(config.outPath)
|
|
454
|
+
let verdicts = vendorReuseVerdicts.get(config.outPath)
|
|
1065
455
|
if (!verdicts) {
|
|
1066
|
-
verdicts = new Map()
|
|
1067
|
-
vendorReuseVerdicts.set(config.outPath, verdicts)
|
|
456
|
+
verdicts = new Map()
|
|
457
|
+
vendorReuseVerdicts.set(config.outPath, verdicts)
|
|
1068
458
|
}
|
|
1069
|
-
return verdicts
|
|
459
|
+
return verdicts
|
|
1070
460
|
}
|
|
1071
461
|
|
|
1072
462
|
/** Verdicts persist per checkout: probes are paid once, restarts read the store. */
|
|
1073
463
|
function loadVendorReuseVerdicts(config: ResolvedConfig) {
|
|
1074
|
-
if (vendorReuseLoaded.has(config.outPath)) return
|
|
1075
|
-
vendorReuseLoaded.add(config.outPath)
|
|
464
|
+
if (vendorReuseLoaded.has(config.outPath)) return
|
|
465
|
+
vendorReuseLoaded.add(config.outPath)
|
|
1076
466
|
try {
|
|
1077
467
|
const stored = JSON.parse(readFileSync(vendorReuseFile(config), 'utf8')) as Record<
|
|
1078
468
|
string,
|
|
1079
469
|
VendorReuseVerdict
|
|
1080
|
-
|
|
1081
|
-
const verdicts = vendorReuseFor(config)
|
|
1082
|
-
for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict)
|
|
470
|
+
>
|
|
471
|
+
const verdicts = vendorReuseFor(config)
|
|
472
|
+
for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict)
|
|
1083
473
|
} catch {
|
|
1084
474
|
// No store yet; verdicts are written as probes settle.
|
|
1085
475
|
}
|
|
@@ -1089,24 +479,24 @@ function persistVendorReuseVerdicts(config: ResolvedConfig) {
|
|
|
1089
479
|
void writeFileAtomic(
|
|
1090
480
|
vendorReuseFile(config),
|
|
1091
481
|
JSON.stringify(Object.fromEntries(vendorReuseFor(config))),
|
|
1092
|
-
).catch(() => undefined)
|
|
482
|
+
).catch(() => undefined)
|
|
1093
483
|
}
|
|
1094
484
|
|
|
1095
485
|
/** Probe results shared across verdict pairs — node_modules bytes are immutable per process. */
|
|
1096
|
-
const vendorLayerProbeMemo = new Map<string, Promise<VendorLayerProbe>>()
|
|
486
|
+
const vendorLayerProbeMemo = new Map<string, Promise<VendorLayerProbe>>()
|
|
1097
487
|
|
|
1098
|
-
type VendorLayerProbe = Awaited<ReturnType<typeof probeVendorLayer
|
|
488
|
+
type VendorLayerProbe = Awaited<ReturnType<typeof probeVendorLayer>>
|
|
1099
489
|
|
|
1100
490
|
function probeVendorLayerMemo(config: ResolvedConfig, entry: string, conditions: string[]) {
|
|
1101
|
-
if (!vendorReuseExtEnabled()) return probeVendorLayer(config, entry, conditions)
|
|
1102
|
-
const key = `${config.outPath}\0${entry}\0${conditions.join(',')}
|
|
1103
|
-
let probe = vendorLayerProbeMemo.get(key)
|
|
491
|
+
if (!vendorReuseExtEnabled()) return probeVendorLayer(config, entry, conditions)
|
|
492
|
+
const key = `${config.outPath}\0${entry}\0${conditions.join(',')}`
|
|
493
|
+
let probe = vendorLayerProbeMemo.get(key)
|
|
1104
494
|
if (!probe) {
|
|
1105
|
-
probe = probeVendorLayer(config, entry, conditions)
|
|
1106
|
-
probe.catch(() => vendorLayerProbeMemo.delete(key))
|
|
1107
|
-
vendorLayerProbeMemo.set(key, probe)
|
|
495
|
+
probe = probeVendorLayer(config, entry, conditions)
|
|
496
|
+
probe.catch(() => vendorLayerProbeMemo.delete(key))
|
|
497
|
+
vendorLayerProbeMemo.set(key, probe)
|
|
1108
498
|
}
|
|
1109
|
-
return probe
|
|
499
|
+
return probe
|
|
1110
500
|
}
|
|
1111
501
|
|
|
1112
502
|
/** Metafile-only probe of one layer's view of an entry (never writes, no slots). */
|
|
@@ -1127,10 +517,10 @@ async function probeVendorLayer(config: ResolvedConfig, entry: string, condition
|
|
|
1127
517
|
jsxImportSource: 'preact',
|
|
1128
518
|
logLevel: 'silent',
|
|
1129
519
|
packages: 'external',
|
|
1130
|
-
})
|
|
1131
|
-
const externals = new Set<string>()
|
|
520
|
+
})
|
|
521
|
+
const externals = new Set<string>()
|
|
1132
522
|
for (const meta of Object.values(result.metafile.inputs)) {
|
|
1133
|
-
for (const reference of meta.imports) if (reference.external) externals.add(reference.path)
|
|
523
|
+
for (const reference of meta.imports) if (reference.external) externals.add(reference.path)
|
|
1134
524
|
}
|
|
1135
525
|
return {
|
|
1136
526
|
inputs: Object.keys(result.metafile.inputs).sort().join('\n'),
|
|
@@ -1138,7 +528,7 @@ async function probeVendorLayer(config: ResolvedConfig, entry: string, condition
|
|
|
1138
528
|
directive: /['"]use (?:client|server|cache)['"]/.test(
|
|
1139
529
|
result.outputFiles.map(file => file.text).join(''),
|
|
1140
530
|
),
|
|
1141
|
-
}
|
|
531
|
+
}
|
|
1142
532
|
}
|
|
1143
533
|
|
|
1144
534
|
async function computeVendorReuseVerdict(
|
|
@@ -1147,28 +537,35 @@ async function computeVendorReuseVerdict(
|
|
|
1147
537
|
clientEntry: string,
|
|
1148
538
|
otherTarget: ServerBundleTarget = 'client',
|
|
1149
539
|
): Promise<{ reusable: boolean; reason?: string }> {
|
|
1150
|
-
const tp = performance.now()
|
|
540
|
+
const tp = performance.now()
|
|
1151
541
|
const done = (v: { reusable: boolean; reason?: string }) => {
|
|
1152
|
-
heavyProfRow({
|
|
1153
|
-
|
|
1154
|
-
|
|
542
|
+
heavyProfRow({
|
|
543
|
+
k: 'reuse-probe',
|
|
544
|
+
entry: serverEntry,
|
|
545
|
+
other: otherTarget,
|
|
546
|
+
ms: performance.now() - tp,
|
|
547
|
+
...v,
|
|
548
|
+
})
|
|
549
|
+
return v
|
|
550
|
+
}
|
|
1155
551
|
// A finished server build already proved itself layer-specific: skip both probes.
|
|
1156
552
|
if (vendorReuseExtEnabled()) {
|
|
1157
|
-
const recorded = vendorLayerRecords.get(`${config.outPath}\0${serverEntry}`)?.get('server')
|
|
1158
|
-
if (recorded?.blocked) return done({ reusable: false, reason: 'recorded-blocked' })
|
|
553
|
+
const recorded = vendorLayerRecords.get(`${config.outPath}\0${serverEntry}`)?.get('server')
|
|
554
|
+
if (recorded?.blocked) return done({ reusable: false, reason: 'recorded-blocked' })
|
|
1159
555
|
}
|
|
1160
556
|
const [server, client] = await Promise.all([
|
|
1161
557
|
probeVendorLayerMemo(config, serverEntry, serverBundleConditions('server')),
|
|
1162
558
|
probeVendorLayerMemo(config, clientEntry, serverBundleConditions(otherTarget)),
|
|
1163
|
-
])
|
|
1164
|
-
if (server.inputs !== client.inputs) return done({ reusable: false, reason: 'inputs-differ' })
|
|
559
|
+
])
|
|
560
|
+
if (server.inputs !== client.inputs) return done({ reusable: false, reason: 'inputs-differ' })
|
|
1165
561
|
if (server.externals.join('\n') !== client.externals.join('\n')) {
|
|
1166
|
-
return done({ reusable: false, reason: 'externals-differ' })
|
|
562
|
+
return done({ reusable: false, reason: 'externals-differ' })
|
|
1167
563
|
}
|
|
1168
|
-
const aliasHit = server.externals.filter(isVendorLayerAlias)
|
|
1169
|
-
if (aliasHit.length > 0)
|
|
1170
|
-
|
|
1171
|
-
return done({ reusable:
|
|
564
|
+
const aliasHit = server.externals.filter(isVendorLayerAlias)
|
|
565
|
+
if (aliasHit.length > 0)
|
|
566
|
+
return done({ reusable: false, reason: `layer-alias: ${aliasHit.join(',')}` })
|
|
567
|
+
if (server.directive || client.directive) return done({ reusable: false, reason: 'directive' })
|
|
568
|
+
return done({ reusable: true })
|
|
1172
569
|
}
|
|
1173
570
|
|
|
1174
571
|
/** Whether the client demand may resolve to the server-layer artifact. */
|
|
@@ -1178,21 +575,27 @@ function crossLayerReuseVerdict(
|
|
|
1178
575
|
clientEntry: string,
|
|
1179
576
|
otherTarget: ServerBundleTarget = 'client',
|
|
1180
577
|
): Promise<boolean> {
|
|
1181
|
-
loadVendorReuseVerdicts(config)
|
|
578
|
+
loadVendorReuseVerdicts(config)
|
|
1182
579
|
const key =
|
|
1183
580
|
otherTarget === 'client'
|
|
1184
581
|
? `${serverEntry}\0${clientEntry}`
|
|
1185
|
-
: `${otherTarget}:${serverEntry}\0${clientEntry}
|
|
1186
|
-
const stats = [
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
582
|
+
: `${otherTarget}:${serverEntry}\0${clientEntry}`
|
|
583
|
+
const stats = [
|
|
584
|
+
statSync(serverEntry, { throwIfNoEntry: false }),
|
|
585
|
+
statSync(clientEntry, { throwIfNoEntry: false }),
|
|
586
|
+
]
|
|
587
|
+
if (stats.some(entryStat => !entryStat)) return Promise.resolve(false)
|
|
588
|
+
const stat_ = stats.map(entryStat => `${entryStat!.mtimeMs}:${entryStat!.size}`).join('|')
|
|
589
|
+
const conds = JSON.stringify([
|
|
590
|
+
serverBundleConditions('server'),
|
|
591
|
+
serverBundleConditions(otherTarget),
|
|
592
|
+
])
|
|
593
|
+
const stored = vendorReuseFor(config).get(key)
|
|
1191
594
|
if (stored?.stat === stat_ && stored.conds === conds) {
|
|
1192
|
-
return Promise.resolve(stored.reusable)
|
|
595
|
+
return Promise.resolve(stored.reusable)
|
|
1193
596
|
}
|
|
1194
|
-
const probeKey = `${config.outPath}\0${key}
|
|
1195
|
-
let probe = vendorReuseProbes.get(probeKey)
|
|
597
|
+
const probeKey = `${config.outPath}\0${key}`
|
|
598
|
+
let probe = vendorReuseProbes.get(probeKey)
|
|
1196
599
|
if (!probe) {
|
|
1197
600
|
probe = computeVendorReuseVerdict(config, serverEntry, clientEntry, otherTarget)
|
|
1198
601
|
.catch(error => ({
|
|
@@ -1200,14 +603,14 @@ function crossLayerReuseVerdict(
|
|
|
1200
603
|
reason: `probe-failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}`,
|
|
1201
604
|
}))
|
|
1202
605
|
.then(verdict => {
|
|
1203
|
-
vendorReuseFor(config).set(key, { stat: stat_, conds, ...verdict })
|
|
1204
|
-
persistVendorReuseVerdicts(config)
|
|
1205
|
-
return verdict.reusable
|
|
606
|
+
vendorReuseFor(config).set(key, { stat: stat_, conds, ...verdict })
|
|
607
|
+
persistVendorReuseVerdicts(config)
|
|
608
|
+
return verdict.reusable
|
|
1206
609
|
})
|
|
1207
|
-
.finally(() => vendorReuseProbes.delete(probeKey))
|
|
1208
|
-
vendorReuseProbes.set(probeKey, probe)
|
|
610
|
+
.finally(() => vendorReuseProbes.delete(probeKey))
|
|
611
|
+
vendorReuseProbes.set(probeKey, probe)
|
|
1209
612
|
}
|
|
1210
|
-
return probe
|
|
613
|
+
return probe
|
|
1211
614
|
}
|
|
1212
615
|
|
|
1213
616
|
/**
|
|
@@ -1224,27 +627,29 @@ async function crossLayerReusePlan(
|
|
|
1224
627
|
conditionTarget: ServerBundleTarget,
|
|
1225
628
|
clientPlan: VendorBuildPlan,
|
|
1226
629
|
): Promise<VendorBuildPlan | undefined> {
|
|
1227
|
-
if (!vendorReuseEnabled() || vendorContentDisabled()) return undefined
|
|
630
|
+
if (!vendorReuseEnabled() || vendorContentDisabled()) return undefined
|
|
1228
631
|
// An edge demand proven condition-independent lands on the server plan —
|
|
1229
632
|
// one build serves both, and the server artifact keeps client reuse alive.
|
|
1230
633
|
if (target === 'server' && conditionTarget === 'edge' && vendorReuseExtEnabled()) {
|
|
1231
|
-
if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined
|
|
1232
|
-
const edgeEntry = resolveVendorEntry(config, specifier, resolveDir, 'edge')
|
|
1233
|
-
const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server')
|
|
1234
|
-
if (!edgeEntry || !serverEntry || edgeEntry !== serverEntry) return undefined
|
|
1235
|
-
const reusable = await crossLayerReuseVerdict(config, serverEntry, edgeEntry, 'edge').catch(
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
634
|
+
if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined
|
|
635
|
+
const edgeEntry = resolveVendorEntry(config, specifier, resolveDir, 'edge')
|
|
636
|
+
const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server')
|
|
637
|
+
if (!edgeEntry || !serverEntry || edgeEntry !== serverEntry) return undefined
|
|
638
|
+
const reusable = await crossLayerReuseVerdict(config, serverEntry, edgeEntry, 'edge').catch(
|
|
639
|
+
() => false,
|
|
640
|
+
)
|
|
641
|
+
return reusable ? vendorBuildPlan(config, specifier, 'server', resolveDir, 'server') : undefined
|
|
642
|
+
}
|
|
643
|
+
if (vendorLayerKey(target, conditionTarget) !== 'client') return undefined
|
|
1239
644
|
// An in-flight or on-disk client artifact already settles cheaper than a probe.
|
|
1240
|
-
if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined
|
|
1241
|
-
const clientEntry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
1242
|
-
const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server')
|
|
1243
|
-
if (!clientEntry || !serverEntry) return undefined
|
|
1244
|
-
const serverPlan = vendorBuildPlan(config, specifier, 'server', resolveDir, 'server')
|
|
1245
|
-
if (!vendorBundleMemHit(serverPlan.key) && !existsSync(serverPlan.file)) return undefined
|
|
1246
|
-
const reusable = await crossLayerReuseVerdict(config, serverEntry, clientEntry).catch(() => false)
|
|
1247
|
-
return reusable ? serverPlan : undefined
|
|
645
|
+
if (vendorBundleMemHit(clientPlan.key) || existsSync(clientPlan.file)) return undefined
|
|
646
|
+
const clientEntry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
647
|
+
const serverEntry = resolveVendorEntry(config, specifier, resolveDir, 'server')
|
|
648
|
+
if (!clientEntry || !serverEntry) return undefined
|
|
649
|
+
const serverPlan = vendorBuildPlan(config, specifier, 'server', resolveDir, 'server')
|
|
650
|
+
if (!vendorBundleMemHit(serverPlan.key) && !existsSync(serverPlan.file)) return undefined
|
|
651
|
+
const reusable = await crossLayerReuseVerdict(config, serverEntry, clientEntry).catch(() => false)
|
|
652
|
+
return reusable ? serverPlan : undefined
|
|
1248
653
|
}
|
|
1249
654
|
|
|
1250
655
|
/** Cache hit and no-compile fast paths, in front of every build. */
|
|
@@ -1260,23 +665,23 @@ async function prepareVendorArtifact(
|
|
|
1260
665
|
// Vendor bundles are keyed by resolved package path (version-specific under
|
|
1261
666
|
// pnpm), so an existing file is already current. Reuse it across dev restarts
|
|
1262
667
|
// instead of re-bundling the whole dependency graph.
|
|
1263
|
-
if (existsSync(file) && (await compiledModuleUsable(file))) return file
|
|
1264
|
-
await mkdir(path.dirname(file), { recursive: true })
|
|
668
|
+
if (existsSync(file) && (await compiledModuleUsable(file))) return file
|
|
669
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
1265
670
|
// An earlier build proved this entry layer-independent: the other layer's
|
|
1266
671
|
// content IS this layer's artifact, so the demand settles without compiling.
|
|
1267
|
-
const shared = reusableVendorContent(config, entry, target, conditionTarget)
|
|
672
|
+
const shared = reusableVendorContent(config, entry, target, conditionTarget)
|
|
1268
673
|
if (shared && (await compiledModuleUsable(shared))) {
|
|
1269
|
-
await linkVendorArtifact(file, shared)
|
|
1270
|
-
noteDevArtifactWritten(file)
|
|
1271
|
-
return shared
|
|
674
|
+
await linkVendorArtifact(file, shared)
|
|
675
|
+
noteDevArtifactWritten(file)
|
|
676
|
+
return shared
|
|
1272
677
|
}
|
|
1273
678
|
// A package that already ships a browser-ready ESM dist is copied, not compiled - the bundler would
|
|
1274
679
|
// only reproduce it. Sits in FRONT of the pass, never replaces it: anything the predicate rejects
|
|
1275
680
|
// falls through unchanged, and the copy is resolved exactly as the bundler would have. The copies
|
|
1276
681
|
// load natively now (rootFilter skips the vendor tree), so an app that configures `compiler.define`
|
|
1277
682
|
// must take the bundling path instead - that is where the defines are applied.
|
|
1278
|
-
if (vendorLoadsNatively() && serverDefineOptions().define) return undefined
|
|
1279
|
-
const distDir = `${file.replace(/\.mjs$/, '')}.dist
|
|
683
|
+
if (vendorLoadsNatively() && serverDefineOptions().define) return undefined
|
|
684
|
+
const distDir = `${file.replace(/\.mjs$/, '')}.dist`
|
|
1280
685
|
const distEntry = await copyBrowserReadyEsmDist(
|
|
1281
686
|
resolveVendorPackageSpecifier(
|
|
1282
687
|
config.root,
|
|
@@ -1285,44 +690,30 @@ async function prepareVendorArtifact(
|
|
|
1285
690
|
serverBundleConditions(conditionTarget),
|
|
1286
691
|
) ?? '',
|
|
1287
692
|
distDir,
|
|
1288
|
-
)
|
|
693
|
+
)
|
|
1289
694
|
if (distEntry) {
|
|
1290
695
|
// Native loading means the `.js` copies need their own type marker: an app
|
|
1291
696
|
// without `"type": "module"` would otherwise have Bun read them as
|
|
1292
697
|
// CommonJS and choke on their `export` syntax.
|
|
1293
|
-
await markEsmDistPackage(distDir)
|
|
1294
|
-
noteDevArtifactWritten(distEntry)
|
|
698
|
+
await markEsmDistPackage(distDir)
|
|
699
|
+
noteDevArtifactWritten(distEntry)
|
|
1295
700
|
}
|
|
1296
|
-
return distEntry
|
|
701
|
+
return distEntry
|
|
1297
702
|
}
|
|
1298
703
|
|
|
1299
|
-
/**
|
|
1300
|
-
* Bisect switch for the vendor-tree load-plugin exclusion. Deliberately NOT `PNEXT_VENDOR_NATIVE` -
|
|
1301
|
-
* that name already gates the plugin-free bundler, and sharing it would arm that opt-in path by
|
|
1302
|
-
* accident whenever this one was bisected.
|
|
1303
|
-
*/
|
|
1304
|
-
function vendorLoadsNatively() {
|
|
1305
|
-
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1306
|
-
return process.env.PNEXT_VENDOR_DIST_NATIVE !== '0';
|
|
1307
|
-
}
|
|
1308
|
-
|
|
1309
|
-
// --------------------------------------------------------------------------
|
|
1310
|
-
// pre-planned vendor policy (PNEXT_VENDOR_PREPLAN=1 opt-in, server layer only)
|
|
1311
|
-
// --------------------------------------------------------------------------
|
|
1312
|
-
|
|
1313
704
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1314
|
-
const vendorPreplanEnabled = () => process.env.PNEXT_VENDOR_PREPLAN === '1'
|
|
705
|
+
const vendorPreplanEnabled = () => process.env.PNEXT_VENDOR_PREPLAN === '1'
|
|
1315
706
|
|
|
1316
707
|
interface VendorPreplanVerdict {
|
|
1317
|
-
stat: string
|
|
1318
|
-
conds: string
|
|
1319
|
-
safe: boolean
|
|
1320
|
-
reason?: string
|
|
708
|
+
stat: string
|
|
709
|
+
conds: string
|
|
710
|
+
safe: boolean
|
|
711
|
+
reason?: string
|
|
1321
712
|
}
|
|
1322
713
|
|
|
1323
|
-
const vendorPreplanVerdicts = new Map<string, Map<string, VendorPreplanVerdict>>()
|
|
1324
|
-
const vendorPreplanLoaded = new Set<string>()
|
|
1325
|
-
const vendorPreplanProbes = new Map<string, Promise<VendorPreplanVerdict>>()
|
|
714
|
+
const vendorPreplanVerdicts = new Map<string, Map<string, VendorPreplanVerdict>>()
|
|
715
|
+
const vendorPreplanLoaded = new Set<string>()
|
|
716
|
+
const vendorPreplanProbes = new Map<string, Promise<VendorPreplanVerdict>>()
|
|
1326
717
|
|
|
1327
718
|
const vendorPreplanStats = {
|
|
1328
719
|
preplanRefs: 0,
|
|
@@ -1332,49 +723,49 @@ const vendorPreplanStats = {
|
|
|
1332
723
|
packagesRouted: 0,
|
|
1333
724
|
packagesRejected: 0,
|
|
1334
725
|
reasons: [] as string[],
|
|
1335
|
-
}
|
|
726
|
+
}
|
|
1336
727
|
|
|
1337
728
|
export function vendorPreplanReport() {
|
|
1338
|
-
return { ...vendorPreplanStats, reasons: vendorPreplanStats.reasons.slice(0, 60) }
|
|
729
|
+
return { ...vendorPreplanStats, reasons: vendorPreplanStats.reasons.slice(0, 60) }
|
|
1339
730
|
}
|
|
1340
731
|
|
|
1341
732
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1342
|
-
const vendorPreplanDebug = () => process.env.PNEXT_VENDOR_PREPLAN_DEBUG === '1'
|
|
1343
|
-
let preplanReportFlush: ReturnType<typeof setTimeout> | undefined
|
|
733
|
+
const vendorPreplanDebug = () => process.env.PNEXT_VENDOR_PREPLAN_DEBUG === '1'
|
|
734
|
+
let preplanReportFlush: ReturnType<typeof setTimeout> | undefined
|
|
1344
735
|
function schedulePreplanReport() {
|
|
1345
|
-
if (!vendorPreplanDebug()) return
|
|
1346
|
-
if (preplanReportFlush) clearTimeout(preplanReportFlush)
|
|
736
|
+
if (!vendorPreplanDebug()) return
|
|
737
|
+
if (preplanReportFlush) clearTimeout(preplanReportFlush)
|
|
1347
738
|
preplanReportFlush = setTimeout(() => {
|
|
1348
|
-
console.error(`pnext vendor preplan ${JSON.stringify(vendorPreplanReport())}`)
|
|
1349
|
-
console.error(`pnext vendor native ${JSON.stringify(vendorNativeReport())}`)
|
|
1350
|
-
}, 3000)
|
|
1351
|
-
preplanReportFlush.unref?.()
|
|
739
|
+
console.error(`pnext vendor preplan ${JSON.stringify(vendorPreplanReport())}`)
|
|
740
|
+
console.error(`pnext vendor native ${JSON.stringify(vendorNativeReport())}`)
|
|
741
|
+
}, 3000)
|
|
742
|
+
preplanReportFlush.unref?.()
|
|
1352
743
|
}
|
|
1353
744
|
|
|
1354
745
|
function vendorPreplanFile(config: ResolvedConfig) {
|
|
1355
|
-
return path.join(config.outPath, 'cache', 'server', 'vendor', 'preplan-verdicts.json')
|
|
746
|
+
return path.join(config.outPath, 'cache', 'server', 'vendor', 'preplan-verdicts.json')
|
|
1356
747
|
}
|
|
1357
748
|
|
|
1358
749
|
function vendorPreplanFor(config: ResolvedConfig) {
|
|
1359
|
-
let verdicts = vendorPreplanVerdicts.get(config.outPath)
|
|
750
|
+
let verdicts = vendorPreplanVerdicts.get(config.outPath)
|
|
1360
751
|
if (!verdicts) {
|
|
1361
|
-
verdicts = new Map()
|
|
1362
|
-
vendorPreplanVerdicts.set(config.outPath, verdicts)
|
|
752
|
+
verdicts = new Map()
|
|
753
|
+
vendorPreplanVerdicts.set(config.outPath, verdicts)
|
|
1363
754
|
}
|
|
1364
|
-
return verdicts
|
|
755
|
+
return verdicts
|
|
1365
756
|
}
|
|
1366
757
|
|
|
1367
758
|
/** Probe verdicts persist per checkout, reuse-verdict style. */
|
|
1368
759
|
function loadVendorPreplanVerdicts(config: ResolvedConfig) {
|
|
1369
|
-
if (vendorPreplanLoaded.has(config.outPath)) return
|
|
1370
|
-
vendorPreplanLoaded.add(config.outPath)
|
|
760
|
+
if (vendorPreplanLoaded.has(config.outPath)) return
|
|
761
|
+
vendorPreplanLoaded.add(config.outPath)
|
|
1371
762
|
try {
|
|
1372
763
|
const stored = JSON.parse(readFileSync(vendorPreplanFile(config), 'utf8')) as Record<
|
|
1373
764
|
string,
|
|
1374
765
|
VendorPreplanVerdict
|
|
1375
|
-
|
|
1376
|
-
const verdicts = vendorPreplanFor(config)
|
|
1377
|
-
for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict)
|
|
766
|
+
>
|
|
767
|
+
const verdicts = vendorPreplanFor(config)
|
|
768
|
+
for (const [key, verdict] of Object.entries(stored)) verdicts.set(key, verdict)
|
|
1378
769
|
} catch {
|
|
1379
770
|
// No store yet; verdicts are written as probes settle.
|
|
1380
771
|
}
|
|
@@ -1384,7 +775,7 @@ function persistVendorPreplanVerdicts(config: ResolvedConfig) {
|
|
|
1384
775
|
void writeFileAtomic(
|
|
1385
776
|
vendorPreplanFile(config),
|
|
1386
777
|
JSON.stringify(Object.fromEntries(vendorPreplanFor(config))),
|
|
1387
|
-
).catch(() => undefined)
|
|
778
|
+
).catch(() => undefined)
|
|
1388
779
|
}
|
|
1389
780
|
|
|
1390
781
|
/** Does this entry's graph change shape under `react-server` (or carry directives)? */
|
|
@@ -1393,15 +784,15 @@ function vendorPreplanEntryVerdict(
|
|
|
1393
784
|
entry: string,
|
|
1394
785
|
conditions: string[],
|
|
1395
786
|
): Promise<VendorPreplanVerdict> | VendorPreplanVerdict {
|
|
1396
|
-
loadVendorPreplanVerdicts(config)
|
|
1397
|
-
const entryStat = statSync(entry, { throwIfNoEntry: false })
|
|
1398
|
-
if (!entryStat) return { stat: '', conds: '', safe: false, reason: 'entry missing' }
|
|
1399
|
-
const stat_ = `${entryStat.mtimeMs}:${entryStat.size}
|
|
1400
|
-
const conds = JSON.stringify(conditions)
|
|
1401
|
-
const stored = vendorPreplanFor(config).get(entry)
|
|
1402
|
-
if (stored?.stat === stat_ && stored.conds === conds) return stored
|
|
1403
|
-
const probeKey = `${config.outPath}\0${entry}\0${conds}
|
|
1404
|
-
let probe = vendorPreplanProbes.get(probeKey)
|
|
787
|
+
loadVendorPreplanVerdicts(config)
|
|
788
|
+
const entryStat = statSync(entry, { throwIfNoEntry: false })
|
|
789
|
+
if (!entryStat) return { stat: '', conds: '', safe: false, reason: 'entry missing' }
|
|
790
|
+
const stat_ = `${entryStat.mtimeMs}:${entryStat.size}`
|
|
791
|
+
const conds = JSON.stringify(conditions)
|
|
792
|
+
const stored = vendorPreplanFor(config).get(entry)
|
|
793
|
+
if (stored?.stat === stat_ && stored.conds === conds) return stored
|
|
794
|
+
const probeKey = `${config.outPath}\0${entry}\0${conds}`
|
|
795
|
+
let probe = vendorPreplanProbes.get(probeKey)
|
|
1405
796
|
if (!probe) {
|
|
1406
797
|
probe = computeVendorPreplanVerdict(config, entry, conditions, stat_, conds)
|
|
1407
798
|
.catch(error => ({
|
|
@@ -1411,14 +802,14 @@ function vendorPreplanEntryVerdict(
|
|
|
1411
802
|
reason: `probe-failed: ${(error instanceof Error ? error.message : String(error)).slice(0, 160)}`,
|
|
1412
803
|
}))
|
|
1413
804
|
.then(verdict => {
|
|
1414
|
-
vendorPreplanFor(config).set(entry, verdict)
|
|
1415
|
-
persistVendorPreplanVerdicts(config)
|
|
1416
|
-
return verdict
|
|
805
|
+
vendorPreplanFor(config).set(entry, verdict)
|
|
806
|
+
persistVendorPreplanVerdicts(config)
|
|
807
|
+
return verdict
|
|
1417
808
|
})
|
|
1418
|
-
.finally(() => vendorPreplanProbes.delete(probeKey))
|
|
1419
|
-
vendorPreplanProbes.set(probeKey, probe)
|
|
809
|
+
.finally(() => vendorPreplanProbes.delete(probeKey))
|
|
810
|
+
vendorPreplanProbes.set(probeKey, probe)
|
|
1420
811
|
}
|
|
1421
|
-
return probe
|
|
812
|
+
return probe
|
|
1422
813
|
}
|
|
1423
814
|
|
|
1424
815
|
async function computeVendorPreplanVerdict(
|
|
@@ -1430,18 +821,22 @@ async function computeVendorPreplanVerdict(
|
|
|
1430
821
|
): Promise<VendorPreplanVerdict> {
|
|
1431
822
|
const [rsc, plain] = await Promise.all([
|
|
1432
823
|
probeVendorLayer(config, entry, conditions),
|
|
1433
|
-
probeVendorLayer(
|
|
1434
|
-
|
|
824
|
+
probeVendorLayer(
|
|
825
|
+
config,
|
|
826
|
+
entry,
|
|
827
|
+
conditions.filter(condition => condition !== 'react-server'),
|
|
828
|
+
),
|
|
829
|
+
])
|
|
1435
830
|
if (rsc.inputs !== plain.inputs) {
|
|
1436
|
-
return { stat: stat_, conds, safe: false, reason: 'react-server inputs differ' }
|
|
831
|
+
return { stat: stat_, conds, safe: false, reason: 'react-server inputs differ' }
|
|
1437
832
|
}
|
|
1438
833
|
if (rsc.externals.join('\n') !== plain.externals.join('\n')) {
|
|
1439
|
-
return { stat: stat_, conds, safe: false, reason: 'react-server externals differ' }
|
|
834
|
+
return { stat: stat_, conds, safe: false, reason: 'react-server externals differ' }
|
|
1440
835
|
}
|
|
1441
836
|
if (rsc.directive || plain.directive) {
|
|
1442
|
-
return { stat: stat_, conds, safe: false, reason: 'directive' }
|
|
837
|
+
return { stat: stat_, conds, safe: false, reason: 'directive' }
|
|
1443
838
|
}
|
|
1444
|
-
return { stat: stat_, conds, safe: true }
|
|
839
|
+
return { stat: stat_, conds, safe: true }
|
|
1445
840
|
}
|
|
1446
841
|
|
|
1447
842
|
/**
|
|
@@ -1457,37 +852,37 @@ async function vendorPreplanPackageSafe(
|
|
|
1457
852
|
target: CompatAliasTarget,
|
|
1458
853
|
conditionTarget: ServerBundleTarget,
|
|
1459
854
|
): Promise<boolean> {
|
|
1460
|
-
if (!vendorPreplanEnabled() || target !== 'server' || conditionTarget !== 'server') return false
|
|
855
|
+
if (!vendorPreplanEnabled() || target !== 'server' || conditionTarget !== 'server') return false
|
|
1461
856
|
const reject = (reason: string) => {
|
|
1462
|
-
vendorPreplanStats.packagesRejected += 1
|
|
1463
|
-
vendorPreplanStats.reasons.push(`${packageName ?? '?'}: ${reason}`)
|
|
1464
|
-
schedulePreplanReport()
|
|
1465
|
-
return false
|
|
1466
|
-
}
|
|
1467
|
-
if (!packageName || !entry || !path.isAbsolute(entry)) return reject('unresolved entry')
|
|
1468
|
-
if (/\.[cm]?tsx?$/.test(entry)) return reject('ts entry')
|
|
1469
|
-
if (!isEsmModuleEntry(entry)) return reject('cjs entry')
|
|
1470
|
-
if (getExternalPackagePolicy().transpile(packageName)) return reject('transpilePackages')
|
|
1471
|
-
const verdict = await vendorPreplanEntryVerdict(config, entry, serverBundleConditions('server'))
|
|
1472
|
-
if (!verdict.safe) return reject(verdict.reason ?? 'react-server divergence')
|
|
1473
|
-
vendorPreplanStats.packagesRouted += 1
|
|
1474
|
-
schedulePreplanReport()
|
|
1475
|
-
return true
|
|
857
|
+
vendorPreplanStats.packagesRejected += 1
|
|
858
|
+
vendorPreplanStats.reasons.push(`${packageName ?? '?'}: ${reason}`)
|
|
859
|
+
schedulePreplanReport()
|
|
860
|
+
return false
|
|
861
|
+
}
|
|
862
|
+
if (!packageName || !entry || !path.isAbsolute(entry)) return reject('unresolved entry')
|
|
863
|
+
if (/\.[cm]?tsx?$/.test(entry)) return reject('ts entry')
|
|
864
|
+
if (!isEsmModuleEntry(entry)) return reject('cjs entry')
|
|
865
|
+
if (getExternalPackagePolicy().transpile(packageName)) return reject('transpilePackages')
|
|
866
|
+
const verdict = await vendorPreplanEntryVerdict(config, entry, serverBundleConditions('server'))
|
|
867
|
+
if (!verdict.safe) return reject(verdict.reason ?? 'react-server divergence')
|
|
868
|
+
vendorPreplanStats.packagesRouted += 1
|
|
869
|
+
schedulePreplanReport()
|
|
870
|
+
return true
|
|
1476
871
|
}
|
|
1477
872
|
|
|
1478
873
|
/** Mirror of `externalServerPackageHref`'s liveness recheck, minus the await(er). */
|
|
1479
874
|
function enqueuePreplanBuild(plan: VendorBuildPlan) {
|
|
1480
|
-
vendorPreplanStats.enqueued += 1
|
|
875
|
+
vendorPreplanStats.enqueued += 1
|
|
1481
876
|
trackPreplanBuild(
|
|
1482
877
|
(async () => {
|
|
1483
|
-
let file = await vendorBundle(plan, false)
|
|
878
|
+
let file = await vendorBundle(plan, false)
|
|
1484
879
|
if (!cachedExistsSync(file) || !(await compiledModuleUsable(file))) {
|
|
1485
|
-
dropVendorBundle(plan.key, file)
|
|
1486
|
-
if (plan.group) dropVendorGroup(plan.group.key)
|
|
1487
|
-
file = await vendorBundle(plan, false)
|
|
880
|
+
dropVendorBundle(plan.key, file)
|
|
881
|
+
if (plan.group) dropVendorGroup(plan.group.key)
|
|
882
|
+
file = await vendorBundle(plan, false)
|
|
1488
883
|
}
|
|
1489
884
|
})(),
|
|
1490
|
-
)
|
|
885
|
+
)
|
|
1491
886
|
}
|
|
1492
887
|
|
|
1493
888
|
/**
|
|
@@ -1504,20 +899,20 @@ async function preplanDependencyDecision(
|
|
|
1504
899
|
specifier: string,
|
|
1505
900
|
resolveDir: string,
|
|
1506
901
|
): Promise<OnResolveResult | undefined> {
|
|
1507
|
-
const depName = packageNameFromSpecifier(specifier)
|
|
1508
|
-
if (!depName || !isPackageSpecifier(specifier)) return undefined
|
|
1509
|
-
if (builtinModules.includes(depName) || isBunBuiltin(depName)) return undefined
|
|
1510
|
-
if (getExternalPackagePolicy().external(depName)) return undefined
|
|
1511
|
-
if (getExternalPackagePolicy().transpile(depName)) return undefined
|
|
1512
|
-
const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
1513
|
-
if (!entry || /\.[cm]?tsx?$/.test(entry) || !isEsmModuleEntry(entry)) return undefined
|
|
1514
|
-
const plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
|
|
902
|
+
const depName = packageNameFromSpecifier(specifier)
|
|
903
|
+
if (!depName || !isPackageSpecifier(specifier)) return undefined
|
|
904
|
+
if (builtinModules.includes(depName) || isBunBuiltin(depName)) return undefined
|
|
905
|
+
if (getExternalPackagePolicy().external(depName)) return undefined
|
|
906
|
+
if (getExternalPackagePolicy().transpile(depName)) return undefined
|
|
907
|
+
const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
908
|
+
if (!entry || /\.[cm]?tsx?$/.test(entry) || !isEsmModuleEntry(entry)) return undefined
|
|
909
|
+
const plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
|
|
1515
910
|
if (cachedExistsSync(plan.file)) {
|
|
1516
|
-
if (!(await compiledModuleUsable(plan.file))) return undefined
|
|
911
|
+
if (!(await compiledModuleUsable(plan.file))) return undefined
|
|
1517
912
|
} else {
|
|
1518
913
|
// Both prepare fast paths settle on a DIFFERENT file than `plan.file`, so
|
|
1519
914
|
// their demands keep the awaited path (which is nearly free for them).
|
|
1520
|
-
if (reusableVendorContent(config, entry, target, conditionTarget)) return undefined
|
|
915
|
+
if (reusableVendorContent(config, entry, target, conditionTarget)) return undefined
|
|
1521
916
|
if (!(vendorLoadsNatively() && serverDefineOptions().define)) {
|
|
1522
917
|
const distEntry = await copyBrowserReadyEsmDist(
|
|
1523
918
|
resolveVendorPackageSpecifier(
|
|
@@ -1527,22 +922,22 @@ async function preplanDependencyDecision(
|
|
|
1527
922
|
serverBundleConditions(conditionTarget),
|
|
1528
923
|
) ?? '',
|
|
1529
924
|
`${plan.file.replace(/\.mjs$/, '')}.dist`,
|
|
1530
|
-
)
|
|
1531
|
-
if (distEntry) return undefined
|
|
925
|
+
)
|
|
926
|
+
if (distEntry) return undefined
|
|
1532
927
|
}
|
|
1533
928
|
}
|
|
1534
|
-
enqueuePreplanBuild(plan)
|
|
1535
|
-
return { path: pathToFileHref(plan.file), external: true }
|
|
929
|
+
enqueuePreplanBuild(plan)
|
|
930
|
+
return { path: pathToFileHref(plan.file), external: true }
|
|
1536
931
|
}
|
|
1537
932
|
|
|
1538
|
-
const esmDistPackages = new Set<string>()
|
|
933
|
+
const esmDistPackages = new Set<string>()
|
|
1539
934
|
|
|
1540
935
|
async function markEsmDistPackage(distDir: string) {
|
|
1541
|
-
if (esmDistPackages.has(distDir)) return
|
|
1542
|
-
esmDistPackages.add(distDir)
|
|
1543
|
-
const marker = path.join(distDir, 'package.json')
|
|
1544
|
-
if (existsSync(marker)) return
|
|
1545
|
-
await writeFile(marker, '{"type":"module"}').catch(() => undefined)
|
|
936
|
+
if (esmDistPackages.has(distDir)) return
|
|
937
|
+
esmDistPackages.add(distDir)
|
|
938
|
+
const marker = path.join(distDir, 'package.json')
|
|
939
|
+
if (existsSync(marker)) return
|
|
940
|
+
await writeFile(marker, '{"type":"module"}').catch(() => undefined)
|
|
1546
941
|
}
|
|
1547
942
|
|
|
1548
943
|
/**
|
|
@@ -1554,7 +949,7 @@ async function markEsmDistPackage(distDir: string) {
|
|
|
1554
949
|
*/
|
|
1555
950
|
// eslint-disable-next-line @typescript-eslint/require-await
|
|
1556
951
|
export async function compiledModuleUsable(file: string): Promise<boolean> {
|
|
1557
|
-
return devArtifactUsable(file)
|
|
952
|
+
return devArtifactUsable(file)
|
|
1558
953
|
}
|
|
1559
954
|
|
|
1560
955
|
/**
|
|
@@ -1573,9 +968,9 @@ function vendorGroupPlan(
|
|
|
1573
968
|
resolveDir: string,
|
|
1574
969
|
conditionTarget: ServerBundleTarget,
|
|
1575
970
|
): VendorGroupPlan | undefined {
|
|
1576
|
-
const packageName = packageNameFromSpecifier(member.specifier)
|
|
1577
|
-
const packageRoot = packageName && vendorPackageRoot(member.entry, packageName)
|
|
1578
|
-
if (!packageName || !packageRoot || !isEsmModuleEntry(member.entry)) return undefined
|
|
971
|
+
const packageName = packageNameFromSpecifier(member.specifier)
|
|
972
|
+
const packageRoot = packageName && vendorPackageRoot(member.entry, packageName)
|
|
973
|
+
if (!packageName || !packageRoot || !isEsmModuleEntry(member.entry)) return undefined
|
|
1579
974
|
return {
|
|
1580
975
|
key: `${config.outPath}\0${target}\0${conditionTarget}\0${packageRoot}`,
|
|
1581
976
|
member,
|
|
@@ -1590,7 +985,7 @@ function vendorGroupPlan(
|
|
|
1590
985
|
conditionTarget,
|
|
1591
986
|
nested,
|
|
1592
987
|
),
|
|
1593
|
-
}
|
|
988
|
+
}
|
|
1594
989
|
}
|
|
1595
990
|
|
|
1596
991
|
/**
|
|
@@ -1598,10 +993,10 @@ function vendorGroupPlan(
|
|
|
1598
993
|
* so a nested (non-hoisted) copy never shares a group with the hoisted one.
|
|
1599
994
|
*/
|
|
1600
995
|
function vendorPackageRoot(entry: string, packageName: string) {
|
|
1601
|
-
const marker = `${path.sep}node_modules${path.sep}
|
|
1602
|
-
const index = entry.lastIndexOf(marker)
|
|
1603
|
-
if (index < 0) return undefined
|
|
1604
|
-
return path.join(entry.slice(0, index + marker.length), packageName)
|
|
996
|
+
const marker = `${path.sep}node_modules${path.sep}`
|
|
997
|
+
const index = entry.lastIndexOf(marker)
|
|
998
|
+
if (index < 0) return undefined
|
|
999
|
+
return path.join(entry.slice(0, index + marker.length), packageName)
|
|
1605
1000
|
}
|
|
1606
1001
|
|
|
1607
1002
|
/**
|
|
@@ -1619,8 +1014,8 @@ async function buildVendorGroup(
|
|
|
1619
1014
|
conditionTarget: ServerBundleTarget,
|
|
1620
1015
|
nested: boolean,
|
|
1621
1016
|
) {
|
|
1622
|
-
const built = members.filter(member => !existsSync(member.file))
|
|
1623
|
-
if (built.length === 0) return
|
|
1017
|
+
const built = members.filter(member => !existsSync(member.file))
|
|
1018
|
+
if (built.length === 0) return
|
|
1624
1019
|
// Only a sibling whose artifact is ON DISK can be externalized — one still
|
|
1625
1020
|
// compiling (a solo build this round did not wait for) must be inlined, or
|
|
1626
1021
|
// this bundle would import a file that is not there yet.
|
|
@@ -1628,26 +1023,26 @@ async function buildVendorGroup(
|
|
|
1628
1023
|
[...external, ...members]
|
|
1629
1024
|
.filter(member => !built.includes(member) && existsSync(member.file))
|
|
1630
1025
|
.map(member => [member.specifier, pathToFileHref(member.file)] as const),
|
|
1631
|
-
)
|
|
1632
|
-
const outdir = path.dirname(built[0]!.file)
|
|
1633
|
-
const context = { nestedExternalFiles: [] as string[] }
|
|
1634
|
-
const entryFiles = new Set(built.map(member => member.file))
|
|
1026
|
+
)
|
|
1027
|
+
const outdir = path.dirname(built[0]!.file)
|
|
1028
|
+
const context = { nestedExternalFiles: [] as string[] }
|
|
1029
|
+
const entryFiles = new Set(built.map(member => member.file))
|
|
1635
1030
|
// The plugin-chain fallback builds from bare specifiers; hand the interop
|
|
1636
1031
|
// plugin each member's already-resolved entry.
|
|
1637
|
-
for (const member of built) provideEntryResolution(member.specifier, resolveDir, member.entry)
|
|
1032
|
+
for (const member of built) provideEntryResolution(member.specifier, resolveDir, member.entry)
|
|
1638
1033
|
const entryPoints = built.map(member => ({
|
|
1639
1034
|
in:
|
|
1640
1035
|
getBundlerExtensions().serverBundleEntry(member.specifier, resolveDir, member.entry) ??
|
|
1641
1036
|
member.specifier,
|
|
1642
1037
|
out: path.basename(member.file, '.mjs'),
|
|
1643
|
-
}))
|
|
1038
|
+
}))
|
|
1644
1039
|
const trace: VendorBuildTrace | undefined = vendorTraceEnabled()
|
|
1645
1040
|
? { plugins: [], entry: built[0]!.entry }
|
|
1646
|
-
: undefined
|
|
1647
|
-
const traceSeq = trace ? nextVendorTraceSeq() : 0
|
|
1648
|
-
const traceStart = performance.now()
|
|
1041
|
+
: undefined
|
|
1042
|
+
const traceSeq = trace ? nextVendorTraceSeq() : 0
|
|
1043
|
+
const traceStart = performance.now()
|
|
1649
1044
|
const traceRow = (outputs: { text: string }[] | undefined, error?: unknown) => {
|
|
1650
|
-
if (!trace) return
|
|
1045
|
+
if (!trace) return
|
|
1651
1046
|
traceVendorBuild({
|
|
1652
1047
|
seq: traceSeq,
|
|
1653
1048
|
specifier: packageName,
|
|
@@ -1660,11 +1055,11 @@ async function buildVendorGroup(
|
|
|
1660
1055
|
trace,
|
|
1661
1056
|
ok: !error,
|
|
1662
1057
|
...(error
|
|
1663
|
-
// eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
1664
|
-
|
|
1058
|
+
? // eslint-disable-next-line @typescript-eslint/no-base-to-string
|
|
1059
|
+
{ error: (error instanceof Error ? error.message : String(error)).slice(0, 300) }
|
|
1665
1060
|
: {}),
|
|
1666
|
-
})
|
|
1667
|
-
}
|
|
1061
|
+
})
|
|
1062
|
+
}
|
|
1668
1063
|
const preplan =
|
|
1669
1064
|
vendorPreplanEnabled() &&
|
|
1670
1065
|
(
|
|
@@ -1673,8 +1068,8 @@ async function buildVendorGroup(
|
|
|
1673
1068
|
vendorPreplanPackageSafe(config, packageName, member.entry, target, conditionTarget),
|
|
1674
1069
|
),
|
|
1675
1070
|
)
|
|
1676
|
-
).every(Boolean)
|
|
1677
|
-
let outputFiles
|
|
1071
|
+
).every(Boolean)
|
|
1072
|
+
let outputFiles
|
|
1678
1073
|
try {
|
|
1679
1074
|
outputFiles = await profileRuntimeStep(
|
|
1680
1075
|
`vendor bundle ${packageName} [${target}/${conditionTarget}] (${built.length} entries)`,
|
|
@@ -1715,27 +1110,24 @@ async function buildVendorGroup(
|
|
|
1715
1110
|
trace,
|
|
1716
1111
|
preplan,
|
|
1717
1112
|
)),
|
|
1718
|
-
)
|
|
1113
|
+
)
|
|
1719
1114
|
} catch (error) {
|
|
1720
|
-
traceRow(undefined, error)
|
|
1721
|
-
throw error
|
|
1115
|
+
traceRow(undefined, error)
|
|
1116
|
+
throw error
|
|
1722
1117
|
}
|
|
1723
|
-
traceRow(outputFiles)
|
|
1724
|
-
await mkdir(path.join(outdir, 'chunks'), { recursive: true })
|
|
1118
|
+
traceRow(outputFiles)
|
|
1119
|
+
await mkdir(path.join(outdir, 'chunks'), { recursive: true })
|
|
1725
1120
|
// Chunks first: an entry must never be readable before what it imports is.
|
|
1726
1121
|
for (const output of [...outputFiles].sort(
|
|
1727
1122
|
(a, b) => Number(entryFiles.has(a.path)) - Number(entryFiles.has(b.path)),
|
|
1728
1123
|
)) {
|
|
1729
|
-
const bundled = entryFiles.has(output.path)
|
|
1730
|
-
? addCommonJsNamedExports(output.text)
|
|
1731
|
-
: output.text;
|
|
1124
|
+
const bundled = entryFiles.has(output.path) ? addCommonJsNamedExports(output.text) : output.text
|
|
1732
1125
|
// Packages can ship 'use cache' functions too (Next allows it); client
|
|
1733
1126
|
// bundles keep the source untouched.
|
|
1734
|
-
const code =
|
|
1735
|
-
|
|
1736
|
-
await writeVendorArtifact(output.path, code);
|
|
1127
|
+
const code = target === 'server' ? applyBundledSourceTransforms(bundled, output.path) : bundled
|
|
1128
|
+
await writeVendorArtifact(output.path, code)
|
|
1737
1129
|
}
|
|
1738
|
-
await emitNestedExternalTraceCopies(built[0]!.file, context.nestedExternalFiles)
|
|
1130
|
+
await emitNestedExternalTraceCopies(built[0]!.file, context.nestedExternalFiles)
|
|
1739
1131
|
}
|
|
1740
1132
|
|
|
1741
1133
|
// vendor plugin-callback attribution (PNEXT_VENDOR_PROFILE=1, dormant otherwise). Every
|
|
@@ -1743,32 +1135,32 @@ async function buildVendorGroup(
|
|
|
1743
1135
|
// answers them; counts separate "our callbacks are slow" from "we answer many questions".
|
|
1744
1136
|
|
|
1745
1137
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1746
|
-
const vendorProfileEnabled = process.env.PNEXT_VENDOR_PROFILE === '1'
|
|
1747
|
-
const vendorProfileTotals = new Map<string, number>()
|
|
1138
|
+
const vendorProfileEnabled = process.env.PNEXT_VENDOR_PROFILE === '1'
|
|
1139
|
+
const vendorProfileTotals = new Map<string, number>()
|
|
1748
1140
|
|
|
1749
1141
|
function vendorProfileCount(key: string, by = 1) {
|
|
1750
|
-
vendorProfileTotals.set(key, (vendorProfileTotals.get(key) ?? 0) + by)
|
|
1142
|
+
vendorProfileTotals.set(key, (vendorProfileTotals.get(key) ?? 0) + by)
|
|
1751
1143
|
}
|
|
1752
1144
|
|
|
1753
1145
|
export function vendorProfileReport() {
|
|
1754
1146
|
return Object.fromEntries(
|
|
1755
1147
|
[...vendorProfileTotals].sort((a, b) => b[1] - a[1]).map(([k, v]) => [k, Math.round(v)]),
|
|
1756
|
-
)
|
|
1148
|
+
)
|
|
1757
1149
|
}
|
|
1758
1150
|
|
|
1759
1151
|
// Printed once the callbacks go quiet, so one page's whole plugin chain lands
|
|
1760
1152
|
// in a single line instead of interleaving with the build it is measuring.
|
|
1761
|
-
let vendorProfileFlush: ReturnType<typeof setTimeout> | undefined
|
|
1153
|
+
let vendorProfileFlush: ReturnType<typeof setTimeout> | undefined
|
|
1762
1154
|
function scheduleVendorProfileReport() {
|
|
1763
|
-
if (vendorProfileFlush) clearTimeout(vendorProfileFlush)
|
|
1155
|
+
if (vendorProfileFlush) clearTimeout(vendorProfileFlush)
|
|
1764
1156
|
vendorProfileFlush = setTimeout(() => {
|
|
1765
|
-
console.log(`pnext vendor profile ${JSON.stringify(vendorProfileReport())}`)
|
|
1766
|
-
}, 3000)
|
|
1767
|
-
vendorProfileFlush.unref?.()
|
|
1157
|
+
console.log(`pnext vendor profile ${JSON.stringify(vendorProfileReport())}`)
|
|
1158
|
+
}, 3000)
|
|
1159
|
+
vendorProfileFlush.unref?.()
|
|
1768
1160
|
}
|
|
1769
1161
|
|
|
1770
1162
|
function profiledVendorPlugins(plugins: Plugin[]): Plugin[] {
|
|
1771
|
-
if (!vendorProfileEnabled) return plugins
|
|
1163
|
+
if (!vendorProfileEnabled) return plugins
|
|
1772
1164
|
return plugins.map(plugin => ({
|
|
1773
1165
|
name: plugin.name,
|
|
1774
1166
|
setup(build) {
|
|
@@ -1776,22 +1168,22 @@ function profiledVendorPlugins(plugins: Plugin[]): Plugin[] {
|
|
|
1776
1168
|
(kind: 'resolve' | 'load', register: (options: any, cb: any) => void) =>
|
|
1777
1169
|
(options: any, callback: any) =>
|
|
1778
1170
|
register(options, async (args: any) => {
|
|
1779
|
-
vendorProfileCount(`${plugin.name}:${kind}#`)
|
|
1780
|
-
const start = performance.now()
|
|
1171
|
+
vendorProfileCount(`${plugin.name}:${kind}#`)
|
|
1172
|
+
const start = performance.now()
|
|
1781
1173
|
try {
|
|
1782
|
-
return await (callback as (a: unknown) => unknown)(args)
|
|
1174
|
+
return await (callback as (a: unknown) => unknown)(args)
|
|
1783
1175
|
} finally {
|
|
1784
|
-
vendorProfileCount(`${plugin.name}:${kind}ms`, performance.now() - start)
|
|
1785
|
-
scheduleVendorProfileReport()
|
|
1176
|
+
vendorProfileCount(`${plugin.name}:${kind}ms`, performance.now() - start)
|
|
1177
|
+
scheduleVendorProfileReport()
|
|
1786
1178
|
}
|
|
1787
|
-
})
|
|
1179
|
+
})
|
|
1788
1180
|
return plugin.setup({
|
|
1789
1181
|
...build,
|
|
1790
1182
|
onResolve: wrap('resolve', build.onResolve.bind(build)),
|
|
1791
1183
|
onLoad: wrap('load', build.onLoad.bind(build)),
|
|
1792
|
-
})
|
|
1184
|
+
})
|
|
1793
1185
|
},
|
|
1794
|
-
}))
|
|
1186
|
+
}))
|
|
1795
1187
|
}
|
|
1796
1188
|
|
|
1797
1189
|
/** The plugin chain, for whatever the plugin-free build could not express. */
|
|
@@ -1808,7 +1200,7 @@ async function buildVendorGroupWithPlugins(
|
|
|
1808
1200
|
trace?: VendorBuildTrace,
|
|
1809
1201
|
preplan = false,
|
|
1810
1202
|
) {
|
|
1811
|
-
const pluginStart = performance.now()
|
|
1203
|
+
const pluginStart = performance.now()
|
|
1812
1204
|
const groupPlugins = profiledVendorPlugins([
|
|
1813
1205
|
serverAssetPlugin(config),
|
|
1814
1206
|
...getBundlerExtensions().serverEsbuildPlugins(
|
|
@@ -1822,8 +1214,8 @@ async function buildVendorGroupWithPlugins(
|
|
|
1822
1214
|
vendorGroupSiblingPlugin(siblings),
|
|
1823
1215
|
externalPackageDependencyPlugin(config, packageName, target, conditionTarget, context, preplan),
|
|
1824
1216
|
externalBuiltinBuildPlugin(),
|
|
1825
|
-
])
|
|
1826
|
-
if (trace) trace.plugins = groupPlugins.map(plugin => plugin.name)
|
|
1217
|
+
])
|
|
1218
|
+
if (trace) trace.plugins = groupPlugins.map(plugin => plugin.name)
|
|
1827
1219
|
const result = await build({
|
|
1828
1220
|
entryPoints,
|
|
1829
1221
|
absWorkingDir: resolveDir,
|
|
@@ -1846,11 +1238,11 @@ async function buildVendorGroupWithPlugins(
|
|
|
1846
1238
|
...(trace ? { metafile: true as const } : {}),
|
|
1847
1239
|
...serverDefineOptions(),
|
|
1848
1240
|
plugins: groupPlugins,
|
|
1849
|
-
})
|
|
1850
|
-
vendorNativeStats.pluginBuilds += 1
|
|
1851
|
-
vendorNativeStats.pluginBuildMs += performance.now() - pluginStart
|
|
1852
|
-
if (trace) trace.metafile = result.metafile
|
|
1853
|
-
return result.outputFiles.map(output => ({ path: output.path, text: output.text }))
|
|
1241
|
+
})
|
|
1242
|
+
vendorNativeStats.pluginBuilds += 1
|
|
1243
|
+
vendorNativeStats.pluginBuildMs += performance.now() - pluginStart
|
|
1244
|
+
if (trace) trace.metafile = result.metafile
|
|
1245
|
+
return result.outputFiles.map(output => ({ path: output.path, text: output.text }))
|
|
1854
1246
|
}
|
|
1855
1247
|
|
|
1856
1248
|
/** Siblings already compiled stay out of the graph, as plain externals. */
|
|
@@ -1858,14 +1250,14 @@ function vendorGroupSiblingPlugin(siblings: ReadonlyMap<string, string>): Plugin
|
|
|
1858
1250
|
return {
|
|
1859
1251
|
name: 'pnext-vendor-group-siblings',
|
|
1860
1252
|
setup(build) {
|
|
1861
|
-
if (siblings.size === 0) return
|
|
1253
|
+
if (siblings.size === 0) return
|
|
1862
1254
|
build.onResolve({ filter: /^[^./].*/ }, args => {
|
|
1863
|
-
if (args.kind === 'entry-point') return undefined
|
|
1864
|
-
const sibling = siblings.get(args.path)
|
|
1865
|
-
return sibling ? { path: sibling, external: true } : undefined
|
|
1866
|
-
})
|
|
1255
|
+
if (args.kind === 'entry-point') return undefined
|
|
1256
|
+
const sibling = siblings.get(args.path)
|
|
1257
|
+
return sibling ? { path: sibling, external: true } : undefined
|
|
1258
|
+
})
|
|
1867
1259
|
},
|
|
1868
|
-
}
|
|
1260
|
+
}
|
|
1869
1261
|
}
|
|
1870
1262
|
|
|
1871
1263
|
// per-file client boundaries inside a package.
|
|
@@ -1890,15 +1282,15 @@ function vendorGroupSiblingPlugin(siblings: ReadonlyMap<string, string>): Plugin
|
|
|
1890
1282
|
const vendorClientBoundaryLoads = new WeakMap<
|
|
1891
1283
|
ResolvedConfig,
|
|
1892
1284
|
(file: string) => Promise<OnLoadResult | undefined>
|
|
1893
|
-
>()
|
|
1285
|
+
>()
|
|
1894
1286
|
|
|
1895
1287
|
function vendorClientBoundaryLoad(config: ResolvedConfig) {
|
|
1896
|
-
let load = vendorClientBoundaryLoads.get(config)
|
|
1897
|
-
if (load) return load
|
|
1898
|
-
const memo = new Map<string, Promise<OnLoadResult | undefined>>()
|
|
1288
|
+
let load = vendorClientBoundaryLoads.get(config)
|
|
1289
|
+
if (load) return load
|
|
1290
|
+
const memo = new Map<string, Promise<OnLoadResult | undefined>>()
|
|
1899
1291
|
const scan = async (file: string): Promise<OnLoadResult | undefined> => {
|
|
1900
|
-
const source = await readFile(file, 'utf8').catch(() => undefined)
|
|
1901
|
-
if (source === undefined || !hasUseClientDirective(source)) return undefined
|
|
1292
|
+
const source = await readFile(file, 'utf8').catch(() => undefined)
|
|
1293
|
+
if (source === undefined || !hasUseClientDirective(source)) return undefined
|
|
1902
1294
|
return {
|
|
1903
1295
|
contents: clientReferenceModuleSource(
|
|
1904
1296
|
file,
|
|
@@ -1907,21 +1299,21 @@ function vendorClientBoundaryLoad(config: ResolvedConfig) {
|
|
|
1907
1299
|
),
|
|
1908
1300
|
loader: 'js',
|
|
1909
1301
|
resolveDir: path.dirname(file),
|
|
1910
|
-
}
|
|
1911
|
-
}
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1912
1304
|
// Only node_modules answers are cached: immutable for the process life, the
|
|
1913
1305
|
// same assumption the vendor artifact cache makes. First-party files re-read.
|
|
1914
1306
|
load = file => {
|
|
1915
|
-
if (!file.includes(`${path.sep}node_modules${path.sep}`)) return scan(file)
|
|
1916
|
-
let pending = memo.get(file)
|
|
1307
|
+
if (!file.includes(`${path.sep}node_modules${path.sep}`)) return scan(file)
|
|
1308
|
+
let pending = memo.get(file)
|
|
1917
1309
|
if (!pending) {
|
|
1918
|
-
pending = scan(file)
|
|
1919
|
-
memo.set(file, pending)
|
|
1310
|
+
pending = scan(file)
|
|
1311
|
+
memo.set(file, pending)
|
|
1920
1312
|
}
|
|
1921
|
-
return pending
|
|
1922
|
-
}
|
|
1923
|
-
vendorClientBoundaryLoads.set(config, load)
|
|
1924
|
-
return load
|
|
1313
|
+
return pending
|
|
1314
|
+
}
|
|
1315
|
+
vendorClientBoundaryLoads.set(config, load)
|
|
1316
|
+
return load
|
|
1925
1317
|
}
|
|
1926
1318
|
|
|
1927
1319
|
/**
|
|
@@ -1942,12 +1334,12 @@ function vendorPluginOptions(
|
|
|
1942
1334
|
!isPagesBundleTarget(conditionTarget) &&
|
|
1943
1335
|
reactCompatEnabled(config)
|
|
1944
1336
|
? vendorClientBoundaryLoad(config)
|
|
1945
|
-
: undefined
|
|
1946
|
-
return { vendorClientBoundary: boundary, realPathEntries: true }
|
|
1337
|
+
: undefined
|
|
1338
|
+
return { vendorClientBoundary: boundary, realPathEntries: true }
|
|
1947
1339
|
}
|
|
1948
1340
|
|
|
1949
1341
|
function isPagesBundleTarget(target: ServerBundleTarget) {
|
|
1950
|
-
return target === 'pages' || target === 'pages-api' || target === 'pages-api-edge'
|
|
1342
|
+
return target === 'pages' || target === 'pages-api' || target === 'pages-api-edge'
|
|
1951
1343
|
}
|
|
1952
1344
|
|
|
1953
1345
|
// plugin-free vendor builds.
|
|
@@ -1984,7 +1376,7 @@ function isPagesBundleTarget(target: ServerBundleTarget) {
|
|
|
1984
1376
|
*/
|
|
1985
1377
|
function vendorNativeEnabled() {
|
|
1986
1378
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1987
|
-
return process.env.PNEXT_VENDOR_NATIVE === '1'
|
|
1379
|
+
return process.env.PNEXT_VENDOR_NATIVE === '1'
|
|
1988
1380
|
}
|
|
1989
1381
|
|
|
1990
1382
|
/**
|
|
@@ -1993,7 +1385,7 @@ function vendorNativeEnabled() {
|
|
|
1993
1385
|
* an asset needing real contents), not of the demand, so a retry pays for the
|
|
1994
1386
|
* discarded build again and lands in the same place.
|
|
1995
1387
|
*/
|
|
1996
|
-
const vendorNativeBailed = new Set<string>()
|
|
1388
|
+
const vendorNativeBailed = new Set<string>()
|
|
1997
1389
|
|
|
1998
1390
|
/** How the vendor builds of this process split between the two paths. */
|
|
1999
1391
|
const vendorNativeStats = {
|
|
@@ -2007,10 +1399,10 @@ const vendorNativeStats = {
|
|
|
2007
1399
|
planMs: 0,
|
|
2008
1400
|
verifyMs: 0,
|
|
2009
1401
|
reasons: [] as string[],
|
|
2010
|
-
}
|
|
1402
|
+
}
|
|
2011
1403
|
|
|
2012
1404
|
export function vendorNativeReport() {
|
|
2013
|
-
return { ...vendorNativeStats, reasons: vendorNativeStats.reasons.slice(0, 40) }
|
|
1405
|
+
return { ...vendorNativeStats, reasons: vendorNativeStats.reasons.slice(0, 40) }
|
|
2014
1406
|
}
|
|
2015
1407
|
|
|
2016
1408
|
/**
|
|
@@ -2023,44 +1415,44 @@ const vendorEmptyLoaders = {
|
|
|
2023
1415
|
'.scss': 'empty',
|
|
2024
1416
|
'.sass': 'empty',
|
|
2025
1417
|
'.less': 'empty',
|
|
2026
|
-
} satisfies Record<string, Loader
|
|
1418
|
+
} satisfies Record<string, Loader>
|
|
2027
1419
|
|
|
2028
1420
|
/** Discovery is a fixpoint; past this a package is not worth another try. */
|
|
2029
|
-
const MAX_VENDOR_NATIVE_ROUNDS = 2
|
|
1421
|
+
const MAX_VENDOR_NATIVE_ROUNDS = 2
|
|
2030
1422
|
|
|
2031
1423
|
interface VendorNativeRequest {
|
|
2032
|
-
config: ResolvedConfig
|
|
2033
|
-
target: CompatAliasTarget
|
|
2034
|
-
conditionTarget: ServerBundleTarget
|
|
2035
|
-
resolveDir: string
|
|
1424
|
+
config: ResolvedConfig
|
|
1425
|
+
target: CompatAliasTarget
|
|
1426
|
+
conditionTarget: ServerBundleTarget
|
|
1427
|
+
resolveDir: string
|
|
2036
1428
|
/** Cache key for the bail set — the artifact this build is producing. */
|
|
2037
|
-
bailKey: string
|
|
1429
|
+
bailKey: string
|
|
2038
1430
|
/** The package the entries belong to; its own subpaths are inlined. */
|
|
2039
|
-
packageName: string | undefined
|
|
2040
|
-
entryPoints: { in: string; out: string }[] | string[]
|
|
2041
|
-
outdir?: string
|
|
2042
|
-
splitting: boolean
|
|
1431
|
+
packageName: string | undefined
|
|
1432
|
+
entryPoints: { in: string; out: string }[] | string[]
|
|
1433
|
+
outdir?: string
|
|
1434
|
+
splitting: boolean
|
|
2043
1435
|
/** Demanded siblings already on disk (group builds only). */
|
|
2044
|
-
siblings?: ReadonlyMap<string, string
|
|
1436
|
+
siblings?: ReadonlyMap<string, string>
|
|
2045
1437
|
/** Entries THIS build emits, by resolved entry file: artifacts-to-be. */
|
|
2046
|
-
ownEntries?: ReadonlyMap<string, string
|
|
1438
|
+
ownEntries?: ReadonlyMap<string, string>
|
|
2047
1439
|
/** Raised from inside another build: it may never wait on anything shared. */
|
|
2048
|
-
nested: boolean
|
|
1440
|
+
nested: boolean
|
|
2049
1441
|
/** Preplan-safe package: native build with pure ref decisions, no verify. */
|
|
2050
|
-
preplan?: boolean
|
|
1442
|
+
preplan?: boolean
|
|
2051
1443
|
/** Entry outputs, by emitted path; everything else is a shared chunk. */
|
|
2052
|
-
isEntry: (outputPath: string) => boolean
|
|
1444
|
+
isEntry: (outputPath: string) => boolean
|
|
2053
1445
|
/** Where a solo build's single output lands, for the verification gate. */
|
|
2054
|
-
soloFile?: string
|
|
2055
|
-
context: { nestedExternalFiles: string[] }
|
|
1446
|
+
soloFile?: string
|
|
1447
|
+
context: { nestedExternalFiles: string[] }
|
|
2056
1448
|
/** Trace-only: filled with the producing pass's plugins/metafile. */
|
|
2057
|
-
trace?: VendorBuildTrace
|
|
1449
|
+
trace?: VendorBuildTrace
|
|
2058
1450
|
}
|
|
2059
1451
|
|
|
2060
1452
|
/** A repointed reference: the specifier to emit, and the file it must exist at. */
|
|
2061
1453
|
interface VendorRefTarget {
|
|
2062
|
-
specifier: string
|
|
2063
|
-
file: string
|
|
1454
|
+
specifier: string
|
|
1455
|
+
file: string
|
|
2064
1456
|
}
|
|
2065
1457
|
|
|
2066
1458
|
/**
|
|
@@ -2068,45 +1460,42 @@ interface VendorRefTarget {
|
|
|
2068
1460
|
* verified one is a bail, so a caller can treat undefined as "nothing happened" - no artifact is written here.
|
|
2069
1461
|
*/
|
|
2070
1462
|
async function buildVendorNative(request: VendorNativeRequest) {
|
|
2071
|
-
vendorNativeStats.attempts += 1
|
|
2072
|
-
if (
|
|
2073
|
-
|
|
2074
|
-
vendorNativeBailed.has(request.bailKey)
|
|
2075
|
-
) {
|
|
2076
|
-
return undefined;
|
|
1463
|
+
vendorNativeStats.attempts += 1
|
|
1464
|
+
if ((!vendorNativeEnabled() && !request.preplan) || vendorNativeBailed.has(request.bailKey)) {
|
|
1465
|
+
return undefined
|
|
2077
1466
|
}
|
|
2078
1467
|
// A bail is remembered so the next demand does not pay for the same
|
|
2079
1468
|
// discarded build — unless the cause was the fixpoint running out of rounds,
|
|
2080
1469
|
// which the package shape this build just recorded has already moved on from.
|
|
2081
1470
|
const bail = (why: string, permanent = true) => {
|
|
2082
|
-
if (permanent) vendorNativeBailed.add(request.bailKey)
|
|
2083
|
-
vendorNativeStats.bailed += 1
|
|
2084
|
-
vendorNativeStats.reasons.push(`${request.packageName ?? '?'}: ${why}`)
|
|
2085
|
-
return undefined
|
|
2086
|
-
}
|
|
1471
|
+
if (permanent) vendorNativeBailed.add(request.bailKey)
|
|
1472
|
+
vendorNativeStats.bailed += 1
|
|
1473
|
+
vendorNativeStats.reasons.push(`${request.packageName ?? '?'}: ${why}`)
|
|
1474
|
+
return undefined
|
|
1475
|
+
}
|
|
2087
1476
|
|
|
2088
|
-
const conditions = serverBundleConditions(request.conditionTarget)
|
|
1477
|
+
const conditions = serverBundleConditions(request.conditionTarget)
|
|
2089
1478
|
// What a package has to keep INSIDE its bundle is a property of the package,
|
|
2090
1479
|
// not of one subpath's build, so it is discovered once and reused by every
|
|
2091
1480
|
// later build of it — the fixpoint the §4e prototype ran app-wide, amortized
|
|
2092
1481
|
// per package here.
|
|
2093
|
-
const shape = vendorPackageShape(request)
|
|
1482
|
+
const shape = vendorPackageShape(request)
|
|
2094
1483
|
// Only the FIRST build of a package discovers its shape; the rest wait for that answer instead of racing to
|
|
2095
1484
|
// the same rounds. Off the slot, because a waiter that keeps its worker is exactly the inversion this is
|
|
2096
1485
|
// here to fix. A nested build never waits - its own parent is holding a slot for it, and a cycle through two
|
|
2097
1486
|
// packages would otherwise have nowhere to go.
|
|
2098
1487
|
if (!request.nested && shape.discovery) {
|
|
2099
|
-
await outsideVendorSlot(() => shape.discovery!)
|
|
1488
|
+
await outsideVendorSlot(() => shape.discovery!)
|
|
2100
1489
|
}
|
|
2101
|
-
const pioneer = !shape.discovery
|
|
2102
|
-
let settleDiscovery: (() => void) | undefined
|
|
2103
|
-
if (pioneer) shape.discovery = new Promise<void>(resolve => (settleDiscovery = resolve))
|
|
1490
|
+
const pioneer = !shape.discovery
|
|
1491
|
+
let settleDiscovery: (() => void) | undefined
|
|
1492
|
+
if (pioneer) shape.discovery = new Promise<void>(resolve => (settleDiscovery = resolve))
|
|
2104
1493
|
try {
|
|
2105
|
-
return await runVendorNativeRounds(request, shape, conditions, bail)
|
|
1494
|
+
return await runVendorNativeRounds(request, shape, conditions, bail)
|
|
2106
1495
|
} finally {
|
|
2107
1496
|
if (pioneer) {
|
|
2108
|
-
shape.discovery = undefined
|
|
2109
|
-
settleDiscovery?.()
|
|
1497
|
+
shape.discovery = undefined
|
|
1498
|
+
settleDiscovery?.()
|
|
2110
1499
|
}
|
|
2111
1500
|
}
|
|
2112
1501
|
}
|
|
@@ -2117,29 +1506,28 @@ async function runVendorNativeRounds(
|
|
|
2117
1506
|
conditions: string[],
|
|
2118
1507
|
bail: (why: string, permanent?: boolean) => undefined,
|
|
2119
1508
|
) {
|
|
2120
|
-
const { config, resolveDir } = request
|
|
1509
|
+
const { config, resolveDir } = request
|
|
2121
1510
|
|
|
2122
1511
|
for (let round = 1; round <= MAX_VENDOR_NATIVE_ROUNDS; round += 1) {
|
|
2123
|
-
let result
|
|
2124
|
-
vendorNativeStats.rounds += 1
|
|
2125
|
-
const buildStart = performance.now()
|
|
1512
|
+
let result
|
|
1513
|
+
vendorNativeStats.rounds += 1
|
|
1514
|
+
const buildStart = performance.now()
|
|
2126
1515
|
const nativePlugins = getBundlerExtensions().serverEsbuildPlugins(
|
|
2127
1516
|
config,
|
|
2128
1517
|
vendorPluginOptions(config, request.target, request.conditionTarget),
|
|
2129
|
-
)
|
|
2130
|
-
if (request.trace) request.trace.plugins = nativePlugins.map(plugin => plugin.name)
|
|
1518
|
+
)
|
|
1519
|
+
if (request.trace) request.trace.plugins = nativePlugins.map(plugin => plugin.name)
|
|
2131
1520
|
try {
|
|
2132
1521
|
result = await build({
|
|
2133
|
-
entryPoints: (request.entryPoints as (string | { in: string; out: string })[]).map(
|
|
2134
|
-
entry
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
in
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
},
|
|
1522
|
+
entryPoints: (request.entryPoints as (string | { in: string; out: string })[]).map(entry =>
|
|
1523
|
+
typeof entry === 'string'
|
|
1524
|
+
? (getBundlerExtensions().serverBundleEntry(entry, resolveDir, entry) ?? entry)
|
|
1525
|
+
: {
|
|
1526
|
+
...entry,
|
|
1527
|
+
in:
|
|
1528
|
+
getBundlerExtensions().serverBundleEntry(entry.in, resolveDir, entry.in) ??
|
|
1529
|
+
entry.in,
|
|
1530
|
+
},
|
|
2143
1531
|
) as string[],
|
|
2144
1532
|
absWorkingDir: resolveDir,
|
|
2145
1533
|
bundle: true,
|
|
@@ -2164,32 +1552,32 @@ async function runVendorNativeRounds(
|
|
|
2164
1552
|
packages: 'external',
|
|
2165
1553
|
...(shape.inline.size > 0 ? { alias: Object.fromEntries(shape.inline) } : {}),
|
|
2166
1554
|
plugins: nativePlugins,
|
|
2167
|
-
})
|
|
1555
|
+
})
|
|
2168
1556
|
} catch (error) {
|
|
2169
|
-
vendorNativeStats.buildMs += performance.now() - buildStart
|
|
2170
|
-
return bail((error instanceof Error ? error.message : String(error)).slice(0, 200))
|
|
1557
|
+
vendorNativeStats.buildMs += performance.now() - buildStart
|
|
1558
|
+
return bail((error instanceof Error ? error.message : String(error)).slice(0, 200))
|
|
2171
1559
|
}
|
|
2172
|
-
vendorNativeStats.buildMs += performance.now() - buildStart
|
|
1560
|
+
vendorNativeStats.buildMs += performance.now() - buildStart
|
|
2173
1561
|
|
|
2174
|
-
const inputs = Object.keys(result.metafile.inputs)
|
|
1562
|
+
const inputs = Object.keys(result.metafile.inputs)
|
|
2175
1563
|
// An asset whose stub carries content, or one a configured loader rule
|
|
2176
1564
|
// claims, is not what `loader: empty` produced. Hand it to the plugins.
|
|
2177
1565
|
for (const input of inputs) {
|
|
2178
|
-
const file = path.resolve(resolveDir, input)
|
|
2179
|
-
if (!serverIgnoredAssetFilter.test(file)) continue
|
|
2180
|
-
if (getAssetExtensions().hasLoaderRuleFor(file)) return bail(`loader rule for ${input}`)
|
|
1566
|
+
const file = path.resolve(resolveDir, input)
|
|
1567
|
+
if (!serverIgnoredAssetFilter.test(file)) continue
|
|
1568
|
+
if (getAssetExtensions().hasLoaderRuleFor(file)) return bail(`loader rule for ${input}`)
|
|
2181
1569
|
if (getCssExtensions().loadCssModuleForClient(file) !== undefined) {
|
|
2182
|
-
return bail(`css module ${input}`)
|
|
1570
|
+
return bail(`css module ${input}`)
|
|
2183
1571
|
}
|
|
2184
1572
|
}
|
|
2185
1573
|
// Preplan class is ESM-only across the GRAPH, not just the entry.
|
|
2186
1574
|
if (request.preplan) {
|
|
2187
1575
|
for (const input of inputs) {
|
|
2188
|
-
const file = path.resolve(resolveDir, input)
|
|
2189
|
-
if (serverIgnoredAssetFilter.test(file) || !cachedExistsSync(file)) continue
|
|
2190
|
-
if (/\.[cm]?tsx?$/.test(file)) return bail(`ts input ${input}`)
|
|
2191
|
-
if (file.endsWith('.cjs') ||(/\.jsx?$/.test(file) && !isEsmModuleEntry(file))) {
|
|
2192
|
-
return bail(`cjs input ${input}`)
|
|
1576
|
+
const file = path.resolve(resolveDir, input)
|
|
1577
|
+
if (serverIgnoredAssetFilter.test(file) || !cachedExistsSync(file)) continue
|
|
1578
|
+
if (/\.[cm]?tsx?$/.test(file)) return bail(`ts input ${input}`)
|
|
1579
|
+
if (file.endsWith('.cjs') || (/\.jsx?$/.test(file) && !isEsmModuleEntry(file))) {
|
|
1580
|
+
return bail(`cjs input ${input}`)
|
|
2193
1581
|
}
|
|
2194
1582
|
}
|
|
2195
1583
|
}
|
|
@@ -2198,60 +1586,61 @@ async function runVendorNativeRounds(
|
|
|
2198
1586
|
// the worker has already handed its output over. Nothing else in the
|
|
2199
1587
|
// pipeline can do this — the plugin chain's discovery runs inside a resolve
|
|
2200
1588
|
// callback, with the bundler still open behind it.
|
|
2201
|
-
const planStart = performance.now()
|
|
1589
|
+
const planStart = performance.now()
|
|
2202
1590
|
const plan = await outsideVendorSlot(() =>
|
|
2203
1591
|
planVendorNativeRefs(request, result.metafile, conditions),
|
|
2204
|
-
)
|
|
2205
|
-
vendorNativeStats.planMs += performance.now() - planStart
|
|
2206
|
-
if (!plan) return bail('unrepresentable reference')
|
|
1592
|
+
)
|
|
1593
|
+
vendorNativeStats.planMs += performance.now() - planStart
|
|
1594
|
+
if (!plan) return bail('unrepresentable reference')
|
|
2207
1595
|
if (plan.inline.size > 0) {
|
|
2208
1596
|
// Record before deciding: even a bail teaches the package's shape, so the
|
|
2209
1597
|
// next build of it starts where this one gave up.
|
|
2210
|
-
let grew = false
|
|
1598
|
+
let grew = false
|
|
2211
1599
|
for (const [specifier, file] of plan.inline) {
|
|
2212
|
-
if (shape.inline.has(specifier)) continue
|
|
2213
|
-
shape.inline.set(specifier, file)
|
|
2214
|
-
grew = true
|
|
1600
|
+
if (shape.inline.has(specifier)) continue
|
|
1601
|
+
shape.inline.set(specifier, file)
|
|
1602
|
+
grew = true
|
|
2215
1603
|
}
|
|
2216
|
-
if (!grew) return bail('inline set stopped converging')
|
|
2217
|
-
if (round === MAX_VENDOR_NATIVE_ROUNDS) return bail('inlining unsettled', false)
|
|
2218
|
-
continue
|
|
1604
|
+
if (!grew) return bail('inline set stopped converging')
|
|
1605
|
+
if (round === MAX_VENDOR_NATIVE_ROUNDS) return bail('inlining unsettled', false)
|
|
1606
|
+
continue
|
|
2219
1607
|
}
|
|
2220
1608
|
|
|
2221
1609
|
const outputs = result.outputFiles.map(output => ({
|
|
2222
1610
|
path: request.soloFile ?? output.path,
|
|
2223
|
-
text: rewriteEmittedRefs(
|
|
2224
|
-
|
|
1611
|
+
text: rewriteEmittedRefs(
|
|
1612
|
+
output.text,
|
|
1613
|
+
ref => (ref.require ? plan.requires : plan.imports).get(ref.specifier)?.specifier,
|
|
2225
1614
|
),
|
|
2226
|
-
}))
|
|
1615
|
+
}))
|
|
2227
1616
|
const repointed = new Map(
|
|
2228
1617
|
[...plan.imports.values(), ...plan.requires.values()].map(t => [t.specifier, t.file]),
|
|
2229
|
-
)
|
|
1618
|
+
)
|
|
2230
1619
|
// Preplan mode: the drain barrier + byte-eq gates stand in for the
|
|
2231
1620
|
// re-bundle verification loop — its cost is one of the two this composes out.
|
|
2232
1621
|
if (!request.preplan) {
|
|
2233
|
-
const verifyStart = performance.now()
|
|
1622
|
+
const verifyStart = performance.now()
|
|
2234
1623
|
const chunks = new Map(
|
|
2235
1624
|
outputs.filter(output => !request.isEntry(output.path)).map(o => [o.path, o.text]),
|
|
2236
|
-
)
|
|
1625
|
+
)
|
|
2237
1626
|
for (const output of outputs) {
|
|
2238
|
-
if (!request.isEntry(output.path)) continue
|
|
2239
|
-
const failure = await verifyVendorArtifact(output.path, output.text, repointed, chunks)
|
|
1627
|
+
if (!request.isEntry(output.path)) continue
|
|
1628
|
+
const failure = await verifyVendorArtifact(output.path, output.text, repointed, chunks)
|
|
2240
1629
|
if (failure) {
|
|
2241
|
-
vendorNativeStats.verifyMs += performance.now() - verifyStart
|
|
2242
|
-
return bail(`verification: ${failure}`)
|
|
1630
|
+
vendorNativeStats.verifyMs += performance.now() - verifyStart
|
|
1631
|
+
return bail(`verification: ${failure}`)
|
|
2243
1632
|
}
|
|
2244
1633
|
}
|
|
2245
|
-
vendorNativeStats.verifyMs += performance.now() - verifyStart
|
|
1634
|
+
vendorNativeStats.verifyMs += performance.now() - verifyStart
|
|
2246
1635
|
} else {
|
|
2247
|
-
vendorPreplanStats.composedBuilds += 1
|
|
2248
|
-
schedulePreplanReport()
|
|
1636
|
+
vendorPreplanStats.composedBuilds += 1
|
|
1637
|
+
schedulePreplanReport()
|
|
2249
1638
|
}
|
|
2250
|
-
vendorNativeStats.native += 1
|
|
2251
|
-
if (request.trace) request.trace.metafile = result.metafile
|
|
2252
|
-
return outputs
|
|
1639
|
+
vendorNativeStats.native += 1
|
|
1640
|
+
if (request.trace) request.trace.metafile = result.metafile
|
|
1641
|
+
return outputs
|
|
2253
1642
|
}
|
|
2254
|
-
return undefined
|
|
1643
|
+
return undefined
|
|
2255
1644
|
}
|
|
2256
1645
|
|
|
2257
1646
|
/**
|
|
@@ -2262,38 +1651,38 @@ async function runVendorNativeRounds(
|
|
|
2262
1651
|
*/
|
|
2263
1652
|
interface VendorPackageShape {
|
|
2264
1653
|
/** Specifier -> file it must be bundled from, as an esbuild `alias` entry. */
|
|
2265
|
-
inline: Map<string, string
|
|
1654
|
+
inline: Map<string, string>
|
|
2266
1655
|
/** Set while one build is discovering it; every other build awaits this. */
|
|
2267
|
-
discovery?: Promise<void
|
|
1656
|
+
discovery?: Promise<void>
|
|
2268
1657
|
}
|
|
2269
1658
|
|
|
2270
|
-
const vendorPackageShapes = new Map<string, VendorPackageShape>()
|
|
1659
|
+
const vendorPackageShapes = new Map<string, VendorPackageShape>()
|
|
2271
1660
|
|
|
2272
1661
|
function vendorPackageShape(request: VendorNativeRequest): VendorPackageShape {
|
|
2273
|
-
const entry = (request.entryPoints as { in?: string }[])[0]
|
|
2274
|
-
const first = typeof entry === 'string' ? entry : entry?.in
|
|
1662
|
+
const entry = (request.entryPoints as { in?: string }[])[0]
|
|
1663
|
+
const first = typeof entry === 'string' ? entry : entry?.in
|
|
2275
1664
|
// A workspace package resolves to its real source path, which has no
|
|
2276
1665
|
// `node_modules` segment to key on — walk up to its manifest instead.
|
|
2277
1666
|
const root =
|
|
2278
1667
|
(first && request.packageName && vendorPackageRoot(first, request.packageName)) ??
|
|
2279
|
-
(first && nearestManifestDir(path.dirname(first)))
|
|
2280
|
-
if (!root) return { inline: new Map() }
|
|
2281
|
-
const key = `${root}\0${request.target}\0${request.conditionTarget}
|
|
2282
|
-
let shape = vendorPackageShapes.get(key)
|
|
1668
|
+
(first && nearestManifestDir(path.dirname(first)))
|
|
1669
|
+
if (!root) return { inline: new Map() }
|
|
1670
|
+
const key = `${root}\0${request.target}\0${request.conditionTarget}`
|
|
1671
|
+
let shape = vendorPackageShapes.get(key)
|
|
2283
1672
|
if (!shape) {
|
|
2284
|
-
shape = { inline: new Map() }
|
|
2285
|
-
vendorPackageShapes.set(key, shape)
|
|
1673
|
+
shape = { inline: new Map() }
|
|
1674
|
+
vendorPackageShapes.set(key, shape)
|
|
2286
1675
|
}
|
|
2287
|
-
return shape
|
|
1676
|
+
return shape
|
|
2288
1677
|
}
|
|
2289
1678
|
|
|
2290
1679
|
function nearestManifestDir(from: string) {
|
|
2291
|
-
let dir = from
|
|
1680
|
+
let dir = from
|
|
2292
1681
|
while (true) {
|
|
2293
|
-
if (existsSync(path.join(dir, 'package.json'))) return dir
|
|
2294
|
-
const parent = path.dirname(dir)
|
|
2295
|
-
if (parent === dir) return undefined
|
|
2296
|
-
dir = parent
|
|
1682
|
+
if (existsSync(path.join(dir, 'package.json'))) return dir
|
|
1683
|
+
const parent = path.dirname(dir)
|
|
1684
|
+
if (parent === dir) return undefined
|
|
1685
|
+
dir = parent
|
|
2297
1686
|
}
|
|
2298
1687
|
}
|
|
2299
1688
|
|
|
@@ -2307,41 +1696,41 @@ async function planVendorNativeRefs(
|
|
|
2307
1696
|
metafile: Metafile,
|
|
2308
1697
|
conditions: string[],
|
|
2309
1698
|
) {
|
|
2310
|
-
const imports = new Map<string, VendorRefTarget>()
|
|
2311
|
-
const requires = new Map<string, VendorRefTarget>()
|
|
2312
|
-
const inline = new Map<string, string>()
|
|
2313
|
-
const settled = new Set<string>()
|
|
1699
|
+
const imports = new Map<string, VendorRefTarget>()
|
|
1700
|
+
const requires = new Map<string, VendorRefTarget>()
|
|
1701
|
+
const inline = new Map<string, string>()
|
|
1702
|
+
const settled = new Set<string>()
|
|
2314
1703
|
|
|
2315
1704
|
for (const [input, meta] of Object.entries(metafile.inputs)) {
|
|
2316
1705
|
// Decisions are a property of the DIRECTORY an import sits in, never of the
|
|
2317
1706
|
// file — so a package's several hundred modules all asking for `react`
|
|
2318
1707
|
// decide once, and the answer is reused across builds.
|
|
2319
|
-
const importerFile = path.resolve(request.resolveDir, input)
|
|
2320
|
-
const importerDir = path.dirname(importerFile)
|
|
1708
|
+
const importerFile = path.resolve(request.resolveDir, input)
|
|
1709
|
+
const importerDir = path.dirname(importerFile)
|
|
2321
1710
|
for (const reference of meta.imports) {
|
|
2322
|
-
if (!reference.external) continue
|
|
2323
|
-
const specifier = reference.path
|
|
2324
|
-
const isRequire = reference.kind === 'require-call'
|
|
2325
|
-
const key = `${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}
|
|
2326
|
-
if (settled.has(key)) continue
|
|
2327
|
-
settled.add(key)
|
|
1711
|
+
if (!reference.external) continue
|
|
1712
|
+
const specifier = reference.path
|
|
1713
|
+
const isRequire = reference.kind === 'require-call'
|
|
1714
|
+
const key = `${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`
|
|
1715
|
+
if (settled.has(key)) continue
|
|
1716
|
+
settled.add(key)
|
|
2328
1717
|
|
|
2329
1718
|
// Same package. A subpath THIS build already emits is repointed at that artifact; a sibling on disk at
|
|
2330
1719
|
// its own. Everything else becomes its own vendor demand rather than being inlined here - inlining is
|
|
2331
1720
|
// what makes a package's shared graph get re-parsed once per subpath, the cost this whole pipeline
|
|
2332
1721
|
// exists to remove. Request-scoped, so it sits in front of the shared decision cache.
|
|
2333
1722
|
if (packageNameFromSpecifier(specifier) === request.packageName) {
|
|
2334
|
-
const sibling = request.siblings?.get(specifier)
|
|
1723
|
+
const sibling = request.siblings?.get(specifier)
|
|
2335
1724
|
if (sibling) {
|
|
2336
1725
|
if (!record(imports, specifier, { specifier: sibling, file: fileURLToPath(sibling) })) {
|
|
2337
|
-
return undefined
|
|
1726
|
+
return undefined
|
|
2338
1727
|
}
|
|
2339
|
-
continue
|
|
1728
|
+
continue
|
|
2340
1729
|
}
|
|
2341
|
-
const own = ownEntryArtifact(request, specifier, importerDir, conditions)
|
|
1730
|
+
const own = ownEntryArtifact(request, specifier, importerDir, conditions)
|
|
2342
1731
|
if (own) {
|
|
2343
|
-
if (!record(imports, specifier, own)) return undefined
|
|
2344
|
-
continue
|
|
1732
|
+
if (!record(imports, specifier, own)) return undefined
|
|
1733
|
+
continue
|
|
2345
1734
|
}
|
|
2346
1735
|
// A subpath nothing demanded: it comes back inside, as the plugin build
|
|
2347
1736
|
// would have had it. Demanding it instead can close a cycle back onto
|
|
@@ -2351,26 +1740,26 @@ async function planVendorNativeRefs(
|
|
|
2351
1740
|
path.join(importerDir, 'pnext-resolve.ts'),
|
|
2352
1741
|
specifier,
|
|
2353
1742
|
conditions,
|
|
2354
|
-
)
|
|
2355
|
-
if (!resolved) return undefined
|
|
2356
|
-
inline.set(specifier, resolved)
|
|
2357
|
-
continue
|
|
1743
|
+
)
|
|
1744
|
+
if (!resolved) return undefined
|
|
1745
|
+
inline.set(specifier, resolved)
|
|
1746
|
+
continue
|
|
2358
1747
|
}
|
|
2359
1748
|
|
|
2360
1749
|
const decision = await (request.preplan
|
|
2361
1750
|
? preplanVendorRef(request, specifier, importerFile, isRequire, conditions)
|
|
2362
|
-
: decideVendorRef(request, specifier, importerFile, isRequire, conditions))
|
|
2363
|
-
if (!decision) return undefined
|
|
2364
|
-
if (decision.kind === 'keep') continue
|
|
1751
|
+
: decideVendorRef(request, specifier, importerFile, isRequire, conditions))
|
|
1752
|
+
if (!decision) return undefined
|
|
1753
|
+
if (decision.kind === 'keep') continue
|
|
2365
1754
|
if (decision.kind === 'inline') {
|
|
2366
|
-
inline.set(specifier, decision.file)
|
|
2367
|
-
continue
|
|
1755
|
+
inline.set(specifier, decision.file)
|
|
1756
|
+
continue
|
|
2368
1757
|
}
|
|
2369
|
-
if (decision.nested) request.context.nestedExternalFiles.push(decision.nested)
|
|
2370
|
-
if (!record(isRequire ? requires : imports, specifier, decision.target)) return undefined
|
|
1758
|
+
if (decision.nested) request.context.nestedExternalFiles.push(decision.nested)
|
|
1759
|
+
if (!record(isRequire ? requires : imports, specifier, decision.target)) return undefined
|
|
2371
1760
|
}
|
|
2372
1761
|
}
|
|
2373
|
-
return { imports, requires, inline }
|
|
1762
|
+
return { imports, requires, inline }
|
|
2374
1763
|
}
|
|
2375
1764
|
|
|
2376
1765
|
/**
|
|
@@ -2384,22 +1773,22 @@ function ownEntryArtifact(
|
|
|
2384
1773
|
importerDir: string,
|
|
2385
1774
|
conditions: string[],
|
|
2386
1775
|
): VendorRefTarget | undefined {
|
|
2387
|
-
if (!request.ownEntries?.size) return undefined
|
|
1776
|
+
if (!request.ownEntries?.size) return undefined
|
|
2388
1777
|
const entry = resolveVendorPackageSpecifier(
|
|
2389
1778
|
request.config.root,
|
|
2390
1779
|
path.join(importerDir, 'pnext-resolve.ts'),
|
|
2391
1780
|
specifier,
|
|
2392
1781
|
conditions,
|
|
2393
|
-
)
|
|
2394
|
-
const artifact = entry && request.ownEntries.get(entry)
|
|
2395
|
-
return artifact ? { specifier: pathToFileHref(artifact), file: artifact } : undefined
|
|
1782
|
+
)
|
|
1783
|
+
const artifact = entry && request.ownEntries.get(entry)
|
|
1784
|
+
return artifact ? { specifier: pathToFileHref(artifact), file: artifact } : undefined
|
|
2396
1785
|
}
|
|
2397
1786
|
|
|
2398
1787
|
type VendorRefDecision =
|
|
2399
1788
|
| { kind: 'keep' }
|
|
2400
1789
|
/** Must be bundled: `file` is what an alias entry points the specifier at. */
|
|
2401
1790
|
| { kind: 'inline'; file: string }
|
|
2402
|
-
| { kind: 'repoint'; target: VendorRefTarget; nested?: string }
|
|
1791
|
+
| { kind: 'repoint'; target: VendorRefTarget; nested?: string }
|
|
2403
1792
|
|
|
2404
1793
|
/**
|
|
2405
1794
|
* Decisions are pure in (layer, specifier, importing directory), and the same
|
|
@@ -2407,13 +1796,13 @@ type VendorRefDecision =
|
|
|
2407
1796
|
* build that meets the reference again. Without this the post-pass re-runs the
|
|
2408
1797
|
* resolver work the plugin chain at least only did once per bundle.
|
|
2409
1798
|
*/
|
|
2410
|
-
const vendorRefDecisions = new Map<string, Promise<VendorRefDecision | undefined>>()
|
|
1799
|
+
const vendorRefDecisions = new Map<string, Promise<VendorRefDecision | undefined>>()
|
|
2411
1800
|
|
|
2412
1801
|
/** Everything the plugin-free path learned about packages, forgotten. */
|
|
2413
1802
|
function clearVendorNativeCaches() {
|
|
2414
|
-
vendorRefDecisions.clear()
|
|
2415
|
-
vendorNativeBailed.clear()
|
|
2416
|
-
vendorPackageShapes.clear()
|
|
1803
|
+
vendorRefDecisions.clear()
|
|
1804
|
+
vendorNativeBailed.clear()
|
|
1805
|
+
vendorPackageShapes.clear()
|
|
2417
1806
|
}
|
|
2418
1807
|
|
|
2419
1808
|
/**
|
|
@@ -2428,14 +1817,14 @@ function preplanVendorRef(
|
|
|
2428
1817
|
isRequire: boolean,
|
|
2429
1818
|
conditions: string[],
|
|
2430
1819
|
) {
|
|
2431
|
-
const importerDir = path.dirname(importerFile)
|
|
2432
|
-
const key = `p\0${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}
|
|
2433
|
-
let decision = vendorRefDecisions.get(key)
|
|
1820
|
+
const importerDir = path.dirname(importerFile)
|
|
1821
|
+
const key = `p\0${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`
|
|
1822
|
+
let decision = vendorRefDecisions.get(key)
|
|
2434
1823
|
if (!decision) {
|
|
2435
|
-
decision = preplanVendorRefUncached(request, specifier, importerFile, isRequire, conditions)
|
|
2436
|
-
vendorRefDecisions.set(key, decision)
|
|
1824
|
+
decision = preplanVendorRefUncached(request, specifier, importerFile, isRequire, conditions)
|
|
1825
|
+
vendorRefDecisions.set(key, decision)
|
|
2437
1826
|
}
|
|
2438
|
-
return decision
|
|
1827
|
+
return decision
|
|
2439
1828
|
}
|
|
2440
1829
|
|
|
2441
1830
|
async function preplanVendorRefUncached(
|
|
@@ -2445,18 +1834,18 @@ async function preplanVendorRefUncached(
|
|
|
2445
1834
|
isRequire: boolean,
|
|
2446
1835
|
conditions: string[],
|
|
2447
1836
|
): Promise<VendorRefDecision | undefined> {
|
|
2448
|
-
const { config, target } = request
|
|
2449
|
-
const importerDir = path.dirname(importerFile)
|
|
2450
|
-
const keep = { kind: 'keep' } as const
|
|
2451
|
-
const depName = packageNameFromSpecifier(specifier)
|
|
2452
|
-
if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep
|
|
1837
|
+
const { config, target } = request
|
|
1838
|
+
const importerDir = path.dirname(importerFile)
|
|
1839
|
+
const keep = { kind: 'keep' } as const
|
|
1840
|
+
const depName = packageNameFromSpecifier(specifier)
|
|
1841
|
+
if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep
|
|
2453
1842
|
// Require-of-external is the async-interop shape that 500ed; not this class.
|
|
2454
|
-
if (isRequire) return undefined
|
|
1843
|
+
if (isRequire) return undefined
|
|
2455
1844
|
if (aliasSpecifierFilter.test(specifier)) {
|
|
2456
|
-
const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier)
|
|
2457
|
-
if (!resolved) return undefined
|
|
2458
|
-
if (!path.isAbsolute(resolved)) return keep
|
|
2459
|
-
return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } }
|
|
1845
|
+
const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier)
|
|
1846
|
+
if (!resolved) return undefined
|
|
1847
|
+
if (!path.isAbsolute(resolved)) return keep
|
|
1848
|
+
return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } }
|
|
2460
1849
|
}
|
|
2461
1850
|
if (serverIgnoredAssetFilter.test(specifier)) {
|
|
2462
1851
|
const asset = resolveVendorPackageSpecifier(
|
|
@@ -2464,24 +1853,24 @@ async function preplanVendorRefUncached(
|
|
|
2464
1853
|
path.join(importerDir, 'pnext-resolve.ts'),
|
|
2465
1854
|
specifier,
|
|
2466
1855
|
conditions,
|
|
2467
|
-
)
|
|
2468
|
-
return asset ? { kind: 'inline', file: asset } : undefined
|
|
1856
|
+
)
|
|
1857
|
+
return asset ? { kind: 'inline', file: asset } : undefined
|
|
2469
1858
|
}
|
|
2470
|
-
if (!depName || !isPackageSpecifier(specifier)) return undefined
|
|
1859
|
+
if (!depName || !isPackageSpecifier(specifier)) return undefined
|
|
2471
1860
|
if (getExternalPackagePolicy().external(depName)) {
|
|
2472
|
-
const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
|
|
1861
|
+
const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
|
|
2473
1862
|
const fromRoot = resolveVendorPackageSpecifier(
|
|
2474
1863
|
config.root,
|
|
2475
1864
|
path.join(config.root, 'pnext-resolve.ts'),
|
|
2476
1865
|
specifier,
|
|
2477
1866
|
conditions,
|
|
2478
|
-
)
|
|
2479
|
-
if (!fromImporter || fromImporter === fromRoot) return keep
|
|
1867
|
+
)
|
|
1868
|
+
if (!fromImporter || fromImporter === fromRoot) return keep
|
|
2480
1869
|
return {
|
|
2481
1870
|
kind: 'repoint',
|
|
2482
1871
|
target: { specifier: fromImporter, file: fromImporter },
|
|
2483
1872
|
nested: fromImporter,
|
|
2484
|
-
}
|
|
1873
|
+
}
|
|
2485
1874
|
}
|
|
2486
1875
|
const planned = await preplanDependencyDecision(
|
|
2487
1876
|
config,
|
|
@@ -2489,12 +1878,12 @@ async function preplanVendorRefUncached(
|
|
|
2489
1878
|
request.conditionTarget,
|
|
2490
1879
|
specifier,
|
|
2491
1880
|
importerDir,
|
|
2492
|
-
)
|
|
2493
|
-
if (!planned?.path) return undefined
|
|
1881
|
+
)
|
|
1882
|
+
if (!planned?.path) return undefined
|
|
2494
1883
|
return {
|
|
2495
1884
|
kind: 'repoint',
|
|
2496
1885
|
target: { specifier: planned.path, file: fileURLToPath(planned.path) },
|
|
2497
|
-
}
|
|
1886
|
+
}
|
|
2498
1887
|
}
|
|
2499
1888
|
|
|
2500
1889
|
function decideVendorRef(
|
|
@@ -2504,14 +1893,14 @@ function decideVendorRef(
|
|
|
2504
1893
|
isRequire: boolean,
|
|
2505
1894
|
conditions: string[],
|
|
2506
1895
|
) {
|
|
2507
|
-
const importerDir = path.dirname(importerFile)
|
|
2508
|
-
const key = `${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}
|
|
2509
|
-
let decision = vendorRefDecisions.get(key)
|
|
1896
|
+
const importerDir = path.dirname(importerFile)
|
|
1897
|
+
const key = `${request.target}\0${request.conditionTarget}\0${isRequire ? 'r' : 'i'}\0${specifier}\0${importerDir}`
|
|
1898
|
+
let decision = vendorRefDecisions.get(key)
|
|
2510
1899
|
if (!decision) {
|
|
2511
|
-
decision = decideVendorRefUncached(request, specifier, importerFile, isRequire, conditions)
|
|
2512
|
-
vendorRefDecisions.set(key, decision)
|
|
1900
|
+
decision = decideVendorRefUncached(request, specifier, importerFile, isRequire, conditions)
|
|
1901
|
+
vendorRefDecisions.set(key, decision)
|
|
2513
1902
|
}
|
|
2514
|
-
return decision
|
|
1903
|
+
return decision
|
|
2515
1904
|
}
|
|
2516
1905
|
|
|
2517
1906
|
async function decideVendorRefUncached(
|
|
@@ -2521,30 +1910,30 @@ async function decideVendorRefUncached(
|
|
|
2521
1910
|
isRequire: boolean,
|
|
2522
1911
|
conditions: string[],
|
|
2523
1912
|
): Promise<VendorRefDecision | undefined> {
|
|
2524
|
-
const { config, target, conditionTarget } = request
|
|
2525
|
-
const importerDir = path.dirname(importerFile)
|
|
2526
|
-
const keep = { kind: 'keep' } as const
|
|
2527
|
-
const depName = packageNameFromSpecifier(specifier)
|
|
2528
|
-
if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep
|
|
1913
|
+
const { config, target, conditionTarget } = request
|
|
1914
|
+
const importerDir = path.dirname(importerFile)
|
|
1915
|
+
const keep = { kind: 'keep' } as const
|
|
1916
|
+
const depName = packageNameFromSpecifier(specifier)
|
|
1917
|
+
if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return keep
|
|
2529
1918
|
|
|
2530
1919
|
// Framework aliases. A require-call reads the react-server layer — a package
|
|
2531
1920
|
// probing `'useState' in require('react')` must see false — so the two kinds
|
|
2532
1921
|
// of reference to one specifier are repointed separately.
|
|
2533
1922
|
if (aliasSpecifierFilter.test(specifier)) {
|
|
2534
|
-
const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier)
|
|
2535
|
-
if (!resolved) return undefined
|
|
1923
|
+
const resolved = coreAliases(config, target)[specifier] ?? firstAliasForSpecifier(specifier)
|
|
1924
|
+
if (!resolved) return undefined
|
|
2536
1925
|
if (isRequire) {
|
|
2537
1926
|
// A CommonJS shim swap (`next/navigation` -> navigation.cjs) is the one
|
|
2538
1927
|
// alias the plugin BUNDLES; leave that build to the plugin.
|
|
2539
|
-
if (serverRequireAlias(specifier, resolved, 'require-call') !== resolved) return undefined
|
|
1928
|
+
if (serverRequireAlias(specifier, resolved, 'require-call') !== resolved) return undefined
|
|
2540
1929
|
const layerAliases =
|
|
2541
|
-
target === 'server' ? getImportAliasExtensions().reactServerLayerAliases(config) : undefined
|
|
2542
|
-
const layer = layerAliases?.[specifier] ?? resolved
|
|
2543
|
-
const file = path.isAbsolute(layer) ? layer : require.resolve(layer)
|
|
2544
|
-
return { kind: 'repoint', target: { specifier: file, file } }
|
|
1930
|
+
target === 'server' ? getImportAliasExtensions().reactServerLayerAliases(config) : undefined
|
|
1931
|
+
const layer = layerAliases?.[specifier] ?? resolved
|
|
1932
|
+
const file = path.isAbsolute(layer) ? layer : require.resolve(layer)
|
|
1933
|
+
return { kind: 'repoint', target: { specifier: file, file } }
|
|
2545
1934
|
}
|
|
2546
|
-
if (!path.isAbsolute(resolved)) return keep
|
|
2547
|
-
return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } }
|
|
1935
|
+
if (!path.isAbsolute(resolved)) return keep
|
|
1936
|
+
return { kind: 'repoint', target: { specifier: pathToFileHref(resolved), file: resolved } }
|
|
2548
1937
|
}
|
|
2549
1938
|
|
|
2550
1939
|
// An asset the bundler externalized (a cross-package CSS import) has to come
|
|
@@ -2555,27 +1944,27 @@ async function decideVendorRefUncached(
|
|
|
2555
1944
|
path.join(importerDir, 'pnext-resolve.ts'),
|
|
2556
1945
|
specifier,
|
|
2557
1946
|
conditions,
|
|
2558
|
-
)
|
|
2559
|
-
return asset ? { kind: 'inline', file: asset } : undefined
|
|
1947
|
+
)
|
|
1948
|
+
return asset ? { kind: 'inline', file: asset } : undefined
|
|
2560
1949
|
}
|
|
2561
|
-
if (!depName || !isPackageSpecifier(specifier)) return undefined
|
|
1950
|
+
if (!depName || !isPackageSpecifier(specifier)) return undefined
|
|
2562
1951
|
|
|
2563
1952
|
// B7: a serverExternalPackage stays external, pinned to the importing
|
|
2564
1953
|
// package's own copy when that differs from the hoisted root version.
|
|
2565
1954
|
if (getExternalPackagePolicy().external(depName)) {
|
|
2566
|
-
const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
|
|
1955
|
+
const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
|
|
2567
1956
|
const fromRoot = resolveVendorPackageSpecifier(
|
|
2568
1957
|
config.root,
|
|
2569
1958
|
path.join(config.root, 'pnext-resolve.ts'),
|
|
2570
1959
|
specifier,
|
|
2571
1960
|
conditions,
|
|
2572
|
-
)
|
|
2573
|
-
if (!fromImporter || fromImporter === fromRoot) return keep
|
|
1961
|
+
)
|
|
1962
|
+
if (!fromImporter || fromImporter === fromRoot) return keep
|
|
2574
1963
|
return {
|
|
2575
1964
|
kind: 'repoint',
|
|
2576
1965
|
target: { specifier: fromImporter, file: fromImporter },
|
|
2577
1966
|
nested: fromImporter,
|
|
2578
|
-
}
|
|
1967
|
+
}
|
|
2579
1968
|
}
|
|
2580
1969
|
|
|
2581
1970
|
const resolvedEntry = resolveVendorPackageSpecifier(
|
|
@@ -2583,15 +1972,15 @@ async function decideVendorRefUncached(
|
|
|
2583
1972
|
path.join(importerDir, 'pnext-resolve.ts'),
|
|
2584
1973
|
specifier,
|
|
2585
1974
|
conditions,
|
|
2586
|
-
)
|
|
1975
|
+
)
|
|
2587
1976
|
// A CommonJS dependency loses its named exports when externalized, so it is
|
|
2588
1977
|
// inlined unless a verified facade can republish them.
|
|
2589
|
-
const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!)
|
|
1978
|
+
const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!)
|
|
2590
1979
|
if (commonJs && !(await cjsEntryMayHaveNamedExports(resolvedEntry!))) {
|
|
2591
|
-
return { kind: 'inline', file: resolvedEntry! }
|
|
1980
|
+
return { kind: 'inline', file: resolvedEntry! }
|
|
2592
1981
|
}
|
|
2593
|
-
let vendored: string
|
|
2594
|
-
const tv = performance.now()
|
|
1982
|
+
let vendored: string
|
|
1983
|
+
const tv = performance.now()
|
|
2595
1984
|
try {
|
|
2596
1985
|
vendored = await externalServerPackageHref(
|
|
2597
1986
|
config,
|
|
@@ -2601,10 +1990,10 @@ async function decideVendorRefUncached(
|
|
|
2601
1990
|
conditionTarget,
|
|
2602
1991
|
// Raised from inside a running build: must not queue behind it.
|
|
2603
1992
|
true,
|
|
2604
|
-
)
|
|
1993
|
+
)
|
|
2605
1994
|
} catch (error) {
|
|
2606
|
-
if (isResolveFailure(error)) return keep
|
|
2607
|
-
return undefined
|
|
1995
|
+
if (isResolveFailure(error)) return keep
|
|
1996
|
+
return undefined
|
|
2608
1997
|
}
|
|
2609
1998
|
if (vendorTraceEnabled()) {
|
|
2610
1999
|
vendorTraceRow({
|
|
@@ -2614,23 +2003,23 @@ async function decideVendorRefUncached(
|
|
|
2614
2003
|
fromCondition: conditionTarget,
|
|
2615
2004
|
to: specifier,
|
|
2616
2005
|
toResolveDir: importerDir,
|
|
2617
|
-
})
|
|
2006
|
+
})
|
|
2618
2007
|
}
|
|
2619
2008
|
if (commonJs) {
|
|
2620
|
-
heavyProfRow({ k: 'dep-build-wait', site: 'ref', spec: specifier, ms: performance.now() - tv })
|
|
2621
|
-
const tf = performance.now()
|
|
2622
|
-
const facade = await cjsNamedExportFacade(fileURLToPath(vendored), vendored)
|
|
2623
|
-
heavyProfRow({ k: 'facade-wait', site: 'ref', spec: specifier, ms: performance.now() - tf })
|
|
2624
|
-
if (!facade) return { kind: 'inline', file: resolvedEntry! }
|
|
2009
|
+
heavyProfRow({ k: 'dep-build-wait', site: 'ref', spec: specifier, ms: performance.now() - tv })
|
|
2010
|
+
const tf = performance.now()
|
|
2011
|
+
const facade = await cjsNamedExportFacade(fileURLToPath(vendored), vendored)
|
|
2012
|
+
heavyProfRow({ k: 'facade-wait', site: 'ref', spec: specifier, ms: performance.now() - tf })
|
|
2013
|
+
if (!facade) return { kind: 'inline', file: resolvedEntry! }
|
|
2625
2014
|
return {
|
|
2626
2015
|
kind: 'repoint',
|
|
2627
2016
|
target: { specifier: pathToFileHref(facade), file: facade },
|
|
2628
|
-
}
|
|
2017
|
+
}
|
|
2629
2018
|
}
|
|
2630
2019
|
return {
|
|
2631
2020
|
kind: 'repoint',
|
|
2632
2021
|
target: { specifier: vendored, file: fileURLToPath(vendored) },
|
|
2633
|
-
}
|
|
2022
|
+
}
|
|
2634
2023
|
}
|
|
2635
2024
|
|
|
2636
2025
|
/**
|
|
@@ -2638,15 +2027,11 @@ async function decideVendorRefUncached(
|
|
|
2638
2027
|
* DIFFERENTLY (a pinned nested version for one of them) cannot be rewritten in place - that is a bail, not a
|
|
2639
2028
|
* choice.
|
|
2640
2029
|
*/
|
|
2641
|
-
function record(
|
|
2642
|
-
|
|
2643
|
-
specifier
|
|
2644
|
-
target
|
|
2645
|
-
|
|
2646
|
-
const existing = targets.get(specifier);
|
|
2647
|
-
if (existing) return existing.specifier === target.specifier;
|
|
2648
|
-
targets.set(specifier, target);
|
|
2649
|
-
return true;
|
|
2030
|
+
function record(targets: Map<string, VendorRefTarget>, specifier: string, target: VendorRefTarget) {
|
|
2031
|
+
const existing = targets.get(specifier)
|
|
2032
|
+
if (existing) return existing.specifier === target.specifier
|
|
2033
|
+
targets.set(specifier, target)
|
|
2034
|
+
return true
|
|
2650
2035
|
}
|
|
2651
2036
|
|
|
2652
2037
|
async function writeExternalServerPackageBundle(
|
|
@@ -2658,27 +2043,29 @@ async function writeExternalServerPackageBundle(
|
|
|
2658
2043
|
conditionTarget: ServerBundleTarget,
|
|
2659
2044
|
nested: boolean,
|
|
2660
2045
|
) {
|
|
2661
|
-
const context = { nestedExternalFiles: [] as string[] }
|
|
2046
|
+
const context = { nestedExternalFiles: [] as string[] }
|
|
2662
2047
|
// The layer is part of the label: the same specifier legitimately compiles
|
|
2663
2048
|
// once per target, and a profile that cannot tell those apart reads as
|
|
2664
2049
|
// duplication that is not there.
|
|
2665
|
-
const bundled = await profileRuntimeStep(
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2050
|
+
const bundled = await profileRuntimeStep(
|
|
2051
|
+
`vendor bundle ${specifier} [${target}/${conditionTarget}]`,
|
|
2052
|
+
() =>
|
|
2053
|
+
bundleExternalPackage(
|
|
2054
|
+
config,
|
|
2055
|
+
specifier,
|
|
2056
|
+
target,
|
|
2057
|
+
resolveDir,
|
|
2058
|
+
conditionTarget,
|
|
2059
|
+
context,
|
|
2060
|
+
file,
|
|
2061
|
+
nested,
|
|
2062
|
+
),
|
|
2063
|
+
)
|
|
2677
2064
|
// Packages can ship 'use cache' functions too (Next allows it); client
|
|
2678
2065
|
// bundles keep the source untouched.
|
|
2679
2066
|
const code =
|
|
2680
|
-
target === 'server' ? applyBundledSourceTransforms(bundled.code, specifier) : bundled.code
|
|
2681
|
-
const id = await writeVendorArtifact(file, code)
|
|
2067
|
+
target === 'server' ? applyBundledSourceTransforms(bundled.code, specifier) : bundled.code
|
|
2068
|
+
const id = await writeVendorArtifact(file, code)
|
|
2682
2069
|
recordVendorLayerBuild(
|
|
2683
2070
|
config,
|
|
2684
2071
|
resolveVendorEntry(config, specifier, resolveDir, conditionTarget),
|
|
@@ -2687,39 +2074,39 @@ async function writeExternalServerPackageBundle(
|
|
|
2687
2074
|
bundled.metafile,
|
|
2688
2075
|
code,
|
|
2689
2076
|
id,
|
|
2690
|
-
)
|
|
2077
|
+
)
|
|
2691
2078
|
// When this vendor bundle pins a transitive external to a nested version distinct from the hoisted root
|
|
2692
2079
|
// copy, Node/webpack file tracing surfaces that dependency's source as a `.js` artifact in the server
|
|
2693
2080
|
// output. Emit a `.js` trace copy of each such source so build tooling that scans `**/*.js` - never `.mjs` -
|
|
2694
2081
|
// observes the bundled transitive too. These are trace artifacts only; nothing imports them.
|
|
2695
|
-
await emitNestedExternalTraceCopies(file, context.nestedExternalFiles)
|
|
2696
|
-
return file
|
|
2082
|
+
await emitNestedExternalTraceCopies(file, context.nestedExternalFiles)
|
|
2083
|
+
return file
|
|
2697
2084
|
}
|
|
2698
2085
|
|
|
2699
2086
|
/** Trace-only side channel: what the esbuild pass that produced the code did. */
|
|
2700
2087
|
interface VendorBuildTrace {
|
|
2701
|
-
plugins: string[]
|
|
2702
|
-
entry?: string
|
|
2703
|
-
metafile?: Metafile
|
|
2088
|
+
plugins: string[]
|
|
2089
|
+
entry?: string
|
|
2090
|
+
metafile?: Metafile
|
|
2704
2091
|
}
|
|
2705
2092
|
|
|
2706
2093
|
/** Emit one JSONL build row from a finished (or failed) vendor esbuild pass. */
|
|
2707
2094
|
function traceVendorBuild(row: {
|
|
2708
|
-
seq: number
|
|
2709
|
-
specifier: string
|
|
2710
|
-
resolveDir: string
|
|
2711
|
-
target: string
|
|
2712
|
-
conditionTarget: ServerBundleTarget
|
|
2713
|
-
conditions: string[]
|
|
2714
|
-
startMs: number
|
|
2715
|
-
outBytes: number
|
|
2716
|
-
trace: VendorBuildTrace
|
|
2717
|
-
ok: boolean
|
|
2718
|
-
error?: string
|
|
2095
|
+
seq: number
|
|
2096
|
+
specifier: string
|
|
2097
|
+
resolveDir: string
|
|
2098
|
+
target: string
|
|
2099
|
+
conditionTarget: ServerBundleTarget
|
|
2100
|
+
conditions: string[]
|
|
2101
|
+
startMs: number
|
|
2102
|
+
outBytes: number
|
|
2103
|
+
trace: VendorBuildTrace
|
|
2104
|
+
ok: boolean
|
|
2105
|
+
error?: string
|
|
2719
2106
|
}) {
|
|
2720
|
-
const inputs = row.trace.metafile?.inputs ?? {}
|
|
2721
|
-
const inputFiles = Object.keys(inputs)
|
|
2722
|
-
const endMs = performance.now()
|
|
2107
|
+
const inputs = row.trace.metafile?.inputs ?? {}
|
|
2108
|
+
const inputFiles = Object.keys(inputs)
|
|
2109
|
+
const endMs = performance.now()
|
|
2723
2110
|
vendorTraceRow({
|
|
2724
2111
|
seq: row.seq,
|
|
2725
2112
|
specifier: row.specifier,
|
|
@@ -2738,7 +2125,7 @@ function traceVendorBuild(row: {
|
|
|
2738
2125
|
plugins: row.trace.plugins,
|
|
2739
2126
|
ok: row.ok,
|
|
2740
2127
|
...(row.error ? { error: row.error } : {}),
|
|
2741
|
-
})
|
|
2128
|
+
})
|
|
2742
2129
|
}
|
|
2743
2130
|
|
|
2744
2131
|
async function bundleExternalPackage(
|
|
@@ -2762,11 +2149,11 @@ async function bundleExternalPackage(
|
|
|
2762
2149
|
context,
|
|
2763
2150
|
file,
|
|
2764
2151
|
nested,
|
|
2765
|
-
)
|
|
2152
|
+
)
|
|
2766
2153
|
}
|
|
2767
|
-
const trace: VendorBuildTrace = { plugins: [] }
|
|
2768
|
-
const seq = nextVendorTraceSeq()
|
|
2769
|
-
const startMs = performance.now()
|
|
2154
|
+
const trace: VendorBuildTrace = { plugins: [] }
|
|
2155
|
+
const seq = nextVendorTraceSeq()
|
|
2156
|
+
const startMs = performance.now()
|
|
2770
2157
|
const common = {
|
|
2771
2158
|
seq,
|
|
2772
2159
|
specifier,
|
|
@@ -2776,7 +2163,7 @@ async function bundleExternalPackage(
|
|
|
2776
2163
|
conditions: serverBundleConditions(conditionTarget),
|
|
2777
2164
|
startMs,
|
|
2778
2165
|
trace,
|
|
2779
|
-
}
|
|
2166
|
+
}
|
|
2780
2167
|
try {
|
|
2781
2168
|
const result = await bundleExternalPackageImpl(
|
|
2782
2169
|
config,
|
|
@@ -2788,17 +2175,17 @@ async function bundleExternalPackage(
|
|
|
2788
2175
|
file,
|
|
2789
2176
|
nested,
|
|
2790
2177
|
trace,
|
|
2791
|
-
)
|
|
2792
|
-
traceVendorBuild({ ...common, outBytes: result.code.length, ok: true })
|
|
2793
|
-
return result
|
|
2178
|
+
)
|
|
2179
|
+
traceVendorBuild({ ...common, outBytes: result.code.length, ok: true })
|
|
2180
|
+
return result
|
|
2794
2181
|
} catch (error) {
|
|
2795
2182
|
traceVendorBuild({
|
|
2796
2183
|
...common,
|
|
2797
2184
|
outBytes: 0,
|
|
2798
2185
|
ok: false,
|
|
2799
2186
|
error: (error instanceof Error ? error.message : String(error)).slice(0, 300),
|
|
2800
|
-
})
|
|
2801
|
-
throw error
|
|
2187
|
+
})
|
|
2188
|
+
throw error
|
|
2802
2189
|
}
|
|
2803
2190
|
}
|
|
2804
2191
|
|
|
@@ -2814,13 +2201,13 @@ async function bundleExternalPackageImpl(
|
|
|
2814
2201
|
nested = false,
|
|
2815
2202
|
trace?: VendorBuildTrace,
|
|
2816
2203
|
) {
|
|
2817
|
-
const packageName = packageNameFromSpecifier(specifier)
|
|
2204
|
+
const packageName = packageNameFromSpecifier(specifier)
|
|
2818
2205
|
const resolvedEntry = resolveVendorPackageSpecifier(
|
|
2819
2206
|
config.root,
|
|
2820
2207
|
path.join(resolveDir, 'pnext-resolve.ts'),
|
|
2821
2208
|
specifier,
|
|
2822
2209
|
serverBundleConditions(conditionTarget),
|
|
2823
|
-
)
|
|
2210
|
+
)
|
|
2824
2211
|
const requiredEntry =
|
|
2825
2212
|
resolvedEntry &&
|
|
2826
2213
|
!isEsmModuleFile(resolvedEntry) &&
|
|
@@ -2831,18 +2218,18 @@ async function bundleExternalPackageImpl(
|
|
|
2831
2218
|
specifier,
|
|
2832
2219
|
serverBundleRequireConditions(conditionTarget),
|
|
2833
2220
|
)
|
|
2834
|
-
: undefined
|
|
2221
|
+
: undefined
|
|
2835
2222
|
if (
|
|
2836
2223
|
packageName &&
|
|
2837
2224
|
resolvedEntry?.match(/\.[cm]?tsx?$/) &&
|
|
2838
2225
|
!getExternalPackagePolicy().transpile(packageName)
|
|
2839
2226
|
) {
|
|
2840
|
-
const parseFailure = `${path.relative(config.root, resolvedEntry)}\nModule parse failed: Unexpected token
|
|
2227
|
+
const parseFailure = `${path.relative(config.root, resolvedEntry)}\nModule parse failed: Unexpected token`
|
|
2841
2228
|
// Printed directly as well: the digest log path inspects the error's stack,
|
|
2842
2229
|
// which some runtimes emit without the message - the CLI output must always
|
|
2843
2230
|
// carry the parse failure (Next's transpilePackages error contract).
|
|
2844
|
-
console.error(`⨯ Error: ${parseFailure}`)
|
|
2845
|
-
throw new Error(parseFailure)
|
|
2231
|
+
console.error(`⨯ Error: ${parseFailure}`)
|
|
2232
|
+
throw new Error(parseFailure)
|
|
2846
2233
|
}
|
|
2847
2234
|
const entry =
|
|
2848
2235
|
requiredEntry ??
|
|
@@ -2852,12 +2239,12 @@ async function bundleExternalPackageImpl(
|
|
|
2852
2239
|
specifier,
|
|
2853
2240
|
serverBundleConditions(conditionTarget),
|
|
2854
2241
|
) ??
|
|
2855
|
-
specifier
|
|
2242
|
+
specifier
|
|
2856
2243
|
// `packages: 'external'` refuses to bundle a BARE entry point, so the
|
|
2857
2244
|
// plugin-free path needs the resolved file — which is the same file esbuild
|
|
2858
2245
|
// would have resolved the specifier to.
|
|
2859
|
-
const nativeEntry = path.isAbsolute(entry) ? entry : resolvedEntry
|
|
2860
|
-
if (trace) trace.entry = nativeEntry ?? resolvedEntry
|
|
2246
|
+
const nativeEntry = path.isAbsolute(entry) ? entry : resolvedEntry
|
|
2247
|
+
if (trace) trace.entry = nativeEntry ?? resolvedEntry
|
|
2861
2248
|
const preplan =
|
|
2862
2249
|
vendorPreplanEnabled() &&
|
|
2863
2250
|
(await vendorPreplanPackageSafe(
|
|
@@ -2866,7 +2253,7 @@ async function bundleExternalPackageImpl(
|
|
|
2866
2253
|
nativeEntry ?? resolvedEntry,
|
|
2867
2254
|
target,
|
|
2868
2255
|
conditionTarget,
|
|
2869
|
-
))
|
|
2256
|
+
))
|
|
2870
2257
|
const native =
|
|
2871
2258
|
file && nativeEntry
|
|
2872
2259
|
? await buildVendorNative({
|
|
@@ -2886,12 +2273,12 @@ async function bundleExternalPackageImpl(
|
|
|
2886
2273
|
context: context ?? { nestedExternalFiles: [] },
|
|
2887
2274
|
trace,
|
|
2888
2275
|
})
|
|
2889
|
-
: undefined
|
|
2890
|
-
if (native?.[0]) return { code: addCommonJsNamedExports(native[0].text), metafile: undefined }
|
|
2276
|
+
: undefined
|
|
2277
|
+
if (native?.[0]) return { code: addCommonJsNamedExports(native[0].text), metafile: undefined }
|
|
2891
2278
|
|
|
2892
|
-
let bundled: string
|
|
2893
|
-
let metafile: Metafile | undefined
|
|
2894
|
-
const pluginStart = performance.now()
|
|
2279
|
+
let bundled: string
|
|
2280
|
+
let metafile: Metafile | undefined
|
|
2281
|
+
const pluginStart = performance.now()
|
|
2895
2282
|
const vendorPlugins = profiledVendorPlugins([
|
|
2896
2283
|
serverAssetPlugin(config),
|
|
2897
2284
|
...getBundlerExtensions().serverEsbuildPlugins(
|
|
@@ -2906,11 +2293,11 @@ async function bundleExternalPackageImpl(
|
|
|
2906
2293
|
}),
|
|
2907
2294
|
externalPackageDependencyPlugin(config, specifier, target, conditionTarget, context, preplan),
|
|
2908
2295
|
externalBuiltinBuildPlugin(),
|
|
2909
|
-
])
|
|
2910
|
-
if (trace) trace.plugins = vendorPlugins.map(plugin => plugin.name)
|
|
2296
|
+
])
|
|
2297
|
+
if (trace) trace.plugins = vendorPlugins.map(plugin => plugin.name)
|
|
2911
2298
|
// Bare entry: hand the interop plugin the resolution already computed above.
|
|
2912
2299
|
if (!path.isAbsolute(entry) && resolvedEntry) {
|
|
2913
|
-
provideEntryResolution(entry, resolveDir, resolvedEntry)
|
|
2300
|
+
provideEntryResolution(entry, resolveDir, resolvedEntry)
|
|
2914
2301
|
}
|
|
2915
2302
|
try {
|
|
2916
2303
|
const result = await build({
|
|
@@ -2937,14 +2324,14 @@ async function bundleExternalPackageImpl(
|
|
|
2937
2324
|
metafile: true,
|
|
2938
2325
|
...serverDefineOptions(),
|
|
2939
2326
|
plugins: vendorPlugins,
|
|
2940
|
-
})
|
|
2941
|
-
const output = result.outputFiles[0]
|
|
2942
|
-
if (!output) throw new Error(`Failed to bundle ${specifier}`)
|
|
2943
|
-
vendorNativeStats.pluginBuilds += 1
|
|
2944
|
-
vendorNativeStats.pluginBuildMs += performance.now() - pluginStart
|
|
2945
|
-
bundled = output.text
|
|
2946
|
-
metafile = result.metafile
|
|
2947
|
-
if (trace) trace.metafile = result.metafile
|
|
2327
|
+
})
|
|
2328
|
+
const output = result.outputFiles[0]
|
|
2329
|
+
if (!output) throw new Error(`Failed to bundle ${specifier}`)
|
|
2330
|
+
vendorNativeStats.pluginBuilds += 1
|
|
2331
|
+
vendorNativeStats.pluginBuildMs += performance.now() - pluginStart
|
|
2332
|
+
bundled = output.text
|
|
2333
|
+
metafile = result.metafile
|
|
2334
|
+
if (trace) trace.metafile = result.metafile
|
|
2948
2335
|
} catch (error) {
|
|
2949
2336
|
// A subpath the package deliberately withholds from this layer
|
|
2950
2337
|
// (`"node": null`) is not a broken dependency: browser-only code reaches it
|
|
@@ -2960,86 +2347,19 @@ async function bundleExternalPackageImpl(
|
|
|
2960
2347
|
serverBundleConditions(conditionTarget),
|
|
2961
2348
|
)
|
|
2962
2349
|
) {
|
|
2963
|
-
throw error
|
|
2350
|
+
throw error
|
|
2964
2351
|
}
|
|
2965
|
-
return { code: unexportedSubpathModule(specifier), metafile: undefined }
|
|
2352
|
+
return { code: unexportedSubpathModule(specifier), metafile: undefined }
|
|
2966
2353
|
}
|
|
2967
|
-
return { code: addCommonJsNamedExports(bundled), metafile }
|
|
2354
|
+
return { code: addCommonJsNamedExports(bundled), metafile }
|
|
2968
2355
|
}
|
|
2969
2356
|
|
|
2970
2357
|
function unexportedSubpathModule(specifier: string) {
|
|
2971
|
-
const packageName = packageNameFromSpecifier(specifier)
|
|
2972
|
-
const subpath =
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
}
|
|
2976
|
-
|
|
2977
|
-
function runtimeAliasBuildPlugin(
|
|
2978
|
-
config?: ResolvedConfig,
|
|
2979
|
-
target: CompatAliasTarget = 'server',
|
|
2980
|
-
options: { bundleRequireAliases?: boolean; reactServerLayer?: boolean } = {},
|
|
2981
|
-
): Plugin {
|
|
2982
|
-
const aliases = config ? coreAliases(config, target) : undefined;
|
|
2983
|
-
// A node_modules package bundled into the RSC (react-server) layer that requires 'react' must observe the
|
|
2984
|
-
// react-server subset - no client-hook dispatcher - exactly like the framework's own react-server modules,
|
|
2985
|
-
// so a package's `'useState' in require('react')` probe reads false on the server. ESM named imports stay
|
|
2986
|
-
// external and resolve at runtime against the full-hooks server shim (kept for pages/api back-compat), so
|
|
2987
|
-
// this narrow require-call override never breaks them.
|
|
2988
|
-
const reactServerLayerAliases =
|
|
2989
|
-
config && options.reactServerLayer
|
|
2990
|
-
? getImportAliasExtensions().reactServerLayerAliases(config)
|
|
2991
|
-
: undefined;
|
|
2992
|
-
return {
|
|
2993
|
-
name: 'pnext-runtime-build-alias',
|
|
2994
|
-
setup(build) {
|
|
2995
|
-
build.onResolve({ filter: /^\.\/preact$/ }, args => {
|
|
2996
|
-
// `./preact` is the compat react shim's private import. It also appears in MIRRORED copies of the
|
|
2997
|
-
// shim (build-cache outputs under node_modules paths) where no sibling preact file exists - those
|
|
2998
|
-
// must resolve to the single compat instance too. Only a REAL sibling ./preact.* opts out.
|
|
2999
|
-
const hasRealSibling = ['.ts', '.tsx', '.js', '.mjs', '.jsx'].some(ext =>
|
|
3000
|
-
existsSync(path.join(args.resolveDir, `preact${ext}`)),
|
|
3001
|
-
);
|
|
3002
|
-
const compatPreact = path.resolve(import.meta.dirname, '..', 'compat', 'react', 'preact.ts');
|
|
3003
|
-
if (hasRealSibling && path.resolve(args.resolveDir, 'preact.ts') !== compatPreact)
|
|
3004
|
-
return undefined;
|
|
3005
|
-
return { path: compatPreact };
|
|
3006
|
-
});
|
|
3007
|
-
build.onResolve({ filter: /^\.\.\/client\/errors\/primitive-throw$/ }, args => {
|
|
3008
|
-
const compatPreact = path.resolve(import.meta.dirname, '..', 'compat', 'react', 'preact.ts');
|
|
3009
|
-
if (path.resolve(args.importer) !== compatPreact) return undefined;
|
|
3010
|
-
return { path: path.resolve(import.meta.dirname, '..', 'compat', 'client', 'errors', 'primitive-throw.ts') };
|
|
3011
|
-
});
|
|
3012
|
-
build.onResolve({ filter: aliasSpecifierFilter }, args => {
|
|
3013
|
-
const resolved = aliases?.[args.path] ?? firstAliasForSpecifier(args.path);
|
|
3014
|
-
if (!resolved) return undefined;
|
|
3015
|
-
const requireTarget = serverRequireAlias(args.path, resolved, args.kind);
|
|
3016
|
-
if (requireTarget !== resolved) return { path: requireTarget };
|
|
3017
|
-
if (options.bundleRequireAliases && args.kind === 'require-call') {
|
|
3018
|
-
const layerTarget = serverRequireAlias(
|
|
3019
|
-
args.path,
|
|
3020
|
-
reactServerLayerAliases?.[args.path] ?? resolved,
|
|
3021
|
-
args.kind,
|
|
3022
|
-
);
|
|
3023
|
-
return { path: path.isAbsolute(layerTarget) ? layerTarget : require.resolve(layerTarget) };
|
|
3024
|
-
}
|
|
3025
|
-
return {
|
|
3026
|
-
path: path.isAbsolute(resolved) ? pathToFileHref(resolved) : resolved,
|
|
3027
|
-
external: true,
|
|
3028
|
-
};
|
|
3029
|
-
});
|
|
3030
|
-
},
|
|
3031
|
-
};
|
|
3032
|
-
}
|
|
3033
|
-
|
|
3034
|
-
function serverRequireAlias(specifier: string, target: string, kind: string | undefined): string {
|
|
3035
|
-
if (kind !== 'require-call') return target;
|
|
3036
|
-
if (specifier === 'next/navigation' && target.endsWith(`${path.sep}navigation.ts`)) {
|
|
3037
|
-
return path.join(path.dirname(target), 'navigation.cjs');
|
|
3038
|
-
}
|
|
3039
|
-
if (specifier === 'next/router' && target.endsWith(`${path.sep}router.ts`)) {
|
|
3040
|
-
return path.join(path.dirname(target), 'router.cjs');
|
|
3041
|
-
}
|
|
3042
|
-
return target;
|
|
2358
|
+
const packageName = packageNameFromSpecifier(specifier)
|
|
2359
|
+
const subpath =
|
|
2360
|
+
packageName && specifier !== packageName ? `.${specifier.slice(packageName.length)}` : '.'
|
|
2361
|
+
const message = `Package subpath '${subpath}' is not defined by "exports" in ${packageName ?? specifier}`
|
|
2362
|
+
return `throw Object.assign(new Error(${JSON.stringify(message)}), { code: 'ERR_PACKAGE_PATH_NOT_EXPORTED' });\n`
|
|
3043
2363
|
}
|
|
3044
2364
|
|
|
3045
2365
|
function externalPackageDependencyPlugin(
|
|
@@ -3050,13 +2370,13 @@ function externalPackageDependencyPlugin(
|
|
|
3050
2370
|
context?: { nestedExternalFiles: string[] },
|
|
3051
2371
|
preplan = false,
|
|
3052
2372
|
): Plugin {
|
|
3053
|
-
const entryPackageName = packageNameFromSpecifier(entrySpecifier)
|
|
2373
|
+
const entryPackageName = packageNameFromSpecifier(entrySpecifier)
|
|
3054
2374
|
return {
|
|
3055
2375
|
name: 'pnext-external-package-dependencies',
|
|
3056
2376
|
setup(build) {
|
|
3057
2377
|
build.onResolve({ filter: /^[^./].*/ }, async args => {
|
|
3058
|
-
if (!isPackageSpecifier(args.path)) return undefined
|
|
3059
|
-
if (packageNameFromSpecifier(args.path) === entryPackageName) return undefined
|
|
2378
|
+
if (!isPackageSpecifier(args.path)) return undefined
|
|
2379
|
+
if (packageNameFromSpecifier(args.path) === entryPackageName) return undefined
|
|
3060
2380
|
if (preplan && args.kind !== 'require-call') {
|
|
3061
2381
|
const planned = await preplanDependencyDecision(
|
|
3062
2382
|
config,
|
|
@@ -3064,12 +2384,12 @@ function externalPackageDependencyPlugin(
|
|
|
3064
2384
|
conditionTarget,
|
|
3065
2385
|
args.path,
|
|
3066
2386
|
args.resolveDir,
|
|
3067
|
-
)
|
|
2387
|
+
)
|
|
3068
2388
|
if (planned) {
|
|
3069
|
-
vendorPreplanStats.preplanRefs += 1
|
|
3070
|
-
return planned
|
|
2389
|
+
vendorPreplanStats.preplanRefs += 1
|
|
2390
|
+
return planned
|
|
3071
2391
|
}
|
|
3072
|
-
vendorPreplanStats.fallbackRefs += 1
|
|
2392
|
+
vendorPreplanStats.fallbackRefs += 1
|
|
3073
2393
|
}
|
|
3074
2394
|
// Off the slot for the whole decision: it ends in a nested vendor
|
|
3075
2395
|
// build, and holding a worker across that is what caps real
|
|
@@ -3077,8 +2397,15 @@ function externalPackageDependencyPlugin(
|
|
|
3077
2397
|
// behaviour for bisecting). The resolver work in front of it is cheap
|
|
3078
2398
|
// and off-slot too, exactly as the plugin-free path's post-pass runs it.
|
|
3079
2399
|
const decision = await yieldVendorSlotWhile(() =>
|
|
3080
|
-
externalDependencyDecision(
|
|
3081
|
-
|
|
2400
|
+
externalDependencyDecision(
|
|
2401
|
+
config,
|
|
2402
|
+
target,
|
|
2403
|
+
conditionTarget,
|
|
2404
|
+
args.path,
|
|
2405
|
+
args.resolveDir,
|
|
2406
|
+
args.importer,
|
|
2407
|
+
),
|
|
2408
|
+
)
|
|
3082
2409
|
if (decision?.vendored && vendorTraceEnabled()) {
|
|
3083
2410
|
vendorTraceRow({
|
|
3084
2411
|
kind: 'edge',
|
|
@@ -3087,26 +2414,26 @@ function externalPackageDependencyPlugin(
|
|
|
3087
2414
|
fromCondition: conditionTarget,
|
|
3088
2415
|
to: args.path,
|
|
3089
2416
|
toResolveDir: args.resolveDir,
|
|
3090
|
-
})
|
|
2417
|
+
})
|
|
3091
2418
|
}
|
|
3092
|
-
if (decision?.nested && context) context.nestedExternalFiles.push(decision.nested)
|
|
2419
|
+
if (decision?.nested && context) context.nestedExternalFiles.push(decision.nested)
|
|
3093
2420
|
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
3094
|
-
if (decision?.error) throw decision.error
|
|
3095
|
-
return decision?.result
|
|
3096
|
-
})
|
|
2421
|
+
if (decision?.error) throw decision.error
|
|
2422
|
+
return decision?.result
|
|
2423
|
+
})
|
|
3097
2424
|
},
|
|
3098
|
-
}
|
|
2425
|
+
}
|
|
3099
2426
|
}
|
|
3100
2427
|
|
|
3101
2428
|
/** What one external reference resolves to, plus the effects the caller replays. */
|
|
3102
2429
|
interface ExternalDependencyDecision {
|
|
3103
|
-
result: OnResolveResult | undefined
|
|
2430
|
+
result: OnResolveResult | undefined
|
|
3104
2431
|
/** A transitive dep pinned to a nested copy, for the trace-copy pass. */
|
|
3105
|
-
nested?: string
|
|
2432
|
+
nested?: string
|
|
3106
2433
|
/** A build failure, re-thrown per caller rather than cached as a value. */
|
|
3107
|
-
error?: unknown
|
|
2434
|
+
error?: unknown
|
|
3108
2435
|
/** Trace-only: the dep was deferred to a vendor bundle of its own. */
|
|
3109
|
-
vendored?: boolean
|
|
2436
|
+
vendored?: boolean
|
|
3110
2437
|
}
|
|
3111
2438
|
|
|
3112
2439
|
/**
|
|
@@ -3125,7 +2452,7 @@ async function externalDependencyDecision(
|
|
|
3125
2452
|
): Promise<ExternalDependencyDecision> {
|
|
3126
2453
|
// B7: a serverExternalPackage transitive dep stays external (Node resolves it
|
|
3127
2454
|
// from node_modules at runtime) instead of being bundled.
|
|
3128
|
-
const depName = packageNameFromSpecifier(specifier)
|
|
2455
|
+
const depName = packageNameFromSpecifier(specifier)
|
|
3129
2456
|
if (depName && getExternalPackagePolicy().external(depName)) {
|
|
3130
2457
|
// Transitive-version correctness: the runtime resolves a bare external from the VENDOR bundle's own
|
|
3131
2458
|
// location, which walks up to the hoisted root copy - wrong when the importing package pulls a distinct
|
|
@@ -3134,35 +2461,35 @@ async function externalDependencyDecision(
|
|
|
3134
2461
|
// external to that exact resolved file so the correct version loads at runtime. A hoisted dep stays a
|
|
3135
2462
|
// portable bare external. Bundling the nested copy inline instead regresses to a resolve ERROR for the
|
|
3136
2463
|
// bundle's other bare externals, so keep it external and only pin the path.
|
|
3137
|
-
const conditions = serverBundleConditions(conditionTarget)
|
|
2464
|
+
const conditions = serverBundleConditions(conditionTarget)
|
|
3138
2465
|
const fromImporter = importer
|
|
3139
2466
|
? resolveNestedPackageFromImporter(importer, specifier, conditions)
|
|
3140
|
-
: undefined
|
|
2467
|
+
: undefined
|
|
3141
2468
|
const fromRoot = resolveVendorPackageSpecifier(
|
|
3142
2469
|
config.root,
|
|
3143
2470
|
path.join(config.root, 'pnext-resolve.ts'),
|
|
3144
2471
|
specifier,
|
|
3145
2472
|
conditions,
|
|
3146
|
-
)
|
|
2473
|
+
)
|
|
3147
2474
|
if (fromImporter && fromImporter !== fromRoot) {
|
|
3148
|
-
return { result: { path: fromImporter, external: true }, nested: fromImporter }
|
|
2475
|
+
return { result: { path: fromImporter, external: true }, nested: fromImporter }
|
|
3149
2476
|
}
|
|
3150
|
-
return { result: { path: specifier, external: true } }
|
|
2477
|
+
return { result: { path: specifier, external: true } }
|
|
3151
2478
|
}
|
|
3152
2479
|
const resolvedEntry = resolveVendorPackageSpecifier(
|
|
3153
2480
|
config.root,
|
|
3154
2481
|
path.join(resolveDir, 'pnext-resolve.ts'),
|
|
3155
2482
|
specifier,
|
|
3156
2483
|
serverBundleConditions(conditionTarget),
|
|
3157
|
-
)
|
|
2484
|
+
)
|
|
3158
2485
|
// A CommonJS dependency loses its named exports when externalized (esbuild emits only `default` for a CJS
|
|
3159
2486
|
// entry), so it is inlined here instead. `isEsmModuleEntry` first checks CONTENT, not just extension and
|
|
3160
2487
|
// `package.json#type` - most "CJS" entries are plain-.js ESM and inline for no reason. What is left gets a
|
|
3161
2488
|
// verified named-export facade when it publishes names, and is inlined only when it does not.
|
|
3162
|
-
const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!)
|
|
3163
|
-
if (commonJs && !(await cjsEntryMayHaveNamedExports(resolvedEntry!))) return { result: undefined }
|
|
3164
|
-
let resolvedPackage: string
|
|
3165
|
-
const tv = performance.now()
|
|
2489
|
+
const commonJs = Boolean(resolvedEntry) && !isEsmModuleEntry(resolvedEntry!)
|
|
2490
|
+
if (commonJs && !(await cjsEntryMayHaveNamedExports(resolvedEntry!))) return { result: undefined }
|
|
2491
|
+
let resolvedPackage: string
|
|
2492
|
+
const tv = performance.now()
|
|
3166
2493
|
try {
|
|
3167
2494
|
resolvedPackage = await externalServerPackageHref(
|
|
3168
2495
|
config,
|
|
@@ -3172,211 +2499,90 @@ async function externalDependencyDecision(
|
|
|
3172
2499
|
conditionTarget,
|
|
3173
2500
|
// Raised from inside a running build: must not queue behind it.
|
|
3174
2501
|
true,
|
|
3175
|
-
)
|
|
2502
|
+
)
|
|
3176
2503
|
} catch (error) {
|
|
3177
|
-
if (isResolveFailure(error)) return { result: { path: specifier, external: true } }
|
|
3178
|
-
return { result: undefined, error }
|
|
2504
|
+
if (isResolveFailure(error)) return { result: { path: specifier, external: true } }
|
|
2505
|
+
return { result: undefined, error }
|
|
3179
2506
|
}
|
|
3180
2507
|
if (commonJs) {
|
|
3181
|
-
heavyProfRow({
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
2508
|
+
heavyProfRow({
|
|
2509
|
+
k: 'dep-build-wait',
|
|
2510
|
+
site: 'plugin',
|
|
2511
|
+
spec: specifier,
|
|
2512
|
+
ms: performance.now() - tv,
|
|
2513
|
+
})
|
|
2514
|
+
const tf = performance.now()
|
|
2515
|
+
const facade = await cjsNamedExportFacade(fileURLToPath(resolvedPackage), resolvedPackage)
|
|
2516
|
+
heavyProfRow({ k: 'facade-wait', site: 'plugin', spec: specifier, ms: performance.now() - tf })
|
|
2517
|
+
if (!facade) return { result: undefined }
|
|
2518
|
+
return { result: { path: pathToFileHref(facade), external: true }, vendored: true }
|
|
3187
2519
|
}
|
|
3188
|
-
return { result: { path: resolvedPackage, external: true }, vendored: true }
|
|
2520
|
+
return { result: { path: resolvedPackage, external: true }, vendored: true }
|
|
3189
2521
|
}
|
|
3190
2522
|
|
|
3191
2523
|
/** `PNEXT_VENDOR_YIELD=0` keeps the slot across a nested dependency's build. */
|
|
3192
2524
|
function yieldVendorSlotWhile<T>(task: () => Promise<T>) {
|
|
3193
2525
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
3194
|
-
return process.env.PNEXT_VENDOR_YIELD === '0' ? task() : outsideVendorSlot(task)
|
|
2526
|
+
return process.env.PNEXT_VENDOR_YIELD === '0' ? task() : outsideVendorSlot(task)
|
|
3195
2527
|
}
|
|
3196
2528
|
|
|
3197
2529
|
function isResolveFailure(error: unknown) {
|
|
3198
|
-
const details = error instanceof Error ? `${error.message}\n${error.stack ?? ''}` : String(error)
|
|
3199
|
-
return details.includes('Could not resolve')
|
|
2530
|
+
const details = error instanceof Error ? `${error.message}\n${error.stack ?? ''}` : String(error)
|
|
2531
|
+
return details.includes('Could not resolve')
|
|
3200
2532
|
}
|
|
3201
2533
|
|
|
3202
2534
|
async function profileRuntimeStep<T>(label: string, task: () => Promise<T>) {
|
|
3203
2535
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
3204
|
-
if (!process.env.PNEXT_DEV_PROFILE) return task()
|
|
3205
|
-
const start = performance.now()
|
|
2536
|
+
if (!process.env.PNEXT_DEV_PROFILE) return task()
|
|
2537
|
+
const start = performance.now()
|
|
3206
2538
|
try {
|
|
3207
|
-
return await task()
|
|
2539
|
+
return await task()
|
|
3208
2540
|
} finally {
|
|
3209
|
-
console.log(`dev-profile compat ${label} in ${formatDuration(performance.now() - start)}`)
|
|
2541
|
+
console.log(`dev-profile compat ${label} in ${formatDuration(performance.now() - start)}`)
|
|
3210
2542
|
}
|
|
3211
2543
|
}
|
|
3212
2544
|
|
|
3213
|
-
function formatDuration(durationMs: number) {
|
|
3214
|
-
const ms = Math.max(0, durationMs);
|
|
3215
|
-
if (ms < 10) return `${ms.toFixed(1)}ms`;
|
|
3216
|
-
if (ms < 1000) return `${Math.round(ms)}ms`;
|
|
3217
|
-
return `${(ms / 1000).toFixed(ms < 10000 ? 2 : 1)}s`;
|
|
3218
|
-
}
|
|
3219
|
-
|
|
3220
2545
|
function externalBuiltinBuildPlugin(): Plugin {
|
|
3221
2546
|
return {
|
|
3222
2547
|
name: 'pnext-external-builtins',
|
|
3223
2548
|
setup(build) {
|
|
3224
2549
|
build.onResolve({ filter: /^[^./].*/ }, args => {
|
|
3225
|
-
const packageName = packageNameFromSpecifier(args.path)
|
|
2550
|
+
const packageName = packageNameFromSpecifier(args.path)
|
|
3226
2551
|
if (!packageName || (!builtinModules.includes(packageName) && !isBunBuiltin(packageName))) {
|
|
3227
|
-
return undefined
|
|
2552
|
+
return undefined
|
|
3228
2553
|
}
|
|
3229
|
-
return { path: args.path, external: true }
|
|
3230
|
-
})
|
|
2554
|
+
return { path: args.path, external: true }
|
|
2555
|
+
})
|
|
3231
2556
|
},
|
|
3232
|
-
}
|
|
2557
|
+
}
|
|
3233
2558
|
}
|
|
3234
2559
|
|
|
3235
2560
|
function isBunBuiltin(specifier: string) {
|
|
3236
|
-
return specifier === 'bun' || specifier.startsWith('bun:')
|
|
3237
|
-
}
|
|
3238
|
-
|
|
3239
|
-
function serverAssetPlugin(config?: ResolvedConfig): Plugin {
|
|
3240
|
-
return {
|
|
3241
|
-
name: 'pnext-empty-server-assets',
|
|
3242
|
-
setup(build) {
|
|
3243
|
-
build.onResolve({ filter: serverIgnoredAssetFilter }, args => {
|
|
3244
|
-
const resolved = resolveAssetPath(args.path, args.resolveDir);
|
|
3245
|
-
// A configured `turbopack.rules` loader chain for this extension (e.g.
|
|
3246
|
-
// `*.svg`) preempts the generic asset pipeline: defer so the compat
|
|
3247
|
-
// loader-rule plugin's onLoad runs the chain against the real file.
|
|
3248
|
-
if (getAssetExtensions().hasLoaderRuleFor(resolved)) {
|
|
3249
|
-
return undefined;
|
|
3250
|
-
}
|
|
3251
|
-
return { path: resolved, namespace: 'pnext-empty-server-asset' };
|
|
3252
|
-
});
|
|
3253
|
-
build.onLoad({ filter: /.*/, namespace: 'pnext-empty-server-asset' }, async args => ({
|
|
3254
|
-
contents: getCssExtensions().loadCssModuleForClient(args.path) ?? (isCssFile(args.path)
|
|
3255
|
-
? ''
|
|
3256
|
-
: isStaticImageFile(args.path)
|
|
3257
|
-
? await staticImageModuleSource(config, args.path)
|
|
3258
|
-
: 'export default "";'),
|
|
3259
|
-
loader: 'js',
|
|
3260
|
-
}));
|
|
3261
|
-
},
|
|
3262
|
-
};
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
|
-
/**
|
|
3266
|
-
* Whether `rewriteServerSource` can possibly change `source`. One regex scan
|
|
3267
|
-
* over the union of the core dynamic tokens and every registered transform's
|
|
3268
|
-
* own sniff tokens (see `withSniff`); most modules trigger nothing and skip the
|
|
3269
|
-
* whole chain.
|
|
3270
|
-
*/
|
|
3271
|
-
export function needsServerSourceRewrite(source: string) {
|
|
3272
|
-
return sourceHasDynamicImport(source) || sourceNeedsServerTransforms(source);
|
|
3273
|
-
}
|
|
3274
|
-
|
|
3275
|
-
export function rewriteServerSource(
|
|
3276
|
-
source: string,
|
|
3277
|
-
file: string,
|
|
3278
|
-
options: { nextFonts?: boolean; root?: string } = {},
|
|
3279
|
-
) {
|
|
3280
|
-
const root = options.root ?? path.dirname(file);
|
|
3281
|
-
const rewritten = sourceHasDynamicImport(source)
|
|
3282
|
-
? rewriteDynamicCallTargets(
|
|
3283
|
-
rewriteLiteralDynamicCalls(source, file),
|
|
3284
|
-
specifier => resolveImport(root, file, specifier),
|
|
3285
|
-
file,
|
|
3286
|
-
)
|
|
3287
|
-
: source;
|
|
3288
|
-
// Compat pre-transforms (next/font/root-params) run before generic server
|
|
3289
|
-
// transforms (use-cache/action tags). Pure-core apps register no transforms.
|
|
3290
|
-
if (options.nextFonts === false) return rewritten;
|
|
3291
|
-
if (!sourceNeedsServerTransforms(rewritten)) return rewritten;
|
|
3292
|
-
return applyServerSourceTransforms(
|
|
3293
|
-
applyServerSourcePreTransforms(rewritten, file, options.root),
|
|
3294
|
-
file,
|
|
3295
|
-
options.root,
|
|
3296
|
-
);
|
|
2561
|
+
return specifier === 'bun' || specifier.startsWith('bun:')
|
|
3297
2562
|
}
|
|
3298
2563
|
|
|
3299
2564
|
export function runtimeImportTarget(specifier: string, format: 'esm' | 'cjs' = 'esm') {
|
|
3300
|
-
if (isRelativeOrAbsoluteSpecifier(specifier)) return specifier
|
|
3301
|
-
const target = firstAliasForSpecifier(specifier)
|
|
3302
|
-
if (!target) return specifier
|
|
3303
|
-
return format === 'esm' ? pathToFileHref(target) : target
|
|
2565
|
+
if (isRelativeOrAbsoluteSpecifier(specifier)) return specifier
|
|
2566
|
+
const target = firstAliasForSpecifier(specifier)
|
|
2567
|
+
if (!target) return specifier
|
|
2568
|
+
return format === 'esm' ? pathToFileHref(target) : target
|
|
3304
2569
|
}
|
|
3305
2570
|
|
|
3306
2571
|
export function runtimeServerImportTarget(specifier: string) {
|
|
3307
|
-
return runtimeImportTarget(specifier, 'esm')
|
|
2572
|
+
return runtimeImportTarget(specifier, 'esm')
|
|
3308
2573
|
}
|
|
3309
2574
|
|
|
3310
2575
|
function isRelativeOrAbsoluteSpecifier(specifier: string) {
|
|
3311
|
-
return specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')
|
|
2576
|
+
return specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:')
|
|
3312
2577
|
}
|
|
3313
2578
|
|
|
3314
2579
|
function isPackageSpecifier(specifier: string) {
|
|
3315
|
-
if (isRelativeOrAbsoluteSpecifier(specifier)) return false
|
|
3316
|
-
if (specifier.startsWith('#')) return false
|
|
3317
|
-
if (specifier.startsWith('node:')) return false
|
|
3318
|
-
if (specifier.includes(':')) return false
|
|
3319
|
-
const packageName = packageNameFromSpecifier(specifier)
|
|
3320
|
-
return Boolean(packageName && !builtinModules.includes(packageName))
|
|
3321
|
-
}
|
|
3322
|
-
|
|
3323
|
-
function isInside(root: string, file: string) {
|
|
3324
|
-
const relative = path.relative(root, file);
|
|
3325
|
-
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative));
|
|
3326
|
-
}
|
|
3327
|
-
|
|
3328
|
-
function rootPaths(root: string) {
|
|
3329
|
-
const key = path.resolve(root);
|
|
3330
|
-
const cached = rootPathCache.get(key);
|
|
3331
|
-
if (cached) return cached;
|
|
3332
|
-
const paths = new Set([path.resolve(root)]);
|
|
3333
|
-
try {
|
|
3334
|
-
paths.add(realpathSync.native(root));
|
|
3335
|
-
} catch {
|
|
3336
|
-
// The dev cache root is registered before it exists.
|
|
3337
|
-
}
|
|
3338
|
-
const resolved = [...paths];
|
|
3339
|
-
rootPathCache.set(key, resolved);
|
|
3340
|
-
return resolved;
|
|
3341
|
-
}
|
|
3342
|
-
|
|
3343
|
-
function rootFilter(root: string) {
|
|
3344
|
-
// Base source extensions plus any extra loadable extensions registered by
|
|
3345
|
-
// compat (e.g. mdx/md via pageExtensions). Without these, `.mdx` modules skip
|
|
3346
|
-
// the transform load hook and resolve as raw assets (default export = path).
|
|
3347
|
-
const extras = currentLoadExtras().map(escapeRegex);
|
|
3348
|
-
const exts = ['[jt]sx?', ...extras].join('|');
|
|
3349
|
-
// Exclude `next.config.{js,cjs,mjs,ts}`: it is loaded for its exported value (often CommonJS
|
|
3350
|
-
// `module.exports = ...`), not as part of the app's server module graph. Routing it through the
|
|
3351
|
-
// ES-module source transform drops the CJS exports, silently disabling next.config
|
|
3352
|
-
// rewrites/redirects.
|
|
3353
|
-
// Exclude the vendor tree under the cache root: the `*.dist/` copies copyBrowserReadyEsmDist
|
|
3354
|
-
// publishes are verbatim third-party ESM the copy predicate has already proved needs no server
|
|
3355
|
-
// rewrite, but they are `.js`, so each was read and re-transformed on every boot. Bun loads them
|
|
3356
|
-
// natively instead. Two clauses because the vendor tree is reachable from two registered roots.
|
|
3357
|
-
const vendorGuard = vendorLoadsNatively()
|
|
3358
|
-
? `${root.endsWith(`${path.sep}cache${path.sep}server`) ? '(?!\\/vendor\\/)' : ''}(?!.*\\/cache\\/server\\/vendor\\/)`
|
|
3359
|
-
: '';
|
|
3360
|
-
return new RegExp(
|
|
3361
|
-
`^${escapeRegex(root)}${vendorGuard}(?!.*\\/node_modules\\/)(?!\\/next\\.config\\.(?:js|cjs|mjs|ts)$)(?:/.*)?\\.(?:${exts})$`,
|
|
3362
|
-
);
|
|
3363
|
-
}
|
|
3364
|
-
|
|
3365
|
-
function esbuildLoader(file: string) {
|
|
3366
|
-
if (file.endsWith('.tsx')) return 'tsx';
|
|
3367
|
-
if (file.endsWith('.ts')) return 'ts';
|
|
3368
|
-
// Workspace .js/.jsx may contain JSX (Next allows it); jsx is a js superset.
|
|
3369
|
-
return 'jsx';
|
|
3370
|
-
}
|
|
3371
|
-
|
|
3372
|
-
function hashRoot(value: string) {
|
|
3373
|
-
let hash = 5381;
|
|
3374
|
-
for (const char of value) hash = ((hash << 5) + hash) ^ char.charCodeAt(0);
|
|
3375
|
-
return (hash >>> 0).toString(36);
|
|
3376
|
-
}
|
|
3377
|
-
|
|
3378
|
-
function hashBundleSpecifier(value: string) {
|
|
3379
|
-
return createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
2580
|
+
if (isRelativeOrAbsoluteSpecifier(specifier)) return false
|
|
2581
|
+
if (specifier.startsWith('#')) return false
|
|
2582
|
+
if (specifier.startsWith('node:')) return false
|
|
2583
|
+
if (specifier.includes(':')) return false
|
|
2584
|
+
const packageName = packageNameFromSpecifier(specifier)
|
|
2585
|
+
return Boolean(packageName && !builtinModules.includes(packageName))
|
|
3380
2586
|
}
|
|
3381
2587
|
|
|
3382
2588
|
// Trace copies of nested transitive externals (a version distinct from the
|
|
@@ -3384,74 +2590,20 @@ function hashBundleSpecifier(value: string) {
|
|
|
3384
2590
|
// tracing / server-bundle scans observe the bundled dependency the way
|
|
3385
2591
|
// webpack's bundled transitive would appear. Trace-only: never imported.
|
|
3386
2592
|
async function emitNestedExternalTraceCopies(vendorFile: string, sources: readonly string[]) {
|
|
3387
|
-
const dir = path.dirname(vendorFile)
|
|
2593
|
+
const dir = path.dirname(vendorFile)
|
|
3388
2594
|
for (const source of new Set(sources)) {
|
|
3389
2595
|
try {
|
|
3390
|
-
const content = await readFile(source, 'utf8')
|
|
3391
|
-
const out = path.join(dir, `${hashBundleSpecifier(source)}.trace.js`)
|
|
3392
|
-
if (!existsSync(out)) await writeFileAtomic(out, content)
|
|
2596
|
+
const content = await readFile(source, 'utf8')
|
|
2597
|
+
const out = path.join(dir, `${hashBundleSpecifier(source)}.trace.js`)
|
|
2598
|
+
if (!existsSync(out)) await writeFileAtomic(out, content)
|
|
3393
2599
|
} catch {
|
|
3394
2600
|
// A source that cannot be read is simply not traced.
|
|
3395
2601
|
}
|
|
3396
2602
|
}
|
|
3397
2603
|
}
|
|
3398
2604
|
|
|
3399
|
-
|
|
3400
|
-
const serverIgnoredAssetFilter =
|
|
3401
|
-
/\.(?:css|scss|sass|woff2?|ttf|otf|eot|png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/;
|
|
3402
|
-
|
|
3403
|
-
function isCssFile(file: string) {
|
|
3404
|
-
return file.endsWith('.css');
|
|
3405
|
-
}
|
|
3406
|
-
|
|
3407
|
-
function isStaticImageFile(file: string) {
|
|
3408
|
-
return /\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/.test(file);
|
|
3409
|
-
}
|
|
3410
|
-
|
|
3411
|
-
function resolveAssetPath(specifier: string, resolveDir: string) {
|
|
3412
|
-
const [sourcePath = '', hash = ''] = specifier.split('#', 2);
|
|
3413
|
-
const resolved = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(resolveDir, sourcePath);
|
|
3414
|
-
return hash ? `${resolved}#${hash}` : resolved;
|
|
3415
|
-
}
|
|
3416
|
-
|
|
3417
|
-
async function staticImageModuleSource(config: ResolvedConfig | undefined, file: string) {
|
|
3418
|
-
const [sourcePath = ''] = file.split('#', 1);
|
|
3419
|
-
const bytes = new Uint8Array(await readFile(sourcePath));
|
|
3420
|
-
const emitted: { relative: string; bytes: Uint8Array }[] = [];
|
|
3421
|
-
const emit = (relative: string, data: Uint8Array) => {
|
|
3422
|
-
emitted.push({ relative, bytes: data });
|
|
3423
|
-
return `/${relative}`;
|
|
3424
|
-
};
|
|
3425
|
-
const compat = await getAssetExtensions().staticAssetModule({ sourcePath, bytes, emit });
|
|
3426
|
-
const source = compat ?? coreStaticAssetModule(sourcePath, bytes, emit);
|
|
3427
|
-
if (config) {
|
|
3428
|
-
for (const asset of emitted) {
|
|
3429
|
-
const target = path.join(config.outPath, 'public', ...asset.relative.split('/'));
|
|
3430
|
-
await mkdir(path.dirname(target), { recursive: true });
|
|
3431
|
-
if (!existsSync(target)) await Bun.write(target, asset.bytes);
|
|
3432
|
-
}
|
|
3433
|
-
}
|
|
3434
|
-
return source;
|
|
3435
|
-
}
|
|
3436
|
-
|
|
3437
|
-
// Core's generic static-asset module: emit the file under a hashed media URL
|
|
3438
|
-
// and export it as the default (a plain URL string) when no compat override is
|
|
3439
|
-
// registered. next/image compat replaces this with a full StaticImageData shape.
|
|
3440
|
-
function coreStaticAssetModule(
|
|
3441
|
-
sourcePath: string,
|
|
3442
|
-
bytes: Uint8Array,
|
|
3443
|
-
emit: (relative: string, bytes: Uint8Array) => string,
|
|
3444
|
-
): string {
|
|
3445
|
-
const ext = path.extname(sourcePath).toLowerCase() || '.bin';
|
|
3446
|
-
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8);
|
|
3447
|
-
const base = path.basename(sourcePath, path.extname(sourcePath)).replace(/[^A-Za-z0-9_-]+/g, '-');
|
|
3448
|
-
const relative = getAssetExtensions().staticAssetRelativePath({ sourcePath, hash, base, ext });
|
|
3449
|
-
const src = emit(relative, bytes);
|
|
3450
|
-
return `const src = ${JSON.stringify(src)};\nexport default src;\nexport { src };\n`;
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
2605
|
function packageNameFromSpecifier(specifier: string) {
|
|
3454
|
-
const normalized = specifier.startsWith('node:') ? specifier.slice(5) : specifier
|
|
3455
|
-
const parts = normalized.split('/')
|
|
3456
|
-
return normalized.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
2606
|
+
const normalized = specifier.startsWith('node:') ? specifier.slice(5) : specifier
|
|
2607
|
+
const parts = normalized.split('/')
|
|
2608
|
+
return normalized.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
3457
2609
|
}
|