@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
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
|
/**
|
|
@@ -414,20 +422,21 @@ async function runVendorDemand(plan: VendorBuildPlan, nested: boolean, ticket: V
|
|
|
414
422
|
*
|
|
415
423
|
* Every demand pays it before it can even learn it has no siblings, and it is a timer on the one JS thread
|
|
416
424
|
* the concurrent bundlers already saturate with plugin IPC, so the real wait is bounded below by this rather
|
|
417
|
-
* than by it. `PNEXT_VENDOR_GROUP_WINDOW_MS` is the bisect seam
|
|
425
|
+
* than by it. `PNEXT_VENDOR_GROUP_WINDOW_MS` is the bisect seam, read per round so
|
|
426
|
+
* tests can widen it after module load.
|
|
418
427
|
*/
|
|
419
|
-
|
|
428
|
+
function vendorGroupWindowMs() {
|
|
420
429
|
// 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
|
-
}
|
|
430
|
+
const override = Number(process.env.PNEXT_VENDOR_GROUP_WINDOW_MS)
|
|
431
|
+
return Number.isFinite(override) && override >= 0 ? override : 5
|
|
432
|
+
}
|
|
424
433
|
|
|
425
434
|
/**
|
|
426
435
|
* A group re-parses the package graph once per round, so an unbounded trickle
|
|
427
436
|
* of stragglers must not turn into an unbounded chain of rebuilds. Past the
|
|
428
437
|
* bound the package goes back to a build per subpath.
|
|
429
438
|
*/
|
|
430
|
-
const MAX_VENDOR_GROUP_ROUNDS = 4
|
|
439
|
+
const MAX_VENDOR_GROUP_ROUNDS = 4
|
|
431
440
|
|
|
432
441
|
/**
|
|
433
442
|
* Escape hatch: `PNEXT_VENDOR_GROUP=0` puts every subpath back on its own build. Grouping is the one part of
|
|
@@ -436,7 +445,7 @@ const MAX_VENDOR_GROUP_ROUNDS = 4;
|
|
|
436
445
|
*/
|
|
437
446
|
function vendorGroupingEnabled() {
|
|
438
447
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
439
|
-
return process.env.PNEXT_VENDOR_GROUP !== '0'
|
|
448
|
+
return process.env.PNEXT_VENDOR_GROUP !== '0'
|
|
440
449
|
}
|
|
441
450
|
|
|
442
451
|
/**
|
|
@@ -449,13 +458,9 @@ function vendorGroupingEnabled() {
|
|
|
449
458
|
* artifacts from earlier rounds stay valid and are referenced as externals, so a growth round costs
|
|
450
459
|
* what that sibling's own build would have cost.
|
|
451
460
|
*/
|
|
452
|
-
async function joinVendorGroup(
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
ticket: VendorPromotable,
|
|
456
|
-
) {
|
|
457
|
-
if (!vendorGroupingEnabled()) return false;
|
|
458
|
-
let state = vendorGroups.get(group.key);
|
|
461
|
+
async function joinVendorGroup(group: VendorGroupPlan, nested: boolean, ticket: VendorPromotable) {
|
|
462
|
+
if (!vendorGroupingEnabled()) return false
|
|
463
|
+
let state = vendorGroups.get(group.key)
|
|
459
464
|
if (!state) {
|
|
460
465
|
state = {
|
|
461
466
|
pending: new Map(),
|
|
@@ -463,37 +468,37 @@ async function joinVendorGroup(
|
|
|
463
468
|
solo: new Map(),
|
|
464
469
|
rounds: 0,
|
|
465
470
|
closed: false,
|
|
466
|
-
}
|
|
467
|
-
vendorGroups.set(group.key, state)
|
|
471
|
+
}
|
|
472
|
+
vendorGroups.set(group.key, state)
|
|
468
473
|
}
|
|
469
|
-
const specifier = group.member.specifier
|
|
470
|
-
if (state.closed || state.solo.has(specifier)) return false
|
|
474
|
+
const specifier = group.member.specifier
|
|
475
|
+
if (state.closed || state.solo.has(specifier)) return false
|
|
471
476
|
// A demand raised from INSIDE a running round cannot wait for that round:
|
|
472
477
|
// the round is waiting on this demand. Build it alone, and leave it on file
|
|
473
478
|
// so the group externalizes the artifact instead of re-parsing it.
|
|
474
479
|
if (nested && state.round && !state.pending.has(specifier)) {
|
|
475
|
-
state.solo.set(specifier, group.member)
|
|
476
|
-
return false
|
|
480
|
+
state.solo.set(specifier, group.member)
|
|
481
|
+
return false
|
|
477
482
|
}
|
|
478
|
-
state.pending.set(specifier, group.member)
|
|
483
|
+
state.pending.set(specifier, group.member)
|
|
479
484
|
// While this demand waits on a shared round, promoting IT has to promote the
|
|
480
485
|
// round — that is the only work its result depends on.
|
|
481
|
-
const groupState = state
|
|
482
|
-
ticket.promote = () => promoteVendorWork(groupState.ticket)
|
|
486
|
+
const groupState = state
|
|
487
|
+
ticket.promote = () => promoteVendorWork(groupState.ticket)
|
|
483
488
|
try {
|
|
484
489
|
while (!state.produced.has(specifier)) {
|
|
485
|
-
if (state.closed || state.solo.has(specifier)) return false
|
|
486
|
-
if (nested) promoteVendorWork(state.ticket)
|
|
490
|
+
if (state.closed || state.solo.has(specifier)) return false
|
|
491
|
+
if (nested) promoteVendorWork(state.ticket)
|
|
487
492
|
await watchVendorWait(
|
|
488
493
|
nested ? 'group-round-nested' : 'group-round',
|
|
489
494
|
`${group.key} :: ${specifier}`,
|
|
490
495
|
runVendorGroupRound(group, state, nested),
|
|
491
|
-
)
|
|
496
|
+
)
|
|
492
497
|
}
|
|
493
498
|
} finally {
|
|
494
|
-
ticket.promote = undefined
|
|
499
|
+
ticket.promote = undefined
|
|
495
500
|
}
|
|
496
|
-
return true
|
|
501
|
+
return true
|
|
497
502
|
}
|
|
498
503
|
|
|
499
504
|
function runVendorGroupRound(
|
|
@@ -503,73 +508,69 @@ function runVendorGroupRound(
|
|
|
503
508
|
): Promise<void> {
|
|
504
509
|
if (!state.round) {
|
|
505
510
|
state.round = vendorGroupRound(group, state, nested).finally(() => {
|
|
506
|
-
state.round = undefined
|
|
507
|
-
state.ticket = undefined
|
|
508
|
-
})
|
|
511
|
+
state.round = undefined
|
|
512
|
+
state.ticket = undefined
|
|
513
|
+
})
|
|
509
514
|
}
|
|
510
|
-
return state.round
|
|
515
|
+
return state.round
|
|
511
516
|
}
|
|
512
517
|
|
|
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;
|
|
518
|
+
async function vendorGroupRound(group: VendorGroupPlan, state: VendorGroupState, nested: boolean) {
|
|
519
|
+
await new Promise(resolve => setTimeout(resolve, vendorGroupWindowMs()))
|
|
520
|
+
const members = [...state.pending.values()]
|
|
521
|
+
if (members.length === 0) return
|
|
521
522
|
// A lone subpath with no sibling to share a graph with is the single-bundle
|
|
522
523
|
// path plus chunk plumbing. Hand it back — and keep it on file, so a sibling
|
|
523
524
|
// demanded later externalizes its artifact instead of re-parsing it.
|
|
524
525
|
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
|
|
526
|
+
const only = members[0]!
|
|
527
|
+
state.pending.delete(only.specifier)
|
|
528
|
+
state.solo.set(only.specifier, only)
|
|
529
|
+
return
|
|
529
530
|
}
|
|
530
|
-
state.rounds += 1
|
|
531
|
-
const external = [...state.produced.values(), ...state.solo.values()]
|
|
532
|
-
const ticket: VendorPromotable = {}
|
|
533
|
-
state.ticket = ticket
|
|
531
|
+
state.rounds += 1
|
|
532
|
+
const external = [...state.produced.values(), ...state.solo.values()]
|
|
533
|
+
const ticket: VendorPromotable = {}
|
|
534
|
+
state.ticket = ticket
|
|
534
535
|
try {
|
|
535
|
-
await withVendorSlot(nested, group.key, () => group.build(members, external, nested), ticket)
|
|
536
|
+
await withVendorSlot(nested, group.key, () => group.build(members, external, nested), ticket)
|
|
536
537
|
for (const member of members) {
|
|
537
|
-
state.pending.delete(member.specifier)
|
|
538
|
-
state.produced.set(member.specifier, member)
|
|
538
|
+
state.pending.delete(member.specifier)
|
|
539
|
+
state.produced.set(member.specifier, member)
|
|
539
540
|
}
|
|
540
541
|
} catch {
|
|
541
542
|
// Grouping is an optimization: one bad sibling must not fail the demand,
|
|
542
543
|
// so the group gives up and every member falls back to its own build.
|
|
543
|
-
state.closed = true
|
|
544
|
+
state.closed = true
|
|
544
545
|
}
|
|
545
|
-
if (state.rounds >= MAX_VENDOR_GROUP_ROUNDS) state.closed = true
|
|
546
|
+
if (state.rounds >= MAX_VENDOR_GROUP_ROUNDS) state.closed = true
|
|
546
547
|
}
|
|
547
548
|
|
|
548
549
|
// --------------------------------------------------------------------------
|
|
549
550
|
// pre-planned builds: enqueued without an awaiter, drained at route-module eval
|
|
550
551
|
// --------------------------------------------------------------------------
|
|
551
552
|
|
|
552
|
-
const preplanBuilds = new Set<Promise<unknown>>()
|
|
553
|
-
let preplanBuildError: unknown
|
|
553
|
+
const preplanBuilds = new Set<Promise<unknown>>()
|
|
554
|
+
let preplanBuildError: unknown
|
|
554
555
|
|
|
555
556
|
export function trackPreplanBuild(promise: Promise<unknown>) {
|
|
556
557
|
const tracked = promise.catch(error => {
|
|
557
|
-
preplanBuildError ??= error
|
|
558
|
-
})
|
|
559
|
-
preplanBuilds.add(tracked)
|
|
560
|
-
void tracked.finally(() => preplanBuilds.delete(tracked))
|
|
558
|
+
preplanBuildError ??= error
|
|
559
|
+
})
|
|
560
|
+
preplanBuilds.add(tracked)
|
|
561
|
+
void tracked.finally(() => preplanBuilds.delete(tracked))
|
|
561
562
|
}
|
|
562
563
|
|
|
563
564
|
/** The one join point: nothing evaluates until every enqueued build settles. */
|
|
564
565
|
export async function drainPreplanBuilds() {
|
|
565
566
|
while (preplanBuilds.size > 0) {
|
|
566
|
-
await watchVendorWait('preplan-drain', 'preplan', Promise.allSettled([...preplanBuilds]))
|
|
567
|
+
await watchVendorWait('preplan-drain', 'preplan', Promise.allSettled([...preplanBuilds]))
|
|
567
568
|
}
|
|
568
569
|
if (preplanBuildError !== undefined) {
|
|
569
|
-
const error = preplanBuildError
|
|
570
|
-
preplanBuildError = undefined
|
|
570
|
+
const error = preplanBuildError
|
|
571
|
+
preplanBuildError = undefined
|
|
571
572
|
// eslint-disable-next-line @typescript-eslint/only-throw-error
|
|
572
|
-
throw error
|
|
573
|
+
throw error
|
|
573
574
|
}
|
|
574
575
|
}
|
|
575
576
|
|
|
@@ -579,29 +580,29 @@ export async function drainPreplanBuilds() {
|
|
|
579
580
|
* evicts, so concurrent demands join the one rebuild instead of racing.
|
|
580
581
|
*/
|
|
581
582
|
export function dropVendorBundle(key: string, stale: string) {
|
|
582
|
-
const pending = pendingBundles.get(key)
|
|
583
|
-
if (pending?.result === stale) pendingBundles.delete(key)
|
|
583
|
+
const pending = pendingBundles.get(key)
|
|
584
|
+
if (pending?.result === stale) pendingBundles.delete(key)
|
|
584
585
|
}
|
|
585
586
|
|
|
586
587
|
/** Forget a group whose artifacts were wiped, so the retry rebuilds them. */
|
|
587
588
|
export function dropVendorGroup(key: string) {
|
|
588
|
-
vendorGroups.delete(key)
|
|
589
|
+
vendorGroups.delete(key)
|
|
589
590
|
}
|
|
590
591
|
|
|
591
592
|
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
|
|
593
|
+
pendingBundles.clear()
|
|
594
|
+
vendorGroups.clear()
|
|
595
|
+
preplanBuilds.clear()
|
|
596
|
+
preplanBuildError = undefined
|
|
597
|
+
esmDistFiles.clear()
|
|
598
|
+
vendorContentIds.clear()
|
|
599
|
+
vendorSpans.length = 0
|
|
599
600
|
// In-flight waits belong to the pipeline that was just dropped; leaving them
|
|
600
601
|
// registered would have the watchdog report a stall that no longer exists.
|
|
601
|
-
vendorWaits.clear()
|
|
602
|
+
vendorWaits.clear()
|
|
602
603
|
if (stallTimer) {
|
|
603
|
-
clearInterval(stallTimer)
|
|
604
|
-
stallTimer = undefined
|
|
604
|
+
clearInterval(stallTimer)
|
|
605
|
+
stallTimer = undefined
|
|
605
606
|
}
|
|
606
607
|
}
|
|
607
608
|
|
|
@@ -610,14 +611,14 @@ export function clearVendorPipeline() {
|
|
|
610
611
|
// --------------------------------------------------------------------------
|
|
611
612
|
|
|
612
613
|
/** Artifact (or facade) file -> content id of the bytes it resolves to. */
|
|
613
|
-
const vendorContentIds = new Map<string, string>()
|
|
614
|
+
const vendorContentIds = new Map<string, string>()
|
|
614
615
|
|
|
615
616
|
export function vendorContentId(file: string) {
|
|
616
|
-
return vendorContentIds.get(file)
|
|
617
|
+
return vendorContentIds.get(file)
|
|
617
618
|
}
|
|
618
619
|
|
|
619
620
|
export function noteVendorContentId(file: string, id: string) {
|
|
620
|
-
vendorContentIds.set(file, id)
|
|
621
|
+
vendorContentIds.set(file, id)
|
|
621
622
|
}
|
|
622
623
|
|
|
623
624
|
/**
|
|
@@ -626,10 +627,10 @@ export function noteVendorContentId(file: string, id: string) {
|
|
|
626
627
|
*/
|
|
627
628
|
export function canonicalVendorCode(code: string) {
|
|
628
629
|
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
|
-
})
|
|
630
|
+
if (!ref.specifier.startsWith('file:')) return undefined
|
|
631
|
+
const id = vendorContentIds.get(fileURLToPath(ref.specifier))
|
|
632
|
+
return id ? `pnext-content:${id}` : undefined
|
|
633
|
+
})
|
|
633
634
|
}
|
|
634
635
|
|
|
635
636
|
// --------------------------------------------------------------------------
|
|
@@ -642,61 +643,58 @@ export function canonicalVendorCode(code: string) {
|
|
|
642
643
|
* and cost a JS bridge hop per file.
|
|
643
644
|
*/
|
|
644
645
|
export interface EmittedRef {
|
|
645
|
-
specifier: string
|
|
646
|
+
specifier: string
|
|
646
647
|
/** esbuild emits an external `require()` as `__require("x")`. */
|
|
647
|
-
require: boolean
|
|
648
|
+
require: boolean
|
|
648
649
|
/** Offsets of the specifier text itself, quotes excluded. */
|
|
649
|
-
start: number
|
|
650
|
-
end: number
|
|
650
|
+
start: number
|
|
651
|
+
end: number
|
|
651
652
|
}
|
|
652
653
|
|
|
653
654
|
// Import/export statements, dynamic imports, and (esbuild's) require calls.
|
|
654
655
|
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
|
|
656
|
+
/(?:^|[\s;}])(?:import|export)\s*(?:[\w$*{}\s,]*?\bfrom\s*)?(["'])([^"'\n]*)\1/g
|
|
657
|
+
const EMITTED_CALL_REF = /(?:^|[^\w$.])(?:(__)?require|import)\s*\(\s*(["'])([^"'\n]*)\2\s*\)/g
|
|
657
658
|
|
|
658
659
|
/** Every bare-specifier reference the emitted code still carries. */
|
|
659
660
|
export function emittedRefs(code: string): EmittedRef[] {
|
|
660
|
-
const refs: EmittedRef[] = []
|
|
661
|
-
const seen = new Set<number>()
|
|
661
|
+
const refs: EmittedRef[] = []
|
|
662
|
+
const seen = new Set<number>()
|
|
662
663
|
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 })
|
|
664
|
+
const start = match.index + match[0].length - match[2]!.length - 1
|
|
665
|
+
if (seen.has(start)) continue
|
|
666
|
+
seen.add(start)
|
|
667
|
+
refs.push({ specifier: match[2]!, require: false, start, end: start + match[2]!.length })
|
|
667
668
|
}
|
|
668
669
|
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)
|
|
670
|
+
const value = match[3]!
|
|
671
|
+
const start = match.index + match[0].lastIndexOf(value)
|
|
672
|
+
if (seen.has(start)) continue
|
|
673
|
+
seen.add(start)
|
|
673
674
|
refs.push({
|
|
674
675
|
specifier: value,
|
|
675
676
|
require: match[0].includes('require'),
|
|
676
677
|
start,
|
|
677
678
|
end: start + value.length,
|
|
678
|
-
})
|
|
679
|
+
})
|
|
679
680
|
}
|
|
680
|
-
return refs.sort((a, b) => a.start - b.start)
|
|
681
|
+
return refs.sort((a, b) => a.start - b.start)
|
|
681
682
|
}
|
|
682
683
|
|
|
683
684
|
/**
|
|
684
685
|
* Repoint emitted references. `replace` returns the new specifier, or undefined
|
|
685
686
|
* to leave one alone — a bare package name the runtime resolves itself.
|
|
686
687
|
*/
|
|
687
|
-
export function rewriteEmittedRefs(
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
) {
|
|
691
|
-
let out = '';
|
|
692
|
-
let cursor = 0;
|
|
688
|
+
export function rewriteEmittedRefs(code: string, replace: (ref: EmittedRef) => string | undefined) {
|
|
689
|
+
let out = ''
|
|
690
|
+
let cursor = 0
|
|
693
691
|
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
|
|
692
|
+
const next = replace(ref)
|
|
693
|
+
if (next === undefined || next === ref.specifier) continue
|
|
694
|
+
out += code.slice(cursor, ref.start) + next.replace(/\\/g, '\\\\')
|
|
695
|
+
cursor = ref.end
|
|
698
696
|
}
|
|
699
|
-
return cursor === 0 ? code : out + code.slice(cursor)
|
|
697
|
+
return cursor === 0 ? code : out + code.slice(cursor)
|
|
700
698
|
}
|
|
701
699
|
|
|
702
700
|
/**
|
|
@@ -705,21 +703,25 @@ export function rewriteEmittedRefs(
|
|
|
705
703
|
* actually bind what its importer asks for.
|
|
706
704
|
*/
|
|
707
705
|
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
|
|
706
|
+
const names = new Set<string>()
|
|
707
|
+
if (/(^|\n)\s*export\s*\*\s*from/.test(code)) return undefined
|
|
710
708
|
for (const match of code.matchAll(/(^|[\s;}])export\s*\{([^}]*)\}/g)) {
|
|
711
709
|
for (const entry of match[2]!.split(',')) {
|
|
712
|
-
const name = entry
|
|
713
|
-
|
|
710
|
+
const name = entry
|
|
711
|
+
.trim()
|
|
712
|
+
.split(/\s+as\s+/)
|
|
713
|
+
.pop()
|
|
714
|
+
?.trim()
|
|
715
|
+
if (name) names.add(name)
|
|
714
716
|
}
|
|
715
717
|
}
|
|
716
718
|
for (const match of code.matchAll(
|
|
717
719
|
/(^|[\s;}])export\s+(?:declare\s+)?(?:async\s+)?(?:const|let|var|function\*?|class)\s+([A-Za-z_$][\w$]*)/g,
|
|
718
720
|
)) {
|
|
719
|
-
names.add(match[2]!)
|
|
721
|
+
names.add(match[2]!)
|
|
720
722
|
}
|
|
721
|
-
if (/(^|[\s;}])export\s+default[\s({[]/.test(code)) names.add('default')
|
|
722
|
-
return names
|
|
723
|
+
if (/(^|[\s;}])export\s+default[\s({[]/.test(code)) names.add('default')
|
|
724
|
+
return names
|
|
723
725
|
}
|
|
724
726
|
|
|
725
727
|
/**
|
|
@@ -727,34 +729,34 @@ export function emittedExportNames(code: string): Set<string> | undefined {
|
|
|
727
729
|
* Applied to the bundle itself (not a separate module) for the inlined case.
|
|
728
730
|
*/
|
|
729
731
|
export function addCommonJsNamedExports(code: string) {
|
|
730
|
-
if (hasNonDefaultExport(code)) return code
|
|
732
|
+
if (hasNonDefaultExport(code)) return code
|
|
731
733
|
const names = [...new Set([...commonJsExportNames(code), ...esbuildEntryExportNames(code)])]
|
|
732
734
|
.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]
|
|
735
|
+
.sort()
|
|
736
|
+
if (names.length === 0) return code
|
|
737
|
+
const defaultExport = /(^|\n)export default ([^;\n]+);/.exec(code)
|
|
738
|
+
if (!defaultExport?.[2]) return code
|
|
739
|
+
const defaultName = uniqueIdentifier(code, '__pnext_cjs_default')
|
|
740
|
+
const usedNames = [defaultName]
|
|
739
741
|
const bindings = names.map(name => {
|
|
740
|
-
const localName = uniqueIdentifier(code, `__pnext_cjs_export_${name}`, ...usedNames)
|
|
741
|
-
usedNames.push(localName)
|
|
742
|
-
return { name, localName }
|
|
743
|
-
})
|
|
742
|
+
const localName = uniqueIdentifier(code, `__pnext_cjs_export_${name}`, ...usedNames)
|
|
743
|
+
usedNames.push(localName)
|
|
744
|
+
return { name, localName }
|
|
745
|
+
})
|
|
744
746
|
const namedBindings = bindings
|
|
745
747
|
.map(({ name, localName }) => `const ${localName} = ${defaultName}[${JSON.stringify(name)}];`)
|
|
746
|
-
.join('\n')
|
|
747
|
-
const namedExports = bindings.map(({ name, localName }) => `${localName} as ${name}`).join(', ')
|
|
748
|
+
.join('\n')
|
|
749
|
+
const namedExports = bindings.map(({ name, localName }) => `${localName} as ${name}`).join(', ')
|
|
748
750
|
return code.replace(
|
|
749
751
|
defaultExport[0],
|
|
750
752
|
`${defaultExport[1]}const ${defaultName} = ${defaultExport[2]};\nexport default ${defaultName};\n${namedBindings}\nexport { ${namedExports} };\n`,
|
|
751
|
-
)
|
|
753
|
+
)
|
|
752
754
|
}
|
|
753
755
|
|
|
754
756
|
function hasNonDefaultExport(code: string) {
|
|
755
757
|
return /(^|\n)export\s+(?:\*|\{[^}]*\b(?!default\b)[A-Za-z_$][\w$]*\b|(?:const|let|var|function|class)\s+)/.test(
|
|
756
758
|
code,
|
|
757
|
-
)
|
|
759
|
+
)
|
|
758
760
|
}
|
|
759
761
|
|
|
760
762
|
export function commonJsExportNames(code: string) {
|
|
@@ -767,7 +769,7 @@ export function commonJsExportNames(code: string) {
|
|
|
767
769
|
]),
|
|
768
770
|
]
|
|
769
771
|
.filter((name): name is string => Boolean(name && name !== '__esModule' && isIdentifier(name)))
|
|
770
|
-
.sort()
|
|
772
|
+
.sort()
|
|
771
773
|
}
|
|
772
774
|
|
|
773
775
|
// Recover named exports from esbuild's `__export(entry, {...}); module.exports =
|
|
@@ -775,16 +777,16 @@ export function commonJsExportNames(code: string) {
|
|
|
775
777
|
function esbuildEntryExportNames(code: string) {
|
|
776
778
|
const toCommonJs = [
|
|
777
779
|
...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 []
|
|
780
|
+
]
|
|
781
|
+
const entryVar = toCommonJs.at(-1)?.[1]
|
|
782
|
+
if (!entryVar) return []
|
|
781
783
|
const exportCall = new RegExp(
|
|
782
784
|
`__export\\w*\\(\\s*${escapeRegex(entryVar)}\\s*,\\s*\\{([\\s\\S]*?)\\}\\s*\\)`,
|
|
783
|
-
).exec(code)
|
|
784
|
-
if (!exportCall?.[1]) return []
|
|
785
|
+
).exec(code)
|
|
786
|
+
if (!exportCall?.[1]) return []
|
|
785
787
|
return [...exportCall[1].matchAll(/(?:^|,)\s*([A-Za-z_$][\w$]*)\s*:/g)]
|
|
786
788
|
.map(match => match[1])
|
|
787
|
-
.filter((name): name is string => Boolean(name && isIdentifier(name)))
|
|
789
|
+
.filter((name): name is string => Boolean(name && isIdentifier(name)))
|
|
788
790
|
}
|
|
789
791
|
|
|
790
792
|
/**
|
|
@@ -802,16 +804,16 @@ export async function verifyVendorArtifact(
|
|
|
802
804
|
chunks: ReadonlyMap<string, string> = new Map(),
|
|
803
805
|
) {
|
|
804
806
|
for (const [specifier, target] of repointed) {
|
|
805
|
-
if (!existsSync(target)) return `repointed ${specifier} -> missing ${target}
|
|
807
|
+
if (!existsSync(target)) return `repointed ${specifier} -> missing ${target}`
|
|
806
808
|
}
|
|
807
809
|
// Nothing was rewritten and nothing was split out: this is esbuild's own
|
|
808
810
|
// output, unedited, and there is no claim left for a re-bundle to check.
|
|
809
|
-
if (repointed.size === 0 && chunks.size === 0) return undefined
|
|
811
|
+
if (repointed.size === 0 && chunks.size === 0) return undefined
|
|
810
812
|
// 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)
|
|
813
|
+
const mode = process.env.PNEXT_VENDOR_VERIFY
|
|
814
|
+
if (mode === 'off') return undefined
|
|
815
|
+
const deep = mode === 'deep'
|
|
816
|
+
const outDir = path.dirname(file)
|
|
815
817
|
try {
|
|
816
818
|
await build({
|
|
817
819
|
stdin: { contents: code, resolveDir: outDir, sourcefile: file, loader: 'js' },
|
|
@@ -830,32 +832,34 @@ export async function verifyVendorArtifact(
|
|
|
830
832
|
// before anything is published, so nothing half-written is ever
|
|
831
833
|
// readable by a concurrent demand.
|
|
832
834
|
verify.onResolve({ filter: /^\.\.?\// }, args => {
|
|
833
|
-
const resolved = path.resolve(args.resolveDir, args.path)
|
|
835
|
+
const resolved = path.resolve(args.resolveDir, args.path)
|
|
834
836
|
return chunks.has(resolved)
|
|
835
837
|
? { path: resolved, namespace: 'pnext-vendor-chunk' }
|
|
836
|
-
: undefined
|
|
837
|
-
})
|
|
838
|
+
: undefined
|
|
839
|
+
})
|
|
838
840
|
verify.onLoad({ filter: /.*/, namespace: 'pnext-vendor-chunk' }, args => ({
|
|
839
841
|
contents: chunks.get(args.path),
|
|
840
842
|
loader: 'js',
|
|
841
843
|
resolveDir: path.dirname(args.path),
|
|
842
|
-
}))
|
|
844
|
+
}))
|
|
843
845
|
verify.onResolve({ filter: /^file:/ }, args => {
|
|
844
|
-
const resolved = fileURLToPath(args.path)
|
|
846
|
+
const resolved = fileURLToPath(args.path)
|
|
845
847
|
// Deep: one level in (esbuild then checks the export names), and
|
|
846
848
|
// only from the artifact itself — its dependencies' own externals
|
|
847
849
|
// 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
|
-
})
|
|
850
|
+
const inside = args.importer === file || args.importer.startsWith(outDir)
|
|
851
|
+
return { path: resolved, external: !deep || !inside }
|
|
852
|
+
})
|
|
851
853
|
},
|
|
852
854
|
},
|
|
853
855
|
],
|
|
854
|
-
})
|
|
856
|
+
})
|
|
855
857
|
} catch (error) {
|
|
856
|
-
return (error instanceof Error ? error.message : String(error))
|
|
858
|
+
return (error instanceof Error ? error.message : String(error))
|
|
859
|
+
.replace(/\s+/g, ' ')
|
|
860
|
+
.slice(0, 300)
|
|
857
861
|
}
|
|
858
|
-
return undefined
|
|
862
|
+
return undefined
|
|
859
863
|
}
|
|
860
864
|
|
|
861
865
|
// --------------------------------------------------------------------------
|
|
@@ -872,46 +876,46 @@ export async function verifyVendorArtifact(
|
|
|
872
876
|
* in the closure means a plugin could have fired, so the package goes back to the bundler. A false negative
|
|
873
877
|
* costs a compile; a false positive ships wrong code.
|
|
874
878
|
*/
|
|
875
|
-
const MAX_ESM_DIST_FILES = 4096
|
|
876
|
-
const ESM_DIST_ENTRY_MARKER = 'entry'
|
|
879
|
+
const MAX_ESM_DIST_FILES = 4096
|
|
880
|
+
const ESM_DIST_ENTRY_MARKER = 'entry'
|
|
877
881
|
|
|
878
|
-
const esmDists = new Map<string, Promise<string | undefined>>()
|
|
882
|
+
const esmDists = new Map<string, Promise<string | undefined>>()
|
|
879
883
|
|
|
880
884
|
export function copyBrowserReadyEsmDist(entry: string, distDir: string) {
|
|
881
885
|
// One copy per destination: a second copier would rewrite files the first has
|
|
882
886
|
// already published, and copyFile is not atomic against a concurrent import.
|
|
883
|
-
let dist = esmDists.get(distDir)
|
|
887
|
+
let dist = esmDists.get(distDir)
|
|
884
888
|
if (!dist) {
|
|
885
|
-
dist = copyBrowserReadyEsmDistUncached(entry, distDir)
|
|
886
|
-
esmDists.set(distDir, dist)
|
|
889
|
+
dist = copyBrowserReadyEsmDistUncached(entry, distDir)
|
|
890
|
+
esmDists.set(distDir, dist)
|
|
887
891
|
}
|
|
888
|
-
return dist
|
|
892
|
+
return dist
|
|
889
893
|
}
|
|
890
894
|
|
|
891
895
|
async function copyBrowserReadyEsmDistUncached(entry: string, distDir: string) {
|
|
892
896
|
const existing = await readFile(path.join(distDir, ESM_DIST_ENTRY_MARKER), 'utf8').catch(
|
|
893
897
|
() => undefined,
|
|
894
|
-
)
|
|
895
|
-
if (existing !== undefined) return path.join(distDir, existing)
|
|
898
|
+
)
|
|
899
|
+
if (existing !== undefined) return path.join(distDir, existing)
|
|
896
900
|
|
|
897
|
-
const closure = await collectBrowserReadyEsmClosure(entry)
|
|
898
|
-
if (!closure) return undefined
|
|
901
|
+
const closure = await collectBrowserReadyEsmClosure(entry)
|
|
902
|
+
if (!closure) return undefined
|
|
899
903
|
// Copies keep their offsets relative to the shallowest directory the closure
|
|
900
904
|
// 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)
|
|
905
|
+
const base = commonAncestorDir([...closure])
|
|
906
|
+
const relative = (file: string) => path.relative(base, file)
|
|
903
907
|
await Promise.all(
|
|
904
908
|
[...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)
|
|
909
|
+
const target = path.join(distDir, relative(file))
|
|
910
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
911
|
+
await copyFile(file, target)
|
|
908
912
|
}),
|
|
909
|
-
)
|
|
910
|
-
await writeFileAtomic(path.join(distDir, ESM_DIST_ENTRY_MARKER), relative(entry))
|
|
911
|
-
return path.join(distDir, relative(entry))
|
|
913
|
+
)
|
|
914
|
+
await writeFileAtomic(path.join(distDir, ESM_DIST_ENTRY_MARKER), relative(entry))
|
|
915
|
+
return path.join(distDir, relative(entry))
|
|
912
916
|
}
|
|
913
917
|
|
|
914
|
-
const ESM_DIST_EXTENSIONS = new Set(['.js', '.mjs'])
|
|
918
|
+
const ESM_DIST_EXTENSIONS = new Set(['.js', '.mjs'])
|
|
915
919
|
|
|
916
920
|
/**
|
|
917
921
|
* Per-FILE verdict, memoized: the predicate runs once per demanded artifact and
|
|
@@ -919,85 +923,85 @@ const ESM_DIST_EXTENSIONS = new Set(['.js', '.mjs']);
|
|
|
919
923
|
* thousand files were re-read, re-regexed and re-`existsSync`ed per subpath.
|
|
920
924
|
* `undefined` means "not browser-ready", which fails the whole closure.
|
|
921
925
|
*/
|
|
922
|
-
const esmDistFiles = new Map<string, Promise<string[] | undefined>>()
|
|
926
|
+
const esmDistFiles = new Map<string, Promise<string[] | undefined>>()
|
|
923
927
|
|
|
924
928
|
function browserReadyEsmImports(file: string) {
|
|
925
|
-
let entry = esmDistFiles.get(file)
|
|
929
|
+
let entry = esmDistFiles.get(file)
|
|
926
930
|
if (!entry) {
|
|
927
|
-
entry = browserReadyEsmImportsUncached(file)
|
|
928
|
-
esmDistFiles.set(file, entry)
|
|
931
|
+
entry = browserReadyEsmImportsUncached(file)
|
|
932
|
+
esmDistFiles.set(file, entry)
|
|
929
933
|
}
|
|
930
|
-
return entry
|
|
934
|
+
return entry
|
|
931
935
|
}
|
|
932
936
|
|
|
933
937
|
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
|
|
938
|
+
if (!ESM_DIST_EXTENSIONS.has(path.extname(file))) return undefined
|
|
939
|
+
const source = await readFile(file, 'utf8').catch(() => undefined)
|
|
940
|
+
if (source === undefined) return undefined
|
|
937
941
|
// `use cache` would need the post-bundle source transforms this path skips.
|
|
938
942
|
if (
|
|
939
943
|
isCommonJsModuleSource(source, file) ||
|
|
940
944
|
/\brequire\s*\(|\bmodule\.exports\b/.test(source) ||
|
|
941
945
|
source.includes('use cache')
|
|
942
946
|
) {
|
|
943
|
-
return undefined
|
|
947
|
+
return undefined
|
|
944
948
|
}
|
|
945
|
-
const imports: string[] = []
|
|
949
|
+
const imports: string[] = []
|
|
946
950
|
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)
|
|
951
|
+
const specifier = found.value
|
|
952
|
+
if (!specifier.startsWith('.')) return undefined
|
|
953
|
+
const resolved = resolveEsmDistImport(path.dirname(file), specifier)
|
|
954
|
+
if (!resolved) return undefined
|
|
955
|
+
imports.push(resolved)
|
|
952
956
|
}
|
|
953
|
-
return imports
|
|
957
|
+
return imports
|
|
954
958
|
}
|
|
955
959
|
|
|
956
960
|
async function collectBrowserReadyEsmClosure(entry: string) {
|
|
957
|
-
const closure = new Set<string>()
|
|
958
|
-
const queue = [entry]
|
|
961
|
+
const closure = new Set<string>()
|
|
962
|
+
const queue = [entry]
|
|
959
963
|
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)
|
|
964
|
+
const file = queue.pop()!
|
|
965
|
+
if (closure.has(file)) continue
|
|
966
|
+
if (closure.size >= MAX_ESM_DIST_FILES) return undefined
|
|
967
|
+
closure.add(file)
|
|
968
|
+
const imports = await browserReadyEsmImports(file)
|
|
969
|
+
if (!imports) return undefined
|
|
970
|
+
queue.push(...imports)
|
|
967
971
|
}
|
|
968
972
|
// A single-file "closure" is a re-export shim or a stub; bundling it is
|
|
969
973
|
// already free, and the marker directory would cost more than it saves.
|
|
970
|
-
return closure.size > 1 ? closure : undefined
|
|
974
|
+
return closure.size > 1 ? closure : undefined
|
|
971
975
|
}
|
|
972
976
|
|
|
973
977
|
/** Relative resolution limited to what a browser would do: exact file first. */
|
|
974
978
|
function resolveEsmDistImport(fromDir: string, specifier: string) {
|
|
975
|
-
const base = path.resolve(fromDir, specifier)
|
|
979
|
+
const base = path.resolve(fromDir, specifier)
|
|
976
980
|
for (const candidate of [base, `${base}.js`, `${base}.mjs`, path.join(base, 'index.js')]) {
|
|
977
981
|
if (ESM_DIST_EXTENSIONS.has(path.extname(candidate)) && cachedExistsSync(candidate)) {
|
|
978
|
-
return candidate
|
|
982
|
+
return candidate
|
|
979
983
|
}
|
|
980
984
|
}
|
|
981
|
-
return undefined
|
|
985
|
+
return undefined
|
|
982
986
|
}
|
|
983
987
|
|
|
984
988
|
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[] = []
|
|
989
|
+
const segmentLists = files.map(file => path.dirname(file).split(path.sep))
|
|
990
|
+
const shortest = Math.min(...segmentLists.map(segments => segments.length))
|
|
991
|
+
const common: string[] = []
|
|
988
992
|
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)
|
|
993
|
+
const segment = segmentLists[0]![index]!
|
|
994
|
+
if (segmentLists.some(segments => segments[index] !== segment)) break
|
|
995
|
+
common.push(segment)
|
|
992
996
|
}
|
|
993
|
-
return common.join(path.sep) || path.sep
|
|
997
|
+
return common.join(path.sep) || path.sep
|
|
994
998
|
}
|
|
995
999
|
|
|
996
1000
|
// --------------------------------------------------------------------------
|
|
997
1001
|
// facades
|
|
998
1002
|
// --------------------------------------------------------------------------
|
|
999
1003
|
|
|
1000
|
-
const REEXPORT_CALL = /\bmodule\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)
|
|
1004
|
+
const REEXPORT_CALL = /\bmodule\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)/
|
|
1001
1005
|
|
|
1002
1006
|
/**
|
|
1003
1007
|
* Cheap pre-filter: can this CommonJS entry plausibly carry named exports? A
|
|
@@ -1007,19 +1011,19 @@ const REEXPORT_CALL = /\bmodule\.exports\s*=\s*require\(\s*(['"])([^'"]+)\1\s*\)
|
|
|
1007
1011
|
* level, which is where most packages' real export list lives.
|
|
1008
1012
|
*/
|
|
1009
1013
|
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
|
|
1014
|
+
const source = await readFile(entry, 'utf8').catch(() => undefined)
|
|
1015
|
+
if (source === undefined) return false
|
|
1016
|
+
if (commonJsExportNames(source).length > 0) return true
|
|
1017
|
+
const reexport = REEXPORT_CALL.exec(source)
|
|
1018
|
+
const target = reexport?.[2]
|
|
1019
|
+
if (!target?.startsWith('.')) return false
|
|
1016
1020
|
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
|
|
1021
|
+
const file = path.resolve(path.dirname(entry), candidate)
|
|
1022
|
+
if (!existsSync(file) || !file.endsWith('.js')) continue
|
|
1023
|
+
const inner = await readFile(file, 'utf8').catch(() => undefined)
|
|
1024
|
+
if (inner && commonJsExportNames(inner).length > 0) return true
|
|
1021
1025
|
}
|
|
1022
|
-
return false
|
|
1026
|
+
return false
|
|
1023
1027
|
}
|
|
1024
1028
|
|
|
1025
1029
|
/**
|
|
@@ -1029,28 +1033,32 @@ export async function cjsEntryMayHaveNamedExports(entry: string) {
|
|
|
1029
1033
|
* full cache wipe recovers from. A new key sidesteps both: the old facade stays on disk for whoever still
|
|
1030
1034
|
* imports it, and every new compile names the new one.
|
|
1031
1035
|
*/
|
|
1032
|
-
const FACADE_GENERATOR = 'v2'
|
|
1036
|
+
const FACADE_GENERATOR = 'v2'
|
|
1033
1037
|
|
|
1034
1038
|
/** Marker written beside a bundle whose export set turned out to be default-only. */
|
|
1035
|
-
const NO_FACADE_SUFFIX = `.no-facade-${FACADE_GENERATOR}
|
|
1039
|
+
const NO_FACADE_SUFFIX = `.no-facade-${FACADE_GENERATOR}`
|
|
1036
1040
|
|
|
1037
1041
|
/** The facade this generator writes for `bundleFile`. */
|
|
1038
1042
|
export function cjsFacadePath(bundleFile: string) {
|
|
1039
|
-
return `${bundleFile.replace(/\.mjs$/, '')}.facade-${FACADE_GENERATOR}.mjs
|
|
1043
|
+
return `${bundleFile.replace(/\.mjs$/, '')}.facade-${FACADE_GENERATOR}.mjs`
|
|
1040
1044
|
}
|
|
1041
1045
|
|
|
1042
1046
|
/** Where this generator records "no named exports" for `bundleFile`. */
|
|
1043
1047
|
export function cjsNoFacadeMarker(bundleFile: string) {
|
|
1044
|
-
return `${bundleFile}${NO_FACADE_SUFFIX}
|
|
1048
|
+
return `${bundleFile}${NO_FACADE_SUFFIX}`
|
|
1045
1049
|
}
|
|
1046
1050
|
|
|
1047
|
-
const facades = new Map<string, Promise<string | undefined>>()
|
|
1051
|
+
const facades = new Map<string, Promise<string | undefined>>()
|
|
1048
1052
|
|
|
1049
1053
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1050
|
-
const heavyProfFile = process.env.PNEXT_HEAVY_PROF
|
|
1054
|
+
const heavyProfFile = process.env.PNEXT_HEAVY_PROF
|
|
1051
1055
|
export function heavyProfRow(row: Record<string, unknown>) {
|
|
1052
|
-
if (!heavyProfFile) return
|
|
1053
|
-
try {
|
|
1056
|
+
if (!heavyProfFile) return
|
|
1057
|
+
try {
|
|
1058
|
+
appendFileSync(heavyProfFile, `${JSON.stringify(row)}\n`)
|
|
1059
|
+
} catch {
|
|
1060
|
+
/* prof only */
|
|
1061
|
+
}
|
|
1054
1062
|
}
|
|
1055
1063
|
|
|
1056
1064
|
/**
|
|
@@ -1059,48 +1067,55 @@ export function heavyProfRow(row: Record<string, unknown>) {
|
|
|
1059
1067
|
* inlines instead.
|
|
1060
1068
|
*/
|
|
1061
1069
|
export function cjsNamedExportFacade(bundleFile: string, importHref: string) {
|
|
1062
|
-
let facade = facades.get(bundleFile)
|
|
1070
|
+
let facade = facades.get(bundleFile)
|
|
1063
1071
|
if (!facade) {
|
|
1064
|
-
facade = writeCjsNamedExportFacade(bundleFile, importHref).catch(() => undefined)
|
|
1065
|
-
facades.set(bundleFile, facade)
|
|
1072
|
+
facade = writeCjsNamedExportFacade(bundleFile, importHref).catch(() => undefined)
|
|
1073
|
+
facades.set(bundleFile, facade)
|
|
1066
1074
|
}
|
|
1067
|
-
return facade
|
|
1075
|
+
return facade
|
|
1068
1076
|
}
|
|
1069
1077
|
|
|
1070
1078
|
async function writeCjsNamedExportFacade(bundleFile: string, importHref: string) {
|
|
1071
|
-
const file = cjsFacadePath(bundleFile)
|
|
1079
|
+
const file = cjsFacadePath(bundleFile)
|
|
1072
1080
|
// The facade's identity follows its bundle's, so an importer of one canonical-
|
|
1073
1081
|
// izes the same way an importer of the other does.
|
|
1074
|
-
const bundleId = vendorContentIds.get(bundleFile)
|
|
1075
|
-
if (bundleId) vendorContentIds.set(file, `${bundleId}:facade`)
|
|
1082
|
+
const bundleId = vendorContentIds.get(bundleFile)
|
|
1083
|
+
if (bundleId) vendorContentIds.set(file, `${bundleId}:facade`)
|
|
1076
1084
|
// Both outcomes persist: the facade is content-keyed with the bundle it wraps,
|
|
1077
1085
|
// 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
|
|
1086
|
+
if (existsSync(file)) return file
|
|
1087
|
+
if (existsSync(cjsNoFacadeMarker(bundleFile))) return undefined
|
|
1080
1088
|
|
|
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()
|
|
1089
|
+
const t0 = performance.now()
|
|
1090
|
+
const code = await readFile(bundleFile, 'utf8').catch(() => undefined)
|
|
1091
|
+
if (code === undefined) return undefined
|
|
1092
|
+
const tRead = performance.now()
|
|
1085
1093
|
// The exec pass is REQUIRED, not a supplement: a statically-scanned set is routinely short, and an
|
|
1086
1094
|
// incomplete facade is worse than inlining - it breaks an importer's named binding at build time.
|
|
1087
1095
|
// A bundle that cannot be executed here simply gets no facade.
|
|
1088
|
-
const executed = await executedExportNames(importHref)
|
|
1089
|
-
const tExec = performance.now()
|
|
1096
|
+
const executed = await executedExportNames(importHref)
|
|
1097
|
+
const tExec = performance.now()
|
|
1090
1098
|
const names =
|
|
1091
1099
|
executed &&
|
|
1092
1100
|
[...new Set([...commonJsExportNames(code), ...executed])]
|
|
1093
1101
|
.filter(name => name !== 'default' && name !== '__esModule' && isIdentifier(name))
|
|
1094
|
-
.sort()
|
|
1102
|
+
.sort()
|
|
1095
1103
|
if (!names || names.length === 0) {
|
|
1096
|
-
heavyProfRow({
|
|
1097
|
-
|
|
1098
|
-
|
|
1104
|
+
heavyProfRow({
|
|
1105
|
+
k: 'facade',
|
|
1106
|
+
f: path.basename(bundleFile),
|
|
1107
|
+
readMs: tRead - t0,
|
|
1108
|
+
execMs: tExec - tRead,
|
|
1109
|
+
verifyMs: 0,
|
|
1110
|
+
out: 'no-names',
|
|
1111
|
+
})
|
|
1112
|
+
await writeFileAtomic(cjsNoFacadeMarker(bundleFile), '')
|
|
1113
|
+
return undefined
|
|
1099
1114
|
}
|
|
1100
1115
|
|
|
1101
|
-
const binding = '__pnext_cjs'
|
|
1102
|
-
const namespace = '__pnext_cjs_ns'
|
|
1103
|
-
const holder = '__pnext_cjs_names'
|
|
1116
|
+
const binding = '__pnext_cjs'
|
|
1117
|
+
const namespace = '__pnext_cjs_ns'
|
|
1118
|
+
const holder = '__pnext_cjs_names'
|
|
1104
1119
|
// A CommonJS entry marked `__esModule` but carrying no `default` (e.g.
|
|
1105
1120
|
// bind-event-listener) leaves the default binding undefined, so reading names
|
|
1106
1121
|
// off it threw at facade eval. Its names live on the bundle's own exports
|
|
@@ -1110,15 +1125,22 @@ async function writeCjsNamedExportFacade(bundleFile: string, importHref: string)
|
|
|
1110
1125
|
`export default ${binding};\n` +
|
|
1111
1126
|
`const ${holder} = ${binding} ?? ${namespace};\n` +
|
|
1112
1127
|
names.map(name => `export const ${name} = ${holder}[${JSON.stringify(name)}];`).join('\n') +
|
|
1113
|
-
'\n'
|
|
1114
|
-
const ok = await facadeRebundles(file, source)
|
|
1115
|
-
heavyProfRow({
|
|
1128
|
+
'\n'
|
|
1129
|
+
const ok = await facadeRebundles(file, source)
|
|
1130
|
+
heavyProfRow({
|
|
1131
|
+
k: 'facade',
|
|
1132
|
+
f: path.basename(bundleFile),
|
|
1133
|
+
readMs: tRead - t0,
|
|
1134
|
+
execMs: tExec - tRead,
|
|
1135
|
+
verifyMs: performance.now() - tExec,
|
|
1136
|
+
out: ok ? 'facade' : 'verify-fail',
|
|
1137
|
+
})
|
|
1116
1138
|
if (!ok) {
|
|
1117
|
-
await writeFileAtomic(cjsNoFacadeMarker(bundleFile), '')
|
|
1118
|
-
return undefined
|
|
1139
|
+
await writeFileAtomic(cjsNoFacadeMarker(bundleFile), '')
|
|
1140
|
+
return undefined
|
|
1119
1141
|
}
|
|
1120
|
-
await writeFileAtomic(file, source)
|
|
1121
|
-
return file
|
|
1142
|
+
await writeFileAtomic(file, source)
|
|
1143
|
+
return file
|
|
1122
1144
|
}
|
|
1123
1145
|
|
|
1124
1146
|
/**
|
|
@@ -1128,12 +1150,12 @@ async function writeCjsNamedExportFacade(bundleFile: string, importHref: string)
|
|
|
1128
1150
|
*/
|
|
1129
1151
|
async function executedExportNames(importHref: string) {
|
|
1130
1152
|
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)
|
|
1153
|
+
const module_ = (await import(importHref)) as Record<string, unknown>
|
|
1154
|
+
const source = (module_.default as Record<string, unknown> | undefined) ?? module_
|
|
1155
|
+
if (!source || (typeof source !== 'object' && typeof source !== 'function')) return undefined
|
|
1156
|
+
return Object.keys(source)
|
|
1135
1157
|
} catch {
|
|
1136
|
-
return undefined
|
|
1158
|
+
return undefined
|
|
1137
1159
|
}
|
|
1138
1160
|
}
|
|
1139
1161
|
|
|
@@ -1152,9 +1174,9 @@ async function facadeRebundles(file: string, source: string) {
|
|
|
1152
1174
|
target: 'es2022',
|
|
1153
1175
|
packages: 'external',
|
|
1154
1176
|
logLevel: 'silent',
|
|
1155
|
-
})
|
|
1156
|
-
return true
|
|
1177
|
+
})
|
|
1178
|
+
return true
|
|
1157
1179
|
} catch {
|
|
1158
|
-
return false
|
|
1180
|
+
return false
|
|
1159
1181
|
}
|
|
1160
1182
|
}
|