@wular/pnext 0.0.3 → 0.0.6
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 +11 -11
- package/bin/pnext +1 -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 +1 -1
- package/reference/config.md +2 -2
- package/reference/css.md +13 -9
- package/reference/dev.md +1 -1
- package/reference/metadata.md +7 -7
- package/reference/navigation.md +35 -28
- package/reference/performance.md +70 -70
- package/reference/rendering.md +17 -17
- package/reference/routing.md +17 -17
- package/reference/typegen.md +12 -8
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +113 -109
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +49 -52
- 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 +71 -63
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +88 -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 +794 -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 +17 -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 +1988 -1961
- package/src/{api → client}/router/types.ts +93 -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 -1887
- 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 +69 -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 +2076 -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} +152 -143
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +442 -186
- 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 +69 -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 +833 -848
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +952 -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 +425 -404
- package/src/styles.d.ts +9 -0
- package/src/types.ts +320 -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/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
package/src/runtime/vendor.ts
CHANGED
|
@@ -33,50 +33,50 @@
|
|
|
33
33
|
* on "No matching export in X for import Y", so a clean re-bundle proves the facade resolves and
|
|
34
34
|
* exports everything it claims. Packages left with no names (default-only) stay inlined.
|
|
35
35
|
*/
|
|
36
|
-
import { appendFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
37
|
-
import { copyFile, mkdir, readFile } from 'node:fs/promises'
|
|
38
|
-
import path from 'node:path'
|
|
39
|
-
import { fileURLToPath } from 'node:url'
|
|
40
|
-
import { build } from '../utils/esbuild'
|
|
41
|
-
import { isCommonJsModuleSource } from '../resolve/imports'
|
|
42
|
-
import { outputSpecifiers } from '
|
|
43
|
-
import { escapeRegex, isIdentifier, uniqueIdentifier } from '../utils/
|
|
44
|
-
import { cachedExistsSync } from '../utils/fs-cache'
|
|
45
|
-
import { writeFileAtomic } from '../utils/fs'
|
|
36
|
+
import { appendFileSync, existsSync, mkdirSync } from 'node:fs'
|
|
37
|
+
import { copyFile, mkdir, readFile } from 'node:fs/promises'
|
|
38
|
+
import path from 'node:path'
|
|
39
|
+
import { fileURLToPath } from 'node:url'
|
|
40
|
+
import { build } from '../utils/esbuild'
|
|
41
|
+
import { isCommonJsModuleSource } from '../resolve/imports'
|
|
42
|
+
import { outputSpecifiers } from './module-transform'
|
|
43
|
+
import { escapeRegex, isIdentifier, uniqueIdentifier } from '../utils/code'
|
|
44
|
+
import { cachedExistsSync } from '../utils/fs-cache'
|
|
45
|
+
import { writeFileAtomic } from '../utils/fs'
|
|
46
46
|
|
|
47
47
|
// --------------------------------------------------------------------------
|
|
48
48
|
// demand scheduling
|
|
49
49
|
// --------------------------------------------------------------------------
|
|
50
50
|
|
|
51
51
|
export interface VendorGroupMember {
|
|
52
|
-
specifier: string
|
|
52
|
+
specifier: string
|
|
53
53
|
/** Resolved entry file for this subpath. */
|
|
54
|
-
entry: string
|
|
54
|
+
entry: string
|
|
55
55
|
/** Artifact path, identical to the one a solo build of this subpath writes. */
|
|
56
|
-
file: string
|
|
56
|
+
file: string
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export interface VendorGroupPlan {
|
|
60
|
-
key: string
|
|
60
|
+
key: string
|
|
61
61
|
/** The demanded subpath — the only thing a group is ever allowed to compile. */
|
|
62
|
-
member: VendorGroupMember
|
|
62
|
+
member: VendorGroupMember
|
|
63
63
|
/** `external` are siblings already on disk: referenced, never re-parsed. */
|
|
64
64
|
build: (
|
|
65
65
|
members: VendorGroupMember[],
|
|
66
66
|
external: readonly VendorGroupMember[],
|
|
67
67
|
nested: boolean,
|
|
68
|
-
) => Promise<void
|
|
68
|
+
) => Promise<void>
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
export interface VendorBuildPlan {
|
|
72
72
|
/** Artifact key — the ONLY dedup identity. Importer directories never enter it. */
|
|
73
|
-
key: string
|
|
74
|
-
file: string
|
|
73
|
+
key: string
|
|
74
|
+
file: string
|
|
75
75
|
/** Disk cache / no-compile fast paths; a returned path settles the demand. */
|
|
76
|
-
prepare: () => Promise<string | undefined
|
|
76
|
+
prepare: () => Promise<string | undefined>
|
|
77
77
|
/** `nested` marks a build raised from inside another: it may never wait. */
|
|
78
|
-
buildOne: (nested: boolean) => Promise<string
|
|
79
|
-
group?: VendorGroupPlan
|
|
78
|
+
buildOne: (nested: boolean) => Promise<string>
|
|
79
|
+
group?: VendorGroupPlan
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
@@ -85,31 +85,31 @@ export interface VendorBuildPlan {
|
|
|
85
85
|
*/
|
|
86
86
|
interface VendorPromotable {
|
|
87
87
|
/** Set only while the work sits in the queue; cleared once it runs. */
|
|
88
|
-
promote?: () => void
|
|
88
|
+
promote?: () => void
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
interface PendingBundle extends VendorPromotable {
|
|
92
|
-
promise: Promise<string
|
|
93
|
-
result?: string
|
|
92
|
+
promise: Promise<string>
|
|
93
|
+
result?: string
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
interface VendorGroupState {
|
|
97
97
|
/** Demanded, not yet compiled. */
|
|
98
|
-
pending: Map<string, VendorGroupMember
|
|
98
|
+
pending: Map<string, VendorGroupMember>
|
|
99
99
|
/** Compiled by this group. */
|
|
100
|
-
produced: Map<string, VendorGroupMember
|
|
100
|
+
produced: Map<string, VendorGroupMember>
|
|
101
101
|
/** Handed back to the single-bundle path — a round with nothing to share. */
|
|
102
|
-
solo: Map<string, VendorGroupMember
|
|
103
|
-
round?: Promise<void
|
|
102
|
+
solo: Map<string, VendorGroupMember>
|
|
103
|
+
round?: Promise<void>
|
|
104
104
|
/** The current round's queue ticket, while it is still waiting for a slot. */
|
|
105
|
-
ticket?: VendorPromotable
|
|
106
|
-
rounds: number
|
|
105
|
+
ticket?: VendorPromotable
|
|
106
|
+
rounds: number
|
|
107
107
|
/** Once closed (failed, or out of rounds) every member builds alone. */
|
|
108
|
-
closed: boolean
|
|
108
|
+
closed: boolean
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const pendingBundles = new Map<string, PendingBundle>()
|
|
112
|
-
const vendorGroups = new Map<string, VendorGroupState>()
|
|
111
|
+
const pendingBundles = new Map<string, PendingBundle>()
|
|
112
|
+
const vendorGroups = new Map<string, VendorGroupState>()
|
|
113
113
|
|
|
114
114
|
// §4e worker sweep (1/2/4/6/7/8/12 → 3.96/2.03/1.68/1.62/1.55/1.94/1.98 s):
|
|
115
115
|
// 6-7 concurrent builds is the optimum and going past core count regresses.
|
|
@@ -117,7 +117,7 @@ const VENDOR_CONCURRENCY = Math.max(
|
|
|
117
117
|
1,
|
|
118
118
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
119
119
|
Number(process.env.PNEXT_VENDOR_CONCURRENCY) || 7,
|
|
120
|
-
)
|
|
120
|
+
)
|
|
121
121
|
/**
|
|
122
122
|
* Ceiling on builds that have STARTED and not finished, which is a different resource from the
|
|
123
123
|
* compute slots: a build waiting on a dependency gives its slot back (see `outsideVendorSlot`) but
|
|
@@ -129,11 +129,11 @@ const VENDOR_OPEN_LIMIT = Math.max(
|
|
|
129
129
|
VENDOR_CONCURRENCY,
|
|
130
130
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
131
131
|
Number(process.env.PNEXT_VENDOR_OPEN_LIMIT) || VENDOR_CONCURRENCY * 8,
|
|
132
|
-
)
|
|
133
|
-
let vendorActive = 0
|
|
134
|
-
let vendorOpen = 0
|
|
132
|
+
)
|
|
133
|
+
let vendorActive = 0
|
|
134
|
+
let vendorOpen = 0
|
|
135
135
|
/** Queued starts; each returns whether it actually took the slot. */
|
|
136
|
-
const vendorQueue: (() => boolean)[] = []
|
|
136
|
+
const vendorQueue: (() => boolean)[] = []
|
|
137
137
|
|
|
138
138
|
/**
|
|
139
139
|
* Run a vendor build under the concurrency cap. Nested builds - raised by a running build's own resolve
|
|
@@ -149,31 +149,35 @@ function withVendorSlot<T>(
|
|
|
149
149
|
task: () => Promise<T>,
|
|
150
150
|
ticket?: VendorPromotable,
|
|
151
151
|
): Promise<T> {
|
|
152
|
-
return watchVendorWait(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
started =
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
152
|
+
return watchVendorWait(
|
|
153
|
+
nested ? 'slot-nested' : 'slot',
|
|
154
|
+
label,
|
|
155
|
+
new Promise<T>((resolve, reject) => {
|
|
156
|
+
let started = false
|
|
157
|
+
const run = () => {
|
|
158
|
+
if (started) return false
|
|
159
|
+
started = true
|
|
160
|
+
if (ticket) ticket.promote = undefined
|
|
161
|
+
vendorActive += 1
|
|
162
|
+
vendorOpen += 1
|
|
163
|
+
const span = openVendorSpan(label, nested)
|
|
164
|
+
task()
|
|
165
|
+
.then(resolve, reject)
|
|
166
|
+
.finally(() => {
|
|
167
|
+
vendorActive -= 1
|
|
168
|
+
vendorOpen -= 1
|
|
169
|
+
closeVendorSpan(span)
|
|
170
|
+
drainVendorQueue()
|
|
171
|
+
})
|
|
172
|
+
return true
|
|
173
|
+
}
|
|
174
|
+
if (nested || vendorActive < VENDOR_CONCURRENCY) run()
|
|
175
|
+
else {
|
|
176
|
+
if (ticket) ticket.promote = run
|
|
177
|
+
vendorQueue.push(run)
|
|
178
|
+
}
|
|
179
|
+
}),
|
|
180
|
+
)
|
|
177
181
|
}
|
|
178
182
|
|
|
179
183
|
/**
|
|
@@ -184,22 +188,22 @@ function withVendorSlot<T>(
|
|
|
184
188
|
export async function outsideVendorSlot<T>(task: () => Promise<T>): Promise<T> {
|
|
185
189
|
// At the open ceiling, keeping the slot IS the back-pressure: yielding it
|
|
186
190
|
// would only admit one more bundler to sit open alongside this one.
|
|
187
|
-
if (vendorOpen >= VENDOR_OPEN_LIMIT) return task()
|
|
188
|
-
vendorActive -= 1
|
|
189
|
-
drainVendorQueue()
|
|
191
|
+
if (vendorOpen >= VENDOR_OPEN_LIMIT) return task()
|
|
192
|
+
vendorActive -= 1
|
|
193
|
+
drainVendorQueue()
|
|
190
194
|
try {
|
|
191
|
-
return await task()
|
|
195
|
+
return await task()
|
|
192
196
|
} finally {
|
|
193
|
-
vendorActive += 1
|
|
197
|
+
vendorActive += 1
|
|
194
198
|
}
|
|
195
199
|
}
|
|
196
200
|
|
|
197
201
|
/** Hand the freed slot to the first queued build that has not been promoted. */
|
|
198
202
|
function drainVendorQueue() {
|
|
199
203
|
while (vendorActive < VENDOR_CONCURRENCY) {
|
|
200
|
-
const next = vendorQueue.shift()
|
|
201
|
-
if (!next) return
|
|
202
|
-
if (next()) return
|
|
204
|
+
const next = vendorQueue.shift()
|
|
205
|
+
if (!next) return
|
|
206
|
+
if (next()) return
|
|
203
207
|
}
|
|
204
208
|
}
|
|
205
209
|
|
|
@@ -209,7 +213,7 @@ function drainVendorQueue() {
|
|
|
209
213
|
* to move.
|
|
210
214
|
*/
|
|
211
215
|
function promoteVendorWork(work: VendorPromotable | undefined) {
|
|
212
|
-
work?.promote?.()
|
|
216
|
+
work?.promote?.()
|
|
213
217
|
}
|
|
214
218
|
|
|
215
219
|
// --------------------------------------------------------------------------
|
|
@@ -218,26 +222,26 @@ function promoteVendorWork(work: VendorPromotable | undefined) {
|
|
|
218
222
|
|
|
219
223
|
/** One build's occupancy of a worker slot — the active-builds-over-time input. */
|
|
220
224
|
export interface VendorBuildSpan {
|
|
221
|
-
label: string
|
|
222
|
-
nested: boolean
|
|
223
|
-
startMs: number
|
|
224
|
-
endMs?: number
|
|
225
|
+
label: string
|
|
226
|
+
nested: boolean
|
|
227
|
+
startMs: number
|
|
228
|
+
endMs?: number
|
|
225
229
|
}
|
|
226
230
|
|
|
227
|
-
const vendorSpans: VendorBuildSpan[] = []
|
|
231
|
+
const vendorSpans: VendorBuildSpan[] = []
|
|
228
232
|
|
|
229
233
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
230
|
-
const vendorTracing = () => process.env.PNEXT_VENDOR_TRACE === '1'
|
|
234
|
+
const vendorTracing = () => process.env.PNEXT_VENDOR_TRACE === '1'
|
|
231
235
|
|
|
232
236
|
function openVendorSpan(label: string, nested: boolean) {
|
|
233
|
-
if (!vendorTracing()) return undefined
|
|
234
|
-
const span: VendorBuildSpan = { label, nested, startMs: performance.now() }
|
|
235
|
-
vendorSpans.push(span)
|
|
236
|
-
return span
|
|
237
|
+
if (!vendorTracing()) return undefined
|
|
238
|
+
const span: VendorBuildSpan = { label, nested, startMs: performance.now() }
|
|
239
|
+
vendorSpans.push(span)
|
|
240
|
+
return span
|
|
237
241
|
}
|
|
238
242
|
|
|
239
243
|
function closeVendorSpan(span: VendorBuildSpan | undefined) {
|
|
240
|
-
if (span) span.endMs = performance.now()
|
|
244
|
+
if (span) span.endMs = performance.now()
|
|
241
245
|
}
|
|
242
246
|
|
|
243
247
|
// --------------------------------------------------------------------------
|
|
@@ -248,27 +252,27 @@ function closeVendorSpan(span: VendorBuildSpan | undefined) {
|
|
|
248
252
|
// bench/tools/vendor-analyze.ts; `=1` still means the in-memory spans only.
|
|
249
253
|
const vendorTraceFile = (() => {
|
|
250
254
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
251
|
-
const value = process.env.PNEXT_VENDOR_TRACE
|
|
252
|
-
if (!value || !(value.includes('/') || value.endsWith('.jsonl'))) return undefined
|
|
253
|
-
return path.resolve(value)
|
|
254
|
-
})()
|
|
255
|
+
const value = process.env.PNEXT_VENDOR_TRACE
|
|
256
|
+
if (!value || !(value.includes('/') || value.endsWith('.jsonl'))) return undefined
|
|
257
|
+
return path.resolve(value)
|
|
258
|
+
})()
|
|
255
259
|
|
|
256
|
-
let vendorTraceDirReady = false
|
|
257
|
-
let vendorTraceSeq = 0
|
|
260
|
+
let vendorTraceDirReady = false
|
|
261
|
+
let vendorTraceSeq = 0
|
|
258
262
|
|
|
259
|
-
export const vendorTraceEnabled = () => vendorTraceFile !== undefined
|
|
263
|
+
export const vendorTraceEnabled = () => vendorTraceFile !== undefined
|
|
260
264
|
|
|
261
|
-
export const nextVendorTraceSeq = () => vendorTraceSeq
|
|
265
|
+
export const nextVendorTraceSeq = () => vendorTraceSeq++
|
|
262
266
|
|
|
263
267
|
/** Append one JSONL row, stamped with the wall clock the analyzer unions on. */
|
|
264
268
|
export function vendorTraceRow(row: Record<string, unknown>) {
|
|
265
|
-
if (!vendorTraceFile) return
|
|
269
|
+
if (!vendorTraceFile) return
|
|
266
270
|
try {
|
|
267
271
|
if (!vendorTraceDirReady) {
|
|
268
|
-
mkdirSync(path.dirname(vendorTraceFile), { recursive: true })
|
|
269
|
-
vendorTraceDirReady = true
|
|
272
|
+
mkdirSync(path.dirname(vendorTraceFile), { recursive: true })
|
|
273
|
+
vendorTraceDirReady = true
|
|
270
274
|
}
|
|
271
|
-
appendFileSync(vendorTraceFile, `${JSON.stringify({ ...row, w: Date.now() })}\n`)
|
|
275
|
+
appendFileSync(vendorTraceFile, `${JSON.stringify({ ...row, w: Date.now() })}\n`)
|
|
272
276
|
} catch {
|
|
273
277
|
// Tracing must never fail a build.
|
|
274
278
|
}
|
|
@@ -276,7 +280,7 @@ export function vendorTraceRow(row: Record<string, unknown>) {
|
|
|
276
280
|
|
|
277
281
|
/** Whether this artifact key already has an in-flight or settled build. */
|
|
278
282
|
export function vendorBundleMemHit(key: string) {
|
|
279
|
-
return pendingBundles.has(key)
|
|
283
|
+
return pendingBundles.has(key)
|
|
280
284
|
}
|
|
281
285
|
|
|
282
286
|
// --------------------------------------------------------------------------
|
|
@@ -288,39 +292,43 @@ export function vendorBundleMemHit(key: string) {
|
|
|
288
292
|
* without it, a promise that never settles is indistinguishable from slow work. Entries are removed
|
|
289
293
|
* on settle, so a healthy pipeline holds none.
|
|
290
294
|
*/
|
|
291
|
-
const vendorWaits = new Map<number, { label: string; kind: string; startMs: number }>()
|
|
292
|
-
let vendorWaitSeq = 0
|
|
295
|
+
const vendorWaits = new Map<number, { label: string; kind: string; startMs: number }>()
|
|
296
|
+
let vendorWaitSeq = 0
|
|
293
297
|
|
|
294
298
|
const VENDOR_STALL_MS = Math.max(
|
|
295
299
|
0,
|
|
296
300
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
297
301
|
Number(process.env.PNEXT_VENDOR_STALL_MS ?? 20_000),
|
|
298
|
-
)
|
|
302
|
+
)
|
|
299
303
|
|
|
300
304
|
/** Waits older than the threshold, oldest first — the stall report's payload. */
|
|
301
305
|
function stalledVendorWaits(now: number) {
|
|
302
306
|
return [...vendorWaits.values()]
|
|
303
307
|
.filter(wait => now - wait.startMs >= VENDOR_STALL_MS)
|
|
304
|
-
.sort((a, b) => a.startMs - b.startMs)
|
|
308
|
+
.sort((a, b) => a.startMs - b.startMs)
|
|
305
309
|
}
|
|
306
310
|
|
|
307
|
-
let stallTimer: ReturnType<typeof setInterval> | undefined
|
|
311
|
+
let stallTimer: ReturnType<typeof setInterval> | undefined
|
|
308
312
|
|
|
309
313
|
function reportVendorStall() {
|
|
310
|
-
const now = performance.now()
|
|
311
|
-
const stalled = stalledVendorWaits(now)
|
|
312
|
-
if (stalled.length === 0) return
|
|
313
|
-
const trace = vendorPipelineTrace()
|
|
314
|
+
const now = performance.now()
|
|
315
|
+
const stalled = stalledVendorWaits(now)
|
|
316
|
+
if (stalled.length === 0) return
|
|
317
|
+
const trace = vendorPipelineTrace()
|
|
314
318
|
console.error(
|
|
315
319
|
[
|
|
316
320
|
`PNext vendor pipeline stalled: ${stalled.length} wait(s) over ${Math.round(VENDOR_STALL_MS)}ms.`,
|
|
317
321
|
` slots active=${trace.active}/${trace.concurrency} open=${trace.open}/${trace.openLimit} queued=${trace.queued}`,
|
|
318
322
|
` groups=${vendorGroups.size} unsettled bundles=${trace.unsettled.length}`,
|
|
319
|
-
...stalled
|
|
320
|
-
|
|
323
|
+
...stalled
|
|
324
|
+
.slice(0, 20)
|
|
325
|
+
.map(w => ` [${w.kind}] ${w.label} waiting ${Math.round(now - w.startMs)}ms`),
|
|
326
|
+
...(trace.unsettled.length
|
|
327
|
+
? [` unsettled: ${trace.unsettled.slice(0, 20).join(', ')}`]
|
|
328
|
+
: []),
|
|
321
329
|
` set PNEXT_VENDOR_STALL_MS=0 to silence, PNEXT_VENDOR_GROUP=0 to bisect grouping.`,
|
|
322
330
|
].join('\n'),
|
|
323
|
-
)
|
|
331
|
+
)
|
|
324
332
|
}
|
|
325
333
|
|
|
326
334
|
/**
|
|
@@ -328,31 +336,31 @@ function reportVendorStall() {
|
|
|
328
336
|
* changes what resolves — so arming it cannot itself introduce a stall.
|
|
329
337
|
*/
|
|
330
338
|
function watchVendorWait<T>(kind: string, label: string, promise: Promise<T>): Promise<T> {
|
|
331
|
-
if (VENDOR_STALL_MS <= 0) return promise
|
|
332
|
-
const id = vendorWaitSeq
|
|
333
|
-
vendorWaits.set(id, { kind, label, startMs: performance.now() })
|
|
339
|
+
if (VENDOR_STALL_MS <= 0) return promise
|
|
340
|
+
const id = vendorWaitSeq++
|
|
341
|
+
vendorWaits.set(id, { kind, label, startMs: performance.now() })
|
|
334
342
|
if (!stallTimer) {
|
|
335
|
-
stallTimer = setInterval(reportVendorStall, Math.max(1000, VENDOR_STALL_MS))
|
|
343
|
+
stallTimer = setInterval(reportVendorStall, Math.max(1000, VENDOR_STALL_MS))
|
|
336
344
|
// Never keep the process alive for the watchdog alone.
|
|
337
|
-
stallTimer.unref?.()
|
|
345
|
+
stallTimer.unref?.()
|
|
338
346
|
}
|
|
339
347
|
return promise.finally(() => {
|
|
340
|
-
vendorWaits.delete(id)
|
|
348
|
+
vendorWaits.delete(id)
|
|
341
349
|
if (vendorWaits.size === 0 && stallTimer) {
|
|
342
|
-
clearInterval(stallTimer)
|
|
343
|
-
stallTimer = undefined
|
|
350
|
+
clearInterval(stallTimer)
|
|
351
|
+
stallTimer = undefined
|
|
344
352
|
}
|
|
345
|
-
})
|
|
353
|
+
})
|
|
346
354
|
}
|
|
347
355
|
|
|
348
356
|
/** Scheduler waits currently outstanding — for tests and external diagnostics. */
|
|
349
357
|
export function vendorWaitReport() {
|
|
350
|
-
const now = performance.now()
|
|
358
|
+
const now = performance.now()
|
|
351
359
|
return [...vendorWaits.values()].map(w => ({
|
|
352
360
|
kind: w.kind,
|
|
353
361
|
label: w.label,
|
|
354
362
|
waitedMs: now - w.startMs,
|
|
355
|
-
}))
|
|
363
|
+
}))
|
|
356
364
|
}
|
|
357
365
|
|
|
358
366
|
/**
|
|
@@ -371,40 +379,40 @@ export function vendorPipelineTrace() {
|
|
|
371
379
|
unsettled: [...pendingBundles]
|
|
372
380
|
.filter(([, pending]) => pending.result === undefined)
|
|
373
381
|
.map(([key]) => key),
|
|
374
|
-
}
|
|
382
|
+
}
|
|
375
383
|
}
|
|
376
384
|
|
|
377
385
|
/** The single funnel every vendor demand goes through. */
|
|
378
386
|
export function vendorBundle(plan: VendorBuildPlan, nested = false): Promise<string> {
|
|
379
|
-
const existing = pendingBundles.get(plan.key)
|
|
387
|
+
const existing = pendingBundles.get(plan.key)
|
|
380
388
|
if (existing) {
|
|
381
389
|
// Dedup is what makes this a deadlock risk: the demand we join may still be
|
|
382
390
|
// queued, and a nested caller cannot afford to wait for the queue to move.
|
|
383
|
-
if (nested) promoteVendorWork(existing)
|
|
384
|
-
return watchVendorWait(nested ? 'dedup-nested' : 'dedup', plan.key, existing.promise)
|
|
391
|
+
if (nested) promoteVendorWork(existing)
|
|
392
|
+
return watchVendorWait(nested ? 'dedup-nested' : 'dedup', plan.key, existing.promise)
|
|
385
393
|
}
|
|
386
|
-
const pending: PendingBundle = { promise: undefined as unknown as Promise<string> }
|
|
394
|
+
const pending: PendingBundle = { promise: undefined as unknown as Promise<string> }
|
|
387
395
|
pending.promise = runVendorDemand(plan, nested, pending).then(
|
|
388
396
|
file => {
|
|
389
|
-
pending.result = file
|
|
390
|
-
return file
|
|
397
|
+
pending.result = file
|
|
398
|
+
return file
|
|
391
399
|
},
|
|
392
400
|
error => {
|
|
393
401
|
// A write racing a concurrent `.pnext` wipe rejects; evict so the next
|
|
394
402
|
// demand retries instead of re-awaiting a permanently failed promise.
|
|
395
|
-
pendingBundles.delete(plan.key)
|
|
396
|
-
throw error
|
|
403
|
+
pendingBundles.delete(plan.key)
|
|
404
|
+
throw error
|
|
397
405
|
},
|
|
398
|
-
)
|
|
399
|
-
pendingBundles.set(plan.key, pending)
|
|
400
|
-
return pending.promise
|
|
406
|
+
)
|
|
407
|
+
pendingBundles.set(plan.key, pending)
|
|
408
|
+
return pending.promise
|
|
401
409
|
}
|
|
402
410
|
|
|
403
411
|
async function runVendorDemand(plan: VendorBuildPlan, nested: boolean, ticket: VendorPromotable) {
|
|
404
|
-
const ready = await plan.prepare()
|
|
405
|
-
if (ready !== undefined) return ready
|
|
406
|
-
if (plan.group && (await joinVendorGroup(plan.group, nested, ticket))) return plan.file
|
|
407
|
-
return withVendorSlot(nested, plan.key, () => plan.buildOne(nested), ticket)
|
|
412
|
+
const ready = await plan.prepare()
|
|
413
|
+
if (ready !== undefined) return ready
|
|
414
|
+
if (plan.group && (await joinVendorGroup(plan.group, nested, ticket))) return plan.file
|
|
415
|
+
return withVendorSlot(nested, plan.key, () => plan.buildOne(nested), ticket)
|
|
408
416
|
}
|
|
409
417
|
|
|
410
418
|
/**
|
|
@@ -418,16 +426,16 @@ async function runVendorDemand(plan: VendorBuildPlan, nested: boolean, ticket: V
|
|
|
418
426
|
*/
|
|
419
427
|
const VENDOR_GROUP_WINDOW_MS = (() => {
|
|
420
428
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
421
|
-
const override = Number(process.env.PNEXT_VENDOR_GROUP_WINDOW_MS)
|
|
422
|
-
return Number.isFinite(override) && override >= 0 ? override : 5
|
|
423
|
-
})()
|
|
429
|
+
const override = Number(process.env.PNEXT_VENDOR_GROUP_WINDOW_MS)
|
|
430
|
+
return Number.isFinite(override) && override >= 0 ? override : 5
|
|
431
|
+
})()
|
|
424
432
|
|
|
425
433
|
/**
|
|
426
434
|
* A group re-parses the package graph once per round, so an unbounded trickle
|
|
427
435
|
* of stragglers must not turn into an unbounded chain of rebuilds. Past the
|
|
428
436
|
* bound the package goes back to a build per subpath.
|
|
429
437
|
*/
|
|
430
|
-
const MAX_VENDOR_GROUP_ROUNDS = 4
|
|
438
|
+
const MAX_VENDOR_GROUP_ROUNDS = 4
|
|
431
439
|
|
|
432
440
|
/**
|
|
433
441
|
* Escape hatch: `PNEXT_VENDOR_GROUP=0` puts every subpath back on its own build. Grouping is the one part of
|
|
@@ -436,7 +444,7 @@ const MAX_VENDOR_GROUP_ROUNDS = 4;
|
|
|
436
444
|
*/
|
|
437
445
|
function vendorGroupingEnabled() {
|
|
438
446
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
439
|
-
return process.env.PNEXT_VENDOR_GROUP !== '0'
|
|
447
|
+
return process.env.PNEXT_VENDOR_GROUP !== '0'
|
|
440
448
|
}
|
|
441
449
|
|
|
442
450
|
/**
|
|
@@ -449,13 +457,9 @@ function vendorGroupingEnabled() {
|
|
|
449
457
|
* artifacts from earlier rounds stay valid and are referenced as externals, so a growth round costs
|
|
450
458
|
* what that sibling's own build would have cost.
|
|
451
459
|
*/
|
|
452
|
-
async function joinVendorGroup(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
ticket: VendorPromotable,
|
|
456
|
-
) {
|
|
457
|
-
if (!vendorGroupingEnabled()) return false;
|
|
458
|
-
let state = vendorGroups.get(group.key);
|
|
460
|
+
async function joinVendorGroup(group: VendorGroupPlan, nested: boolean, ticket: VendorPromotable) {
|
|
461
|
+
if (!vendorGroupingEnabled()) return false
|
|
462
|
+
let state = vendorGroups.get(group.key)
|
|
459
463
|
if (!state) {
|
|
460
464
|
state = {
|
|
461
465
|
pending: new Map(),
|
|
@@ -463,37 +467,37 @@ async function joinVendorGroup(
|
|
|
463
467
|
solo: new Map(),
|
|
464
468
|
rounds: 0,
|
|
465
469
|
closed: false,
|
|
466
|
-
}
|
|
467
|
-
vendorGroups.set(group.key, state)
|
|
470
|
+
}
|
|
471
|
+
vendorGroups.set(group.key, state)
|
|
468
472
|
}
|
|
469
|
-
const specifier = group.member.specifier
|
|
470
|
-
if (state.closed || state.solo.has(specifier)) return false
|
|
473
|
+
const specifier = group.member.specifier
|
|
474
|
+
if (state.closed || state.solo.has(specifier)) return false
|
|
471
475
|
// A demand raised from INSIDE a running round cannot wait for that round:
|
|
472
476
|
// the round is waiting on this demand. Build it alone, and leave it on file
|
|
473
477
|
// so the group externalizes the artifact instead of re-parsing it.
|
|
474
478
|
if (nested && state.round && !state.pending.has(specifier)) {
|
|
475
|
-
state.solo.set(specifier, group.member)
|
|
476
|
-
return false
|
|
479
|
+
state.solo.set(specifier, group.member)
|
|
480
|
+
return false
|
|
477
481
|
}
|
|
478
|
-
state.pending.set(specifier, group.member)
|
|
482
|
+
state.pending.set(specifier, group.member)
|
|
479
483
|
// While this demand waits on a shared round, promoting IT has to promote the
|
|
480
484
|
// round — that is the only work its result depends on.
|
|
481
|
-
const groupState = state
|
|
482
|
-
ticket.promote = () => promoteVendorWork(groupState.ticket)
|
|
485
|
+
const groupState = state
|
|
486
|
+
ticket.promote = () => promoteVendorWork(groupState.ticket)
|
|
483
487
|
try {
|
|
484
488
|
while (!state.produced.has(specifier)) {
|
|
485
|
-
if (state.closed || state.solo.has(specifier)) return false
|
|
486
|
-
if (nested) promoteVendorWork(state.ticket)
|
|
489
|
+
if (state.closed || state.solo.has(specifier)) return false
|
|
490
|
+
if (nested) promoteVendorWork(state.ticket)
|
|
487
491
|
await watchVendorWait(
|
|
488
492
|
nested ? 'group-round-nested' : 'group-round',
|
|
489
493
|
`${group.key} :: ${specifier}`,
|
|
490
494
|
runVendorGroupRound(group, state, nested),
|
|
491
|
-
)
|
|
495
|
+
)
|
|
492
496
|
}
|
|
493
497
|
} finally {
|
|
494
|
-
ticket.promote = undefined
|
|
498
|
+
ticket.promote = undefined
|
|
495
499
|
}
|
|
496
|
-
return true
|
|
500
|
+
return true
|
|
497
501
|
}
|
|
498
502
|
|
|
499
503
|
function runVendorGroupRound(
|
|
@@ -503,73 +507,69 @@ function runVendorGroupRound(
|
|
|
503
507
|
): Promise<void> {
|
|
504
508
|
if (!state.round) {
|
|
505
509
|
state.round = vendorGroupRound(group, state, nested).finally(() => {
|
|
506
|
-
state.round = undefined
|
|
507
|
-
state.ticket = undefined
|
|
508
|
-
})
|
|
510
|
+
state.round = undefined
|
|
511
|
+
state.ticket = undefined
|
|
512
|
+
})
|
|
509
513
|
}
|
|
510
|
-
return state.round
|
|
514
|
+
return state.round
|
|
511
515
|
}
|
|
512
516
|
|
|
513
|
-
async function vendorGroupRound(
|
|
514
|
-
|
|
515
|
-
state
|
|
516
|
-
|
|
517
|
-
) {
|
|
518
|
-
await new Promise(resolve => setTimeout(resolve, VENDOR_GROUP_WINDOW_MS));
|
|
519
|
-
const members = [...state.pending.values()];
|
|
520
|
-
if (members.length === 0) return;
|
|
517
|
+
async function vendorGroupRound(group: VendorGroupPlan, state: VendorGroupState, nested: boolean) {
|
|
518
|
+
await new Promise(resolve => setTimeout(resolve, VENDOR_GROUP_WINDOW_MS))
|
|
519
|
+
const members = [...state.pending.values()]
|
|
520
|
+
if (members.length === 0) return
|
|
521
521
|
// A lone subpath with no sibling to share a graph with is the single-bundle
|
|
522
522
|
// path plus chunk plumbing. Hand it back — and keep it on file, so a sibling
|
|
523
523
|
// demanded later externalizes its artifact instead of re-parsing it.
|
|
524
524
|
if (members.length === 1 && state.produced.size === 0) {
|
|
525
|
-
const only = members[0]
|
|
526
|
-
state.pending.delete(only.specifier)
|
|
527
|
-
state.solo.set(only.specifier, only)
|
|
528
|
-
return
|
|
525
|
+
const only = members[0]!
|
|
526
|
+
state.pending.delete(only.specifier)
|
|
527
|
+
state.solo.set(only.specifier, only)
|
|
528
|
+
return
|
|
529
529
|
}
|
|
530
|
-
state.rounds += 1
|
|
531
|
-
const external = [...state.produced.values(), ...state.solo.values()]
|
|
532
|
-
const ticket: VendorPromotable = {}
|
|
533
|
-
state.ticket = ticket
|
|
530
|
+
state.rounds += 1
|
|
531
|
+
const external = [...state.produced.values(), ...state.solo.values()]
|
|
532
|
+
const ticket: VendorPromotable = {}
|
|
533
|
+
state.ticket = ticket
|
|
534
534
|
try {
|
|
535
|
-
await withVendorSlot(nested, group.key, () => group.build(members, external, nested), ticket)
|
|
535
|
+
await withVendorSlot(nested, group.key, () => group.build(members, external, nested), ticket)
|
|
536
536
|
for (const member of members) {
|
|
537
|
-
state.pending.delete(member.specifier)
|
|
538
|
-
state.produced.set(member.specifier, member)
|
|
537
|
+
state.pending.delete(member.specifier)
|
|
538
|
+
state.produced.set(member.specifier, member)
|
|
539
539
|
}
|
|
540
540
|
} catch {
|
|
541
541
|
// Grouping is an optimization: one bad sibling must not fail the demand,
|
|
542
542
|
// so the group gives up and every member falls back to its own build.
|
|
543
|
-
state.closed = true
|
|
543
|
+
state.closed = true
|
|
544
544
|
}
|
|
545
|
-
if (state.rounds >= MAX_VENDOR_GROUP_ROUNDS) state.closed = true
|
|
545
|
+
if (state.rounds >= MAX_VENDOR_GROUP_ROUNDS) state.closed = true
|
|
546
546
|
}
|
|
547
547
|
|
|
548
548
|
// --------------------------------------------------------------------------
|
|
549
549
|
// pre-planned builds: enqueued without an awaiter, drained at route-module eval
|
|
550
550
|
// --------------------------------------------------------------------------
|
|
551
551
|
|
|
552
|
-
const preplanBuilds = new Set<Promise<unknown>>()
|
|
553
|
-
let preplanBuildError: unknown
|
|
552
|
+
const preplanBuilds = new Set<Promise<unknown>>()
|
|
553
|
+
let preplanBuildError: unknown
|
|
554
554
|
|
|
555
555
|
export function trackPreplanBuild(promise: Promise<unknown>) {
|
|
556
556
|
const tracked = promise.catch(error => {
|
|
557
|
-
preplanBuildError ??= error
|
|
558
|
-
})
|
|
559
|
-
preplanBuilds.add(tracked)
|
|
560
|
-
void tracked.finally(() => preplanBuilds.delete(tracked))
|
|
557
|
+
preplanBuildError ??= error
|
|
558
|
+
})
|
|
559
|
+
preplanBuilds.add(tracked)
|
|
560
|
+
void tracked.finally(() => preplanBuilds.delete(tracked))
|
|
561
561
|
}
|
|
562
562
|
|
|
563
563
|
/** The one join point: nothing evaluates until every enqueued build settles. */
|
|
564
564
|
export async function drainPreplanBuilds() {
|
|
565
565
|
while (preplanBuilds.size > 0) {
|
|
566
|
-
await watchVendorWait('preplan-drain', 'preplan', Promise.allSettled([...preplanBuilds]))
|
|
566
|
+
await watchVendorWait('preplan-drain', 'preplan', Promise.allSettled([...preplanBuilds]))
|
|
567
567
|
}
|
|
568
568
|
if (preplanBuildError !== undefined) {
|
|
569
|
-
const error = preplanBuildError
|
|
570
|
-
preplanBuildError = undefined
|
|
569
|
+
const error = preplanBuildError
|
|
570
|
+
preplanBuildError = undefined
|
|
571
571
|
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
572
|
-
throw error
|
|
572
|
+
throw error
|
|
573
573
|
}
|
|
574
574
|
}
|
|
575
575
|
|
|
@@ -579,29 +579,29 @@ export async function drainPreplanBuilds() {
|
|
|
579
579
|
* evicts, so concurrent demands join the one rebuild instead of racing.
|
|
580
580
|
*/
|
|
581
581
|
export function dropVendorBundle(key: string, stale: string) {
|
|
582
|
-
const pending = pendingBundles.get(key)
|
|
583
|
-
if (pending?.result === stale) pendingBundles.delete(key)
|
|
582
|
+
const pending = pendingBundles.get(key)
|
|
583
|
+
if (pending?.result === stale) pendingBundles.delete(key)
|
|
584
584
|
}
|
|
585
585
|
|
|
586
586
|
/** Forget a group whose artifacts were wiped, so the retry rebuilds them. */
|
|
587
587
|
export function dropVendorGroup(key: string) {
|
|
588
|
-
vendorGroups.delete(key)
|
|
588
|
+
vendorGroups.delete(key)
|
|
589
589
|
}
|
|
590
590
|
|
|
591
591
|
export function clearVendorPipeline() {
|
|
592
|
-
pendingBundles.clear()
|
|
593
|
-
vendorGroups.clear()
|
|
594
|
-
preplanBuilds.clear()
|
|
595
|
-
preplanBuildError = undefined
|
|
596
|
-
esmDistFiles.clear()
|
|
597
|
-
vendorContentIds.clear()
|
|
598
|
-
vendorSpans.length = 0
|
|
592
|
+
pendingBundles.clear()
|
|
593
|
+
vendorGroups.clear()
|
|
594
|
+
preplanBuilds.clear()
|
|
595
|
+
preplanBuildError = undefined
|
|
596
|
+
esmDistFiles.clear()
|
|
597
|
+
vendorContentIds.clear()
|
|
598
|
+
vendorSpans.length = 0
|
|
599
599
|
// In-flight waits belong to the pipeline that was just dropped; leaving them
|
|
600
600
|
// registered would have the watchdog report a stall that no longer exists.
|
|
601
|
-
vendorWaits.clear()
|
|
601
|
+
vendorWaits.clear()
|
|
602
602
|
if (stallTimer) {
|
|
603
|
-
clearInterval(stallTimer)
|
|
604
|
-
stallTimer = undefined
|
|
603
|
+
clearInterval(stallTimer)
|
|
604
|
+
stallTimer = undefined
|
|
605
605
|
}
|
|
606
606
|
}
|
|
607
607
|
|
|
@@ -610,14 +610,14 @@ export function clearVendorPipeline() {
|
|
|
610
610
|
// --------------------------------------------------------------------------
|
|
611
611
|
|
|
612
612
|
/** Artifact (or facade) file -> content id of the bytes it resolves to. */
|
|
613
|
-
const vendorContentIds = new Map<string, string>()
|
|
613
|
+
const vendorContentIds = new Map<string, string>()
|
|
614
614
|
|
|
615
615
|
export function vendorContentId(file: string) {
|
|
616
|
-
return vendorContentIds.get(file)
|
|
616
|
+
return vendorContentIds.get(file)
|
|
617
617
|
}
|
|
618
618
|
|
|
619
619
|
export function noteVendorContentId(file: string, id: string) {
|
|
620
|
-
vendorContentIds.set(file, id)
|
|
620
|
+
vendorContentIds.set(file, id)
|
|
621
621
|
}
|
|
622
622
|
|
|
623
623
|
/**
|
|
@@ -626,10 +626,10 @@ export function noteVendorContentId(file: string, id: string) {
|
|
|
626
626
|
*/
|
|
627
627
|
export function canonicalVendorCode(code: string) {
|
|
628
628
|
return rewriteEmittedRefs(code, ref => {
|
|
629
|
-
if (!ref.specifier.startsWith('file:')) return undefined
|
|
630
|
-
const id = vendorContentIds.get(fileURLToPath(ref.specifier))
|
|
631
|
-
return id ? `pnext-content:${id}` : undefined
|
|
632
|
-
})
|
|
629
|
+
if (!ref.specifier.startsWith('file:')) return undefined
|
|
630
|
+
const id = vendorContentIds.get(fileURLToPath(ref.specifier))
|
|
631
|
+
return id ? `pnext-content:${id}` : undefined
|
|
632
|
+
})
|
|
633
633
|
}
|
|
634
634
|
|
|
635
635
|
// --------------------------------------------------------------------------
|
|
@@ -642,61 +642,58 @@ export function canonicalVendorCode(code: string) {
|
|
|
642
642
|
* and cost a JS bridge hop per file.
|
|
643
643
|
*/
|
|
644
644
|
export interface EmittedRef {
|
|
645
|
-
specifier: string
|
|
645
|
+
specifier: string
|
|
646
646
|
/** esbuild emits an external `require()` as `__require("x")`. */
|
|
647
|
-
require: boolean
|
|
647
|
+
require: boolean
|
|
648
648
|
/** Offsets of the specifier text itself, quotes excluded. */
|
|
649
|
-
start: number
|
|
650
|
-
end: number
|
|
649
|
+
start: number
|
|
650
|
+
end: number
|
|
651
651
|
}
|
|
652
652
|
|
|
653
653
|
// Import/export statements, dynamic imports, and (esbuild's) require calls.
|
|
654
654
|
const EMITTED_IMPORT_REF =
|
|
655
|
-
/(?:^|[\s;}])(?:import|export)\s*(?:[\w$*{}\s,]*?\bfrom\s*)?(["'])([^"'\n]*)\1/g
|
|
656
|
-
const EMITTED_CALL_REF = /(?:^|[^\w$.])(?:(__)?require|import)\s*\(\s*(["'])([^"'\n]*)\2\s*\)/g
|
|
655
|
+
/(?:^|[\s;}])(?:import|export)\s*(?:[\w$*{}\s,]*?\bfrom\s*)?(["'])([^"'\n]*)\1/g
|
|
656
|
+
const EMITTED_CALL_REF = /(?:^|[^\w$.])(?:(__)?require|import)\s*\(\s*(["'])([^"'\n]*)\2\s*\)/g
|
|
657
657
|
|
|
658
658
|
/** Every bare-specifier reference the emitted code still carries. */
|
|
659
659
|
export function emittedRefs(code: string): EmittedRef[] {
|
|
660
|
-
const refs: EmittedRef[] = []
|
|
661
|
-
const seen = new Set<number>()
|
|
660
|
+
const refs: EmittedRef[] = []
|
|
661
|
+
const seen = new Set<number>()
|
|
662
662
|
for (const match of code.matchAll(EMITTED_IMPORT_REF)) {
|
|
663
|
-
const start = match.index + match[0].length - match[2]!.length - 1
|
|
664
|
-
if (seen.has(start)) continue
|
|
665
|
-
seen.add(start)
|
|
666
|
-
refs.push({ specifier: match[2]!, require: false, start, end: start + match[2]!.length })
|
|
663
|
+
const start = match.index + match[0].length - match[2]!.length - 1
|
|
664
|
+
if (seen.has(start)) continue
|
|
665
|
+
seen.add(start)
|
|
666
|
+
refs.push({ specifier: match[2]!, require: false, start, end: start + match[2]!.length })
|
|
667
667
|
}
|
|
668
668
|
for (const match of code.matchAll(EMITTED_CALL_REF)) {
|
|
669
|
-
const value = match[3]
|
|
670
|
-
const start = match.index + match[0].lastIndexOf(value)
|
|
671
|
-
if (seen.has(start)) continue
|
|
672
|
-
seen.add(start)
|
|
669
|
+
const value = match[3]!
|
|
670
|
+
const start = match.index + match[0].lastIndexOf(value)
|
|
671
|
+
if (seen.has(start)) continue
|
|
672
|
+
seen.add(start)
|
|
673
673
|
refs.push({
|
|
674
674
|
specifier: value,
|
|
675
675
|
require: match[0].includes('require'),
|
|
676
676
|
start,
|
|
677
677
|
end: start + value.length,
|
|
678
|
-
})
|
|
678
|
+
})
|
|
679
679
|
}
|
|
680
|
-
return refs.sort((a, b) => a.start - b.start)
|
|
680
|
+
return refs.sort((a, b) => a.start - b.start)
|
|
681
681
|
}
|
|
682
682
|
|
|
683
683
|
/**
|
|
684
684
|
* Repoint emitted references. `replace` returns the new specifier, or undefined
|
|
685
685
|
* to leave one alone — a bare package name the runtime resolves itself.
|
|
686
686
|
*/
|
|
687
|
-
export function rewriteEmittedRefs(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
) {
|
|
691
|
-
let out = '';
|
|
692
|
-
let cursor = 0;
|
|
687
|
+
export function rewriteEmittedRefs(code: string, replace: (ref: EmittedRef) => string | undefined) {
|
|
688
|
+
let out = ''
|
|
689
|
+
let cursor = 0
|
|
693
690
|
for (const ref of emittedRefs(code)) {
|
|
694
|
-
const next = replace(ref)
|
|
695
|
-
if (next === undefined || next === ref.specifier) continue
|
|
696
|
-
out += code.slice(cursor, ref.start) + next.replace(/\\/g, '\\\\')
|
|
697
|
-
cursor = ref.end
|
|
691
|
+
const next = replace(ref)
|
|
692
|
+
if (next === undefined || next === ref.specifier) continue
|
|
693
|
+
out += code.slice(cursor, ref.start) + next.replace(/\\/g, '\\\\')
|
|
694
|
+
cursor = ref.end
|
|
698
695
|
}
|
|
699
|
-
return cursor === 0 ? code : out + code.slice(cursor)
|
|
696
|
+
return cursor === 0 ? code : out + code.slice(cursor)
|
|
700
697
|
}
|
|
701
698
|
|
|
702
699
|
/**
|
|
@@ -705,21 +702,25 @@ export function rewriteEmittedRefs(
|
|
|
705
702
|
* actually bind what its importer asks for.
|
|
706
703
|
*/
|
|
707
704
|
export function emittedExportNames(code: string): Set<string> | undefined {
|
|
708
|
-
const names = new Set<string>()
|
|
709
|
-
if (/(^|\n)\s*export\s*\*\s*from/.test(code)) return undefined
|
|
705
|
+
const names = new Set<string>()
|
|
706
|
+
if (/(^|\n)\s*export\s*\*\s*from/.test(code)) return undefined
|
|
710
707
|
for (const match of code.matchAll(/(^|[\s;}])export\s*\{([^}]*)\}/g)) {
|
|
711
708
|
for (const entry of match[2]!.split(',')) {
|
|
712
|
-
const name = entry
|
|
713
|
-
|
|
709
|
+
const name = entry
|
|
710
|
+
.trim()
|
|
711
|
+
.split(/\s+as\s+/)
|
|
712
|
+
.pop()
|
|
713
|
+
?.trim()
|
|
714
|
+
if (name) names.add(name)
|
|
714
715
|
}
|
|
715
716
|
}
|
|
716
717
|
for (const match of code.matchAll(
|
|
717
718
|
/(^|[\s;}])export\s+(?:declare\s+)?(?:async\s+)?(?:const|let|var|function\*?|class)\s+([A-Za-z_$][\w$]*)/g,
|
|
718
719
|
)) {
|
|
719
|
-
names.add(match[2]!)
|
|
720
|
+
names.add(match[2]!)
|
|
720
721
|
}
|
|
721
|
-
if (/(^|[\s;}])export\s+default[\s({[]/.test(code)) names.add('default')
|
|
722
|
-
return names
|
|
722
|
+
if (/(^|[\s;}])export\s+default[\s({[]/.test(code)) names.add('default')
|
|
723
|
+
return names
|
|
723
724
|
}
|
|
724
725
|
|
|
725
726
|
/**
|
|
@@ -727,34 +728,34 @@ export function emittedExportNames(code: string): Set<string> | undefined {
|
|
|
727
728
|
* Applied to the bundle itself (not a separate module) for the inlined case.
|
|
728
729
|
*/
|
|
729
730
|
export function addCommonJsNamedExports(code: string) {
|
|
730
|
-
if (hasNonDefaultExport(code)) return code
|
|
731
|
+
if (hasNonDefaultExport(code)) return code
|
|
731
732
|
const names = [...new Set([...commonJsExportNames(code), ...esbuildEntryExportNames(code)])]
|
|
732
733
|
.filter(name => name !== 'default' && name !== '__esModule')
|
|
733
|
-
.sort()
|
|
734
|
-
if (names.length === 0) return code
|
|
735
|
-
const defaultExport = /(^|\n)export default ([^;\n]+);/.exec(code)
|
|
736
|
-
if (!defaultExport?.[2]) return code
|
|
737
|
-
const defaultName = uniqueIdentifier(code, '__pnext_cjs_default')
|
|
738
|
-
const usedNames = [defaultName]
|
|
734
|
+
.sort()
|
|
735
|
+
if (names.length === 0) return code
|
|
736
|
+
const defaultExport = /(^|\n)export default ([^;\n]+);/.exec(code)
|
|
737
|
+
if (!defaultExport?.[2]) return code
|
|
738
|
+
const defaultName = uniqueIdentifier(code, '__pnext_cjs_default')
|
|
739
|
+
const usedNames = [defaultName]
|
|
739
740
|
const bindings = names.map(name => {
|
|
740
|
-
const localName = uniqueIdentifier(code, `__pnext_cjs_export_${name}`, ...usedNames)
|
|
741
|
-
usedNames.push(localName)
|
|
742
|
-
return { name, localName }
|
|
743
|
-
})
|
|
741
|
+
const localName = uniqueIdentifier(code, `__pnext_cjs_export_${name}`, ...usedNames)
|
|
742
|
+
usedNames.push(localName)
|
|
743
|
+
return { name, localName }
|
|
744
|
+
})
|
|
744
745
|
const namedBindings = bindings
|
|
745
746
|
.map(({ name, localName }) => `const ${localName} = ${defaultName}[${JSON.stringify(name)}];`)
|
|
746
|
-
.join('\n')
|
|
747
|
-
const namedExports = bindings.map(({ name, localName }) => `${localName} as ${name}`).join(', ')
|
|
747
|
+
.join('\n')
|
|
748
|
+
const namedExports = bindings.map(({ name, localName }) => `${localName} as ${name}`).join(', ')
|
|
748
749
|
return code.replace(
|
|
749
750
|
defaultExport[0],
|
|
750
751
|
`${defaultExport[1]}const ${defaultName} = ${defaultExport[2]};\nexport default ${defaultName};\n${namedBindings}\nexport { ${namedExports} };\n`,
|
|
751
|
-
)
|
|
752
|
+
)
|
|
752
753
|
}
|
|
753
754
|
|
|
754
755
|
function hasNonDefaultExport(code: string) {
|
|
755
756
|
return /(^|\n)export\s+(?:\*|\{[^}]*\b(?!default\b)[A-Za-z_$][\w$]*\b|(?:const|let|var|function|class)\s+)/.test(
|
|
756
757
|
code,
|
|
757
|
-
)
|
|
758
|
+
)
|
|
758
759
|
}
|
|
759
760
|
|
|
760
761
|
export function commonJsExportNames(code: string) {
|
|
@@ -767,7 +768,7 @@ export function commonJsExportNames(code: string) {
|
|
|
767
768
|
]),
|
|
768
769
|
]
|
|
769
770
|
.filter((name): name is string => Boolean(name && name !== '__esModule' && isIdentifier(name)))
|
|
770
|
-
.sort()
|
|
771
|
+
.sort()
|
|
771
772
|
}
|
|
772
773
|
|
|
773
774
|
// Recover named exports from esbuild's `__export(entry, {...}); module.exports =
|
|
@@ -775,16 +776,16 @@ export function commonJsExportNames(code: string) {
|
|
|
775
776
|
function esbuildEntryExportNames(code: string) {
|
|
776
777
|
const toCommonJs = [
|
|
777
778
|
...code.matchAll(/module\.exports\s*=\s*__toCommonJS\w*\(\s*([A-Za-z_$][\w$]*)\s*\)/g),
|
|
778
|
-
]
|
|
779
|
-
const entryVar = toCommonJs.at(-1)?.[1]
|
|
780
|
-
if (!entryVar) return []
|
|
779
|
+
]
|
|
780
|
+
const entryVar = toCommonJs.at(-1)?.[1]
|
|
781
|
+
if (!entryVar) return []
|
|
781
782
|
const exportCall = new RegExp(
|
|
782
783
|
`__export\\w*\\(\\s*${escapeRegex(entryVar)}\\s*,\\s*\\{([\\s\\S]*?)\\}\\s*\\)`,
|
|
783
|
-
).exec(code)
|
|
784
|
-
if (!exportCall?.[1]) return []
|
|
784
|
+
).exec(code)
|
|
785
|
+
if (!exportCall?.[1]) return []
|
|
785
786
|
return [...exportCall[1].matchAll(/(?:^|,)\s*([A-Za-z_$][\w$]*)\s*:/g)]
|
|
786
787
|
.map(match => match[1])
|
|
787
|
-
.filter((name): name is string => Boolean(name && isIdentifier(name)))
|
|
788
|
+
.filter((name): name is string => Boolean(name && isIdentifier(name)))
|
|
788
789
|
}
|
|
789
790
|
|
|
790
791
|
/**
|
|
@@ -802,16 +803,16 @@ export async function verifyVendorArtifact(
|
|
|
802
803
|
chunks: ReadonlyMap<string, string> = new Map(),
|
|
803
804
|
) {
|
|
804
805
|
for (const [specifier, target] of repointed) {
|
|
805
|
-
if (!existsSync(target)) return `repointed ${specifier} -> missing ${target}
|
|
806
|
+
if (!existsSync(target)) return `repointed ${specifier} -> missing ${target}`
|
|
806
807
|
}
|
|
807
808
|
// Nothing was rewritten and nothing was split out: this is esbuild's own
|
|
808
809
|
// output, unedited, and there is no claim left for a re-bundle to check.
|
|
809
|
-
if (repointed.size === 0 && chunks.size === 0) return undefined
|
|
810
|
+
if (repointed.size === 0 && chunks.size === 0) return undefined
|
|
810
811
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
811
|
-
const mode = process.env.PNEXT_VENDOR_VERIFY
|
|
812
|
-
if (mode === 'off') return undefined
|
|
813
|
-
const deep = mode === 'deep'
|
|
814
|
-
const outDir = path.dirname(file)
|
|
812
|
+
const mode = process.env.PNEXT_VENDOR_VERIFY
|
|
813
|
+
if (mode === 'off') return undefined
|
|
814
|
+
const deep = mode === 'deep'
|
|
815
|
+
const outDir = path.dirname(file)
|
|
815
816
|
try {
|
|
816
817
|
await build({
|
|
817
818
|
stdin: { contents: code, resolveDir: outDir, sourcefile: file, loader: 'js' },
|
|
@@ -830,32 +831,34 @@ export async function verifyVendorArtifact(
|
|
|
830
831
|
// before anything is published, so nothing half-written is ever
|
|
831
832
|
// readable by a concurrent demand.
|
|
832
833
|
verify.onResolve({ filter: /^\.\.?\// }, args => {
|
|
833
|
-
const resolved = path.resolve(args.resolveDir, args.path)
|
|
834
|
+
const resolved = path.resolve(args.resolveDir, args.path)
|
|
834
835
|
return chunks.has(resolved)
|
|
835
836
|
? { path: resolved, namespace: 'pnext-vendor-chunk' }
|
|
836
|
-
: undefined
|
|
837
|
-
})
|
|
837
|
+
: undefined
|
|
838
|
+
})
|
|
838
839
|
verify.onLoad({ filter: /.*/, namespace: 'pnext-vendor-chunk' }, args => ({
|
|
839
840
|
contents: chunks.get(args.path),
|
|
840
841
|
loader: 'js',
|
|
841
842
|
resolveDir: path.dirname(args.path),
|
|
842
|
-
}))
|
|
843
|
+
}))
|
|
843
844
|
verify.onResolve({ filter: /^file:/ }, args => {
|
|
844
|
-
const resolved = fileURLToPath(args.path)
|
|
845
|
+
const resolved = fileURLToPath(args.path)
|
|
845
846
|
// Deep: one level in (esbuild then checks the export names), and
|
|
846
847
|
// only from the artifact itself — its dependencies' own externals
|
|
847
848
|
// stay external, so the closure cannot run away.
|
|
848
|
-
const inside = args.importer === file || args.importer.startsWith(outDir)
|
|
849
|
-
return { path: resolved, external: !deep || !inside }
|
|
850
|
-
})
|
|
849
|
+
const inside = args.importer === file || args.importer.startsWith(outDir)
|
|
850
|
+
return { path: resolved, external: !deep || !inside }
|
|
851
|
+
})
|
|
851
852
|
},
|
|
852
853
|
},
|
|
853
854
|
],
|
|
854
|
-
})
|
|
855
|
+
})
|
|
855
856
|
} catch (error) {
|
|
856
|
-
return (error instanceof Error ? error.message : String(error))
|
|
857
|
+
return (error instanceof Error ? error.message : String(error))
|
|
858
|
+
.replace(/\s+/g, ' ')
|
|
859
|
+
.slice(0, 300)
|
|
857
860
|
}
|
|
858
|
-
return undefined
|
|
861
|
+
return undefined
|
|
859
862
|
}
|
|
860
863
|
|
|
861
864
|
// --------------------------------------------------------------------------
|
|
@@ -872,46 +875,46 @@ export async function verifyVendorArtifact(
|
|
|
872
875
|
* in the closure means a plugin could have fired, so the package goes back to the bundler. A false negative
|
|
873
876
|
* costs a compile; a false positive ships wrong code.
|
|
874
877
|
*/
|
|
875
|
-
const MAX_ESM_DIST_FILES = 4096
|
|
876
|
-
const ESM_DIST_ENTRY_MARKER = 'entry'
|
|
878
|
+
const MAX_ESM_DIST_FILES = 4096
|
|
879
|
+
const ESM_DIST_ENTRY_MARKER = 'entry'
|
|
877
880
|
|
|
878
|
-
const esmDists = new Map<string, Promise<string | undefined>>()
|
|
881
|
+
const esmDists = new Map<string, Promise<string | undefined>>()
|
|
879
882
|
|
|
880
883
|
export function copyBrowserReadyEsmDist(entry: string, distDir: string) {
|
|
881
884
|
// One copy per destination: a second copier would rewrite files the first has
|
|
882
885
|
// already published, and copyFile is not atomic against a concurrent import.
|
|
883
|
-
let dist = esmDists.get(distDir)
|
|
886
|
+
let dist = esmDists.get(distDir)
|
|
884
887
|
if (!dist) {
|
|
885
|
-
dist = copyBrowserReadyEsmDistUncached(entry, distDir)
|
|
886
|
-
esmDists.set(distDir, dist)
|
|
888
|
+
dist = copyBrowserReadyEsmDistUncached(entry, distDir)
|
|
889
|
+
esmDists.set(distDir, dist)
|
|
887
890
|
}
|
|
888
|
-
return dist
|
|
891
|
+
return dist
|
|
889
892
|
}
|
|
890
893
|
|
|
891
894
|
async function copyBrowserReadyEsmDistUncached(entry: string, distDir: string) {
|
|
892
895
|
const existing = await readFile(path.join(distDir, ESM_DIST_ENTRY_MARKER), 'utf8').catch(
|
|
893
896
|
() => undefined,
|
|
894
|
-
)
|
|
895
|
-
if (existing !== undefined) return path.join(distDir, existing)
|
|
897
|
+
)
|
|
898
|
+
if (existing !== undefined) return path.join(distDir, existing)
|
|
896
899
|
|
|
897
|
-
const closure = await collectBrowserReadyEsmClosure(entry)
|
|
898
|
-
if (!closure) return undefined
|
|
900
|
+
const closure = await collectBrowserReadyEsmClosure(entry)
|
|
901
|
+
if (!closure) return undefined
|
|
899
902
|
// Copies keep their offsets relative to the shallowest directory the closure
|
|
900
903
|
// touches, which is what makes every `../` in it still land on its target.
|
|
901
|
-
const base = commonAncestorDir([...closure])
|
|
902
|
-
const relative = (file: string) => path.relative(base, file)
|
|
904
|
+
const base = commonAncestorDir([...closure])
|
|
905
|
+
const relative = (file: string) => path.relative(base, file)
|
|
903
906
|
await Promise.all(
|
|
904
907
|
[...closure].map(async file => {
|
|
905
|
-
const target = path.join(distDir, relative(file))
|
|
906
|
-
await mkdir(path.dirname(target), { recursive: true })
|
|
907
|
-
await copyFile(file, target)
|
|
908
|
+
const target = path.join(distDir, relative(file))
|
|
909
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
910
|
+
await copyFile(file, target)
|
|
908
911
|
}),
|
|
909
|
-
)
|
|
910
|
-
await writeFileAtomic(path.join(distDir, ESM_DIST_ENTRY_MARKER), relative(entry))
|
|
911
|
-
return path.join(distDir, relative(entry))
|
|
912
|
+
)
|
|
913
|
+
await writeFileAtomic(path.join(distDir, ESM_DIST_ENTRY_MARKER), relative(entry))
|
|
914
|
+
return path.join(distDir, relative(entry))
|
|
912
915
|
}
|
|
913
916
|
|
|
914
|
-
const ESM_DIST_EXTENSIONS = new Set(['.js', '.mjs'])
|
|
917
|
+
const ESM_DIST_EXTENSIONS = new Set(['.js', '.mjs'])
|
|
915
918
|
|
|
916
919
|
/**
|
|
917
920
|
* Per-FILE verdict, memoized: the predicate runs once per demanded artifact and
|
|
@@ -919,85 +922,85 @@ const ESM_DIST_EXTENSIONS = new Set(['.js', '.mjs']);
|
|
|
919
922
|
* thousand files were re-read, re-regexed and re-`existsSync`ed per subpath.
|
|
920
923
|
* `undefined` means "not browser-ready", which fails the whole closure.
|
|
921
924
|
*/
|
|
922
|
-
const esmDistFiles = new Map<string, Promise<string[] | undefined>>()
|
|
925
|
+
const esmDistFiles = new Map<string, Promise<string[] | undefined>>()
|
|
923
926
|
|
|
924
927
|
function browserReadyEsmImports(file: string) {
|
|
925
|
-
let entry = esmDistFiles.get(file)
|
|
928
|
+
let entry = esmDistFiles.get(file)
|
|
926
929
|
if (!entry) {
|
|
927
|
-
entry = browserReadyEsmImportsUncached(file)
|
|
928
|
-
esmDistFiles.set(file, entry)
|
|
930
|
+
entry = browserReadyEsmImportsUncached(file)
|
|
931
|
+
esmDistFiles.set(file, entry)
|
|
929
932
|
}
|
|
930
|
-
return entry
|
|
933
|
+
return entry
|
|
931
934
|
}
|
|
932
935
|
|
|
933
936
|
async function browserReadyEsmImportsUncached(file: string) {
|
|
934
|
-
if (!ESM_DIST_EXTENSIONS.has(path.extname(file))) return undefined
|
|
935
|
-
const source = await readFile(file, 'utf8').catch(() => undefined)
|
|
936
|
-
if (source === undefined) return undefined
|
|
937
|
+
if (!ESM_DIST_EXTENSIONS.has(path.extname(file))) return undefined
|
|
938
|
+
const source = await readFile(file, 'utf8').catch(() => undefined)
|
|
939
|
+
if (source === undefined) return undefined
|
|
937
940
|
// `use cache` would need the post-bundle source transforms this path skips.
|
|
938
941
|
if (
|
|
939
942
|
isCommonJsModuleSource(source, file) ||
|
|
940
943
|
/\brequire\s*\(|\bmodule\.exports\b/.test(source) ||
|
|
941
944
|
source.includes('use cache')
|
|
942
945
|
) {
|
|
943
|
-
return undefined
|
|
946
|
+
return undefined
|
|
944
947
|
}
|
|
945
|
-
const imports: string[] = []
|
|
948
|
+
const imports: string[] = []
|
|
946
949
|
for (const found of outputSpecifiers(source)) {
|
|
947
|
-
const specifier = found.value
|
|
948
|
-
if (!specifier.startsWith('.')) return undefined
|
|
949
|
-
const resolved = resolveEsmDistImport(path.dirname(file), specifier)
|
|
950
|
-
if (!resolved) return undefined
|
|
951
|
-
imports.push(resolved)
|
|
950
|
+
const specifier = found.value
|
|
951
|
+
if (!specifier.startsWith('.')) return undefined
|
|
952
|
+
const resolved = resolveEsmDistImport(path.dirname(file), specifier)
|
|
953
|
+
if (!resolved) return undefined
|
|
954
|
+
imports.push(resolved)
|
|
952
955
|
}
|
|
953
|
-
return imports
|
|
956
|
+
return imports
|
|
954
957
|
}
|
|
955
958
|
|
|
956
959
|
async function collectBrowserReadyEsmClosure(entry: string) {
|
|
957
|
-
const closure = new Set<string>()
|
|
958
|
-
const queue = [entry]
|
|
960
|
+
const closure = new Set<string>()
|
|
961
|
+
const queue = [entry]
|
|
959
962
|
while (queue.length > 0) {
|
|
960
|
-
const file = queue.pop()
|
|
961
|
-
if (closure.has(file)) continue
|
|
962
|
-
if (closure.size >= MAX_ESM_DIST_FILES) return undefined
|
|
963
|
-
closure.add(file)
|
|
964
|
-
const imports = await browserReadyEsmImports(file)
|
|
965
|
-
if (!imports) return undefined
|
|
966
|
-
queue.push(...imports)
|
|
963
|
+
const file = queue.pop()!
|
|
964
|
+
if (closure.has(file)) continue
|
|
965
|
+
if (closure.size >= MAX_ESM_DIST_FILES) return undefined
|
|
966
|
+
closure.add(file)
|
|
967
|
+
const imports = await browserReadyEsmImports(file)
|
|
968
|
+
if (!imports) return undefined
|
|
969
|
+
queue.push(...imports)
|
|
967
970
|
}
|
|
968
971
|
// A single-file "closure" is a re-export shim or a stub; bundling it is
|
|
969
972
|
// already free, and the marker directory would cost more than it saves.
|
|
970
|
-
return closure.size > 1 ? closure : undefined
|
|
973
|
+
return closure.size > 1 ? closure : undefined
|
|
971
974
|
}
|
|
972
975
|
|
|
973
976
|
/** Relative resolution limited to what a browser would do: exact file first. */
|
|
974
977
|
function resolveEsmDistImport(fromDir: string, specifier: string) {
|
|
975
|
-
const base = path.resolve(fromDir, specifier)
|
|
978
|
+
const base = path.resolve(fromDir, specifier)
|
|
976
979
|
for (const candidate of [base, `${base}.js`, `${base}.mjs`, path.join(base, 'index.js')]) {
|
|
977
980
|
if (ESM_DIST_EXTENSIONS.has(path.extname(candidate)) && cachedExistsSync(candidate)) {
|
|
978
|
-
return candidate
|
|
981
|
+
return candidate
|
|
979
982
|
}
|
|
980
983
|
}
|
|
981
|
-
return undefined
|
|
984
|
+
return undefined
|
|
982
985
|
}
|
|
983
986
|
|
|
984
987
|
function commonAncestorDir(files: string[]) {
|
|
985
|
-
const segmentLists = files.map(file => path.dirname(file).split(path.sep))
|
|
986
|
-
const shortest = Math.min(...segmentLists.map(segments => segments.length))
|
|
987
|
-
const common: string[] = []
|
|
988
|
+
const segmentLists = files.map(file => path.dirname(file).split(path.sep))
|
|
989
|
+
const shortest = Math.min(...segmentLists.map(segments => segments.length))
|
|
990
|
+
const common: string[] = []
|
|
988
991
|
for (let index = 0; index < shortest; index += 1) {
|
|
989
|
-
const segment = segmentLists[0]![index]
|
|
990
|
-
if (segmentLists.some(segments => segments[index] !== segment)) break
|
|
991
|
-
common.push(segment)
|
|
992
|
+
const segment = segmentLists[0]![index]!
|
|
993
|
+
if (segmentLists.some(segments => segments[index] !== segment)) break
|
|
994
|
+
common.push(segment)
|
|
992
995
|
}
|
|
993
|
-
return common.join(path.sep) || path.sep
|
|
996
|
+
return common.join(path.sep) || path.sep
|
|
994
997
|
}
|
|
995
998
|
|
|
996
999
|
// --------------------------------------------------------------------------
|
|
997
1000
|
// facades
|
|
998
1001
|
// --------------------------------------------------------------------------
|
|
999
1002
|
|
|
1000
|
-
const REEXPORT_CALL = /\bmodule\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)
|
|
1003
|
+
const REEXPORT_CALL = /\bmodule\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)/
|
|
1001
1004
|
|
|
1002
1005
|
/**
|
|
1003
1006
|
* Cheap pre-filter: can this CommonJS entry plausibly carry named exports? A
|
|
@@ -1007,19 +1010,19 @@ const REEXPORT_CALL = /\bmodule\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)
|
|
|
1007
1010
|
* level, which is where most packages' real export list lives.
|
|
1008
1011
|
*/
|
|
1009
1012
|
export async function cjsEntryMayHaveNamedExports(entry: string) {
|
|
1010
|
-
const source = await readFile(entry, 'utf8').catch(() => undefined)
|
|
1011
|
-
if (source === undefined) return false
|
|
1012
|
-
if (commonJsExportNames(source).length > 0) return true
|
|
1013
|
-
const reexport = REEXPORT_CALL.exec(source)
|
|
1014
|
-
const target = reexport?.[2]
|
|
1015
|
-
if (!target?.startsWith('.')) return false
|
|
1013
|
+
const source = await readFile(entry, 'utf8').catch(() => undefined)
|
|
1014
|
+
if (source === undefined) return false
|
|
1015
|
+
if (commonJsExportNames(source).length > 0) return true
|
|
1016
|
+
const reexport = REEXPORT_CALL.exec(source)
|
|
1017
|
+
const target = reexport?.[2]
|
|
1018
|
+
if (!target?.startsWith('.')) return false
|
|
1016
1019
|
for (const candidate of [target, `${target}.js`, path.join(target, 'index.js')]) {
|
|
1017
|
-
const file = path.resolve(path.dirname(entry), candidate)
|
|
1018
|
-
if (!existsSync(file) || !file.endsWith('.js')) continue
|
|
1019
|
-
const inner = await readFile(file, 'utf8').catch(() => undefined)
|
|
1020
|
-
if (inner && commonJsExportNames(inner).length > 0) return true
|
|
1020
|
+
const file = path.resolve(path.dirname(entry), candidate)
|
|
1021
|
+
if (!existsSync(file) || !file.endsWith('.js')) continue
|
|
1022
|
+
const inner = await readFile(file, 'utf8').catch(() => undefined)
|
|
1023
|
+
if (inner && commonJsExportNames(inner).length > 0) return true
|
|
1021
1024
|
}
|
|
1022
|
-
return false
|
|
1025
|
+
return false
|
|
1023
1026
|
}
|
|
1024
1027
|
|
|
1025
1028
|
/**
|
|
@@ -1029,28 +1032,32 @@ export async function cjsEntryMayHaveNamedExports(entry: string) {
|
|
|
1029
1032
|
* full cache wipe recovers from. A new key sidesteps both: the old facade stays on disk for whoever still
|
|
1030
1033
|
* imports it, and every new compile names the new one.
|
|
1031
1034
|
*/
|
|
1032
|
-
const FACADE_GENERATOR = 'v2'
|
|
1035
|
+
const FACADE_GENERATOR = 'v2'
|
|
1033
1036
|
|
|
1034
1037
|
/** Marker written beside a bundle whose export set turned out to be default-only. */
|
|
1035
|
-
const NO_FACADE_SUFFIX = `.no-facade-${FACADE_GENERATOR}
|
|
1038
|
+
const NO_FACADE_SUFFIX = `.no-facade-${FACADE_GENERATOR}`
|
|
1036
1039
|
|
|
1037
1040
|
/** The facade this generator writes for `bundleFile`. */
|
|
1038
1041
|
export function cjsFacadePath(bundleFile: string) {
|
|
1039
|
-
return `${bundleFile.replace(/\.mjs$/, '')}.facade-${FACADE_GENERATOR}.mjs
|
|
1042
|
+
return `${bundleFile.replace(/\.mjs$/, '')}.facade-${FACADE_GENERATOR}.mjs`
|
|
1040
1043
|
}
|
|
1041
1044
|
|
|
1042
1045
|
/** Where this generator records "no named exports" for `bundleFile`. */
|
|
1043
1046
|
export function cjsNoFacadeMarker(bundleFile: string) {
|
|
1044
|
-
return `${bundleFile}${NO_FACADE_SUFFIX}
|
|
1047
|
+
return `${bundleFile}${NO_FACADE_SUFFIX}`
|
|
1045
1048
|
}
|
|
1046
1049
|
|
|
1047
|
-
const facades = new Map<string, Promise<string | undefined>>()
|
|
1050
|
+
const facades = new Map<string, Promise<string | undefined>>()
|
|
1048
1051
|
|
|
1049
1052
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1050
|
-
const heavyProfFile = process.env.PNEXT_HEAVY_PROF
|
|
1053
|
+
const heavyProfFile = process.env.PNEXT_HEAVY_PROF
|
|
1051
1054
|
export function heavyProfRow(row: Record<string, unknown>) {
|
|
1052
|
-
if (!heavyProfFile) return
|
|
1053
|
-
try {
|
|
1055
|
+
if (!heavyProfFile) return
|
|
1056
|
+
try {
|
|
1057
|
+
appendFileSync(heavyProfFile, `${JSON.stringify(row)}\n`)
|
|
1058
|
+
} catch {
|
|
1059
|
+
/* prof only */
|
|
1060
|
+
}
|
|
1054
1061
|
}
|
|
1055
1062
|
|
|
1056
1063
|
/**
|
|
@@ -1059,48 +1066,55 @@ export function heavyProfRow(row: Record<string, unknown>) {
|
|
|
1059
1066
|
* inlines instead.
|
|
1060
1067
|
*/
|
|
1061
1068
|
export function cjsNamedExportFacade(bundleFile: string, importHref: string) {
|
|
1062
|
-
let facade = facades.get(bundleFile)
|
|
1069
|
+
let facade = facades.get(bundleFile)
|
|
1063
1070
|
if (!facade) {
|
|
1064
|
-
facade = writeCjsNamedExportFacade(bundleFile, importHref).catch(() => undefined)
|
|
1065
|
-
facades.set(bundleFile, facade)
|
|
1071
|
+
facade = writeCjsNamedExportFacade(bundleFile, importHref).catch(() => undefined)
|
|
1072
|
+
facades.set(bundleFile, facade)
|
|
1066
1073
|
}
|
|
1067
|
-
return facade
|
|
1074
|
+
return facade
|
|
1068
1075
|
}
|
|
1069
1076
|
|
|
1070
1077
|
async function writeCjsNamedExportFacade(bundleFile: string, importHref: string) {
|
|
1071
|
-
const file = cjsFacadePath(bundleFile)
|
|
1078
|
+
const file = cjsFacadePath(bundleFile)
|
|
1072
1079
|
// The facade's identity follows its bundle's, so an importer of one canonical-
|
|
1073
1080
|
// izes the same way an importer of the other does.
|
|
1074
|
-
const bundleId = vendorContentIds.get(bundleFile)
|
|
1075
|
-
if (bundleId) vendorContentIds.set(file, `${bundleId}:facade`)
|
|
1081
|
+
const bundleId = vendorContentIds.get(bundleFile)
|
|
1082
|
+
if (bundleId) vendorContentIds.set(file, `${bundleId}:facade`)
|
|
1076
1083
|
// Both outcomes persist: the facade is content-keyed with the bundle it wraps,
|
|
1077
1084
|
// so a restart never re-executes the package to re-derive its names.
|
|
1078
|
-
if (existsSync(file)) return file
|
|
1079
|
-
if (existsSync(cjsNoFacadeMarker(bundleFile))) return undefined
|
|
1085
|
+
if (existsSync(file)) return file
|
|
1086
|
+
if (existsSync(cjsNoFacadeMarker(bundleFile))) return undefined
|
|
1080
1087
|
|
|
1081
|
-
const t0 = performance.now()
|
|
1082
|
-
const code = await readFile(bundleFile, 'utf8').catch(() => undefined)
|
|
1083
|
-
if (code === undefined) return undefined
|
|
1084
|
-
const tRead = performance.now()
|
|
1088
|
+
const t0 = performance.now()
|
|
1089
|
+
const code = await readFile(bundleFile, 'utf8').catch(() => undefined)
|
|
1090
|
+
if (code === undefined) return undefined
|
|
1091
|
+
const tRead = performance.now()
|
|
1085
1092
|
// The exec pass is REQUIRED, not a supplement: a statically-scanned set is routinely short, and an
|
|
1086
1093
|
// incomplete facade is worse than inlining - it breaks an importer's named binding at build time.
|
|
1087
1094
|
// A bundle that cannot be executed here simply gets no facade.
|
|
1088
|
-
const executed = await executedExportNames(importHref)
|
|
1089
|
-
const tExec = performance.now()
|
|
1095
|
+
const executed = await executedExportNames(importHref)
|
|
1096
|
+
const tExec = performance.now()
|
|
1090
1097
|
const names =
|
|
1091
1098
|
executed &&
|
|
1092
1099
|
[...new Set([...commonJsExportNames(code), ...executed])]
|
|
1093
1100
|
.filter(name => name !== 'default' && name !== '__esModule' && isIdentifier(name))
|
|
1094
|
-
.sort()
|
|
1101
|
+
.sort()
|
|
1095
1102
|
if (!names || names.length === 0) {
|
|
1096
|
-
heavyProfRow({
|
|
1097
|
-
|
|
1098
|
-
|
|
1103
|
+
heavyProfRow({
|
|
1104
|
+
k: 'facade',
|
|
1105
|
+
f: path.basename(bundleFile),
|
|
1106
|
+
readMs: tRead - t0,
|
|
1107
|
+
execMs: tExec - tRead,
|
|
1108
|
+
verifyMs: 0,
|
|
1109
|
+
out: 'no-names',
|
|
1110
|
+
})
|
|
1111
|
+
await writeFileAtomic(cjsNoFacadeMarker(bundleFile), '')
|
|
1112
|
+
return undefined
|
|
1099
1113
|
}
|
|
1100
1114
|
|
|
1101
|
-
const binding = '__pnext_cjs'
|
|
1102
|
-
const namespace = '__pnext_cjs_ns'
|
|
1103
|
-
const holder = '__pnext_cjs_names'
|
|
1115
|
+
const binding = '__pnext_cjs'
|
|
1116
|
+
const namespace = '__pnext_cjs_ns'
|
|
1117
|
+
const holder = '__pnext_cjs_names'
|
|
1104
1118
|
// A CommonJS entry marked `__esModule` but carrying no `default` (e.g.
|
|
1105
1119
|
// bind-event-listener) leaves the default binding undefined, so reading names
|
|
1106
1120
|
// off it threw at facade eval. Its names live on the bundle's own exports
|
|
@@ -1110,15 +1124,22 @@ async function writeCjsNamedExportFacade(bundleFile: string, importHref: string)
|
|
|
1110
1124
|
`export default ${binding};\n` +
|
|
1111
1125
|
`const ${holder} = ${binding} ?? ${namespace};\n` +
|
|
1112
1126
|
names.map(name => `export const ${name} = ${holder}[${JSON.stringify(name)}];`).join('\n') +
|
|
1113
|
-
'\n'
|
|
1114
|
-
const ok = await facadeRebundles(file, source)
|
|
1115
|
-
heavyProfRow({
|
|
1127
|
+
'\n'
|
|
1128
|
+
const ok = await facadeRebundles(file, source)
|
|
1129
|
+
heavyProfRow({
|
|
1130
|
+
k: 'facade',
|
|
1131
|
+
f: path.basename(bundleFile),
|
|
1132
|
+
readMs: tRead - t0,
|
|
1133
|
+
execMs: tExec - tRead,
|
|
1134
|
+
verifyMs: performance.now() - tExec,
|
|
1135
|
+
out: ok ? 'facade' : 'verify-fail',
|
|
1136
|
+
})
|
|
1116
1137
|
if (!ok) {
|
|
1117
|
-
await writeFileAtomic(cjsNoFacadeMarker(bundleFile), '')
|
|
1118
|
-
return undefined
|
|
1138
|
+
await writeFileAtomic(cjsNoFacadeMarker(bundleFile), '')
|
|
1139
|
+
return undefined
|
|
1119
1140
|
}
|
|
1120
|
-
await writeFileAtomic(file, source)
|
|
1121
|
-
return file
|
|
1141
|
+
await writeFileAtomic(file, source)
|
|
1142
|
+
return file
|
|
1122
1143
|
}
|
|
1123
1144
|
|
|
1124
1145
|
/**
|
|
@@ -1128,12 +1149,12 @@ async function writeCjsNamedExportFacade(bundleFile: string, importHref: string)
|
|
|
1128
1149
|
*/
|
|
1129
1150
|
async function executedExportNames(importHref: string) {
|
|
1130
1151
|
try {
|
|
1131
|
-
const module_ = (await import(importHref)) as Record<string, unknown
|
|
1132
|
-
const source = (module_.default as Record<string, unknown> | undefined) ?? module_
|
|
1133
|
-
if (!source || (typeof source !== 'object' && typeof source !== 'function')) return undefined
|
|
1134
|
-
return Object.keys(source)
|
|
1152
|
+
const module_ = (await import(importHref)) as Record<string, unknown>
|
|
1153
|
+
const source = (module_.default as Record<string, unknown> | undefined) ?? module_
|
|
1154
|
+
if (!source || (typeof source !== 'object' && typeof source !== 'function')) return undefined
|
|
1155
|
+
return Object.keys(source)
|
|
1135
1156
|
} catch {
|
|
1136
|
-
return undefined
|
|
1157
|
+
return undefined
|
|
1137
1158
|
}
|
|
1138
1159
|
}
|
|
1139
1160
|
|
|
@@ -1152,9 +1173,9 @@ async function facadeRebundles(file: string, source: string) {
|
|
|
1152
1173
|
target: 'es2022',
|
|
1153
1174
|
packages: 'external',
|
|
1154
1175
|
logLevel: 'silent',
|
|
1155
|
-
})
|
|
1156
|
-
return true
|
|
1176
|
+
})
|
|
1177
|
+
return true
|
|
1157
1178
|
} catch {
|
|
1158
|
-
return false
|
|
1179
|
+
return false
|
|
1159
1180
|
}
|
|
1160
1181
|
}
|