@wular/pnext 0.0.4 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -16
- package/bin/pnext +6 -1
- package/config/lint/base.js +7 -7
- package/config/ts/base.json +2 -4
- package/config/ts/react.json +2 -6
- package/package.json +23 -2
- package/reference/compat.md +87 -22
- package/reference/config.md +40 -93
- package/reference/css.md +42 -33
- package/reference/dev.md +26 -33
- package/reference/env.md +18 -20
- package/reference/getting-started.md +132 -0
- package/reference/metadata.md +31 -46
- package/reference/navigation.md +65 -105
- package/reference/performance.md +21 -142
- package/reference/rendering.md +78 -75
- package/reference/routing.md +97 -107
- package/reference/typegen.md +27 -29
- package/src/api/cache.ts +36 -37
- package/src/api/client-cache.ts +2 -2
- package/src/api/client-navigation.ts +121 -114
- package/src/api/dynamic.tsx +58 -55
- package/src/api/link.tsx +52 -53
- package/src/api/navigation.ts +59 -58
- package/src/api/server.ts +152 -144
- package/src/api/suspense.ts +4 -4
- package/src/cli/adapters/vercel-warm.ts +126 -121
- package/src/cli/adapters/vercel.ts +437 -443
- package/src/cli/analyze.ts +333 -144
- package/src/cli/{named-bin.ts → boot/named-bin.ts} +37 -37
- package/src/cli/{boot-trace.ts → boot/trace.ts} +10 -10
- package/src/cli/build.ts +976 -978
- package/src/cli/create.ts +72 -64
- package/src/cli/dev.ts +116 -116
- package/src/cli/index.ts +94 -85
- package/src/cli/migrate/package-json.ts +73 -71
- package/src/cli/migrate/report.ts +32 -33
- package/src/cli/migrate/{index.ts → run.ts} +48 -49
- package/src/cli/migrate/scan.ts +46 -46
- package/src/cli/migrate/spinner.ts +9 -9
- package/src/cli/migrate/tsconfig.ts +35 -35
- package/src/cli/{server-entry.ts → serve/entry.ts} +57 -57
- package/src/cli/{request-pipeline.ts → serve/pipeline.ts} +386 -394
- package/src/cli/{serve-ui.ts → serve/ui.ts} +47 -47
- package/src/cli/start.ts +65 -66
- package/src/{typegen.ts → cli/typegen.ts} +59 -59
- package/src/client/build.ts +802 -768
- package/src/client/chunk-fold.ts +245 -240
- package/src/client/{paths.ts → chunk-name.ts} +6 -6
- package/src/client/entry.ts +162 -147
- package/src/client/prebuilt.ts +231 -223
- package/src/client/profile.ts +29 -29
- package/src/client/react-compiler.ts +20 -15
- package/src/client/{compat-surface.ts → react-tier.ts} +64 -64
- package/src/client/reference-stub.ts +51 -51
- package/src/client/reference.ts +19 -19
- package/src/{api → client}/router/events.ts +32 -17
- package/src/{api → client}/router/history.ts +16 -16
- package/src/{api → client}/router/hub.ts +52 -53
- package/src/{api/router.ts → client/router/index.ts} +61 -60
- package/src/{api → client}/router/policies.ts +24 -24
- package/src/{api → client}/router/runtime.ts +2022 -1995
- package/src/{api → client}/router/types.ts +98 -98
- package/src/compat/actions/client-plugin.ts +42 -43
- package/src/compat/actions/client-stub.ts +14 -14
- package/src/compat/actions/{action-client.ts → client.ts} +194 -193
- package/src/compat/actions/config.ts +63 -64
- package/src/compat/actions/detect.ts +68 -68
- package/src/compat/actions/discovery.ts +105 -105
- package/src/compat/actions/{action-dispatch.ts → dispatch.ts} +277 -274
- package/src/compat/actions/early-submit.ts +1 -1
- package/src/compat/actions/endpoint.ts +198 -198
- package/src/compat/actions/extensions.ts +811 -0
- package/src/compat/actions/flight.ts +23 -23
- package/src/compat/actions/form-state.ts +34 -34
- package/src/compat/actions/hoist.ts +382 -236
- package/src/compat/actions/ids.ts +7 -12
- package/src/compat/actions/index.ts +8 -8
- package/src/compat/actions/instances.ts +46 -46
- package/src/compat/actions/origin.ts +47 -48
- package/src/compat/actions/protocol.ts +22 -22
- package/src/compat/actions/registry.ts +19 -19
- package/src/compat/{misc/action-return.ts → actions/return.ts} +60 -57
- package/src/compat/actions/rewrite.ts +125 -125
- package/src/compat/actions/{action-router.ts → router.ts} +10 -10
- package/src/compat/actions/serve.ts +132 -136
- package/src/compat/actions/server-tag.ts +4 -4
- package/src/compat/actions/{action-shared.ts → shared.ts} +34 -34
- package/src/compat/actions/unrecognized-error.ts +4 -4
- package/src/compat/{index.ts → aliases.ts} +112 -109
- package/src/compat/bundler/bun-externals.ts +18 -18
- package/src/compat/bundler/cjs-exports.ts +271 -223
- package/src/compat/bundler/config.ts +116 -111
- package/src/compat/bundler/externals.ts +12 -12
- package/src/compat/bundler/import-meta-url.ts +19 -19
- package/src/compat/bundler/modularize-imports.ts +36 -33
- package/src/compat/bundler/new-url-asset.ts +22 -24
- package/src/compat/bundler/optimize-package-imports.ts +111 -107
- package/src/compat/bundler/polyfill.ts +28 -28
- package/src/compat/bundler/react-compiler.ts +35 -29
- package/src/compat/bundler/react-profiler.tsx +11 -11
- package/src/compat/bundler/relay-transform.ts +48 -47
- package/src/compat/bundler/require-context.ts +119 -113
- package/src/compat/bundler/resolve-extensions.ts +19 -19
- package/src/compat/bundler/source-cache.ts +25 -25
- package/src/compat/bundler/static-imports.ts +7 -7
- package/src/compat/bundler/symlink-imports.ts +46 -46
- package/src/compat/bundler/tsconfig-paths.ts +13 -15
- package/src/compat/bundler/wasm.ts +58 -60
- package/src/compat/bundler/webpack-loaders.ts +254 -241
- package/src/compat/bundler/worker.ts +101 -104
- package/src/compat/cache/build-flags.ts +29 -29
- package/src/compat/cache/build-prerender-errors.ts +40 -44
- package/src/compat/cache/custom-handler.ts +60 -53
- package/src/compat/cache/fetch-patch.ts +240 -240
- package/src/compat/cache/handler.ts +27 -31
- package/src/compat/cache/modern-handler.ts +148 -126
- package/src/compat/cache/resume-data-cache.ts +47 -45
- package/src/compat/cache/revalidate.ts +233 -232
- package/src/compat/cache/runtime-error.ts +35 -35
- package/src/compat/cache/use-cache-transform.ts +442 -417
- package/src/compat/cache/use-cache.ts +650 -614
- package/src/compat/cache-control.ts +140 -142
- package/src/compat/client/base-path.ts +21 -20
- package/src/compat/client/css-order.ts +18 -18
- package/src/compat/client/errors/bare-boundary.ts +11 -11
- package/src/compat/client/errors/control-flow.ts +23 -23
- package/src/compat/client/errors/error-boundary.ts +61 -61
- package/src/compat/client/errors/global-error.ts +101 -91
- package/src/compat/client/errors/install.ts +71 -72
- package/src/compat/client/errors/lazy.ts +23 -23
- package/src/compat/client/errors/primitive-throw.ts +47 -45
- package/src/compat/client/errors/soft-refresh.ts +4 -4
- package/src/compat/client/link-status.ts +33 -33
- package/src/compat/client/{nav-compat-runtime.ts → nav-runtime.ts} +20 -20
- package/src/compat/client/{nav-compat.ts → nav.ts} +12 -13
- package/src/compat/client/navigation-scroll.ts +63 -63
- package/src/compat/client/optimistic-routing.ts +93 -88
- package/src/compat/client/prefetch-cache.ts +23 -24
- package/src/compat/client/route-announcer.ts +35 -35
- package/src/compat/client/segment-cache-policy.ts +20 -20
- package/src/compat/client/segment-cache.ts +309 -315
- package/src/compat/client/segment-prefetch.ts +127 -132
- package/src/compat/client/trailing-slash.ts +5 -4
- package/src/compat/css/chunking.ts +113 -116
- package/src/compat/css/inline-css.ts +21 -21
- package/src/compat/css/lightningcss.ts +37 -38
- package/src/compat/css/modules.ts +161 -175
- package/src/compat/css/nonce.ts +7 -7
- package/src/compat/css/sass-plugin.ts +18 -21
- package/src/compat/css/sass.ts +150 -152
- package/src/compat/css/styled-jsx-runtime.ts +27 -27
- package/src/compat/css/styled-jsx.ts +21 -21
- package/src/compat/edge-runtime.ts +27 -27
- package/src/compat/{adapter → export}/build-complete.ts +72 -75
- package/src/compat/export/client.ts +29 -31
- package/src/compat/export/{index.ts → emit.ts} +111 -110
- package/src/compat/export/standalone.ts +62 -54
- package/src/compat/image-optimizer/cache.ts +51 -49
- package/src/compat/image-optimizer/detect.ts +52 -52
- package/src/compat/image-optimizer/{index.ts → optimize.ts} +189 -194
- package/src/compat/image-optimizer/source.ts +77 -80
- package/src/compat/lifecycle/after-scope.ts +26 -26
- package/src/compat/lifecycle/after.ts +48 -45
- package/src/compat/lifecycle/error-funnel.ts +98 -102
- package/src/compat/lifecycle/error-serialize.ts +30 -26
- package/src/compat/lifecycle/error-ui.ts +67 -32
- package/src/compat/lifecycle/instrumentation-client.ts +36 -38
- package/src/compat/lifecycle/instrumentation.ts +85 -85
- package/src/compat/lifecycle/node-console.ts +11 -11
- package/src/compat/lifecycle/testmode.ts +160 -132
- package/src/compat/mdx/compile.ts +60 -57
- package/src/compat/mdx/plugin.ts +11 -11
- package/src/compat/mdx/{next-mdx-stub.ts → stub.ts} +5 -5
- package/src/compat/{metadata-route-artifacts.ts → metadata-artifacts.ts} +195 -191
- package/src/compat/metadata.ts +75 -81
- package/src/compat/next/cache.ts +68 -69
- package/src/compat/next/canonical-url.ts +9 -9
- package/src/compat/next/client-cache.ts +19 -19
- package/src/compat/next/client-navigation.ts +112 -116
- package/src/compat/next/client-only.ts +1 -1
- package/src/compat/next/client-script.tsx +99 -93
- package/src/compat/next/client-server.ts +10 -10
- package/src/compat/next/config-loader.ts +176 -173
- package/src/compat/next/config.ts +7 -7
- package/src/compat/next/constants.cjs +6 -6
- package/src/compat/next/constants.ts +6 -6
- package/src/compat/next/custom-server.ts +26 -24
- package/src/compat/next/dist/client/components/app-router-headers.ts +21 -21
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +3 -4
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -1
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -1
- package/src/compat/next/dynamic.tsx +21 -18
- package/src/compat/next/error.tsx +52 -52
- package/src/compat/next/font/cache.ts +74 -74
- package/src/compat/next/font/google.ts +2 -2
- package/src/compat/next/font/index.ts +1 -1
- package/src/compat/next/font/local.ts +3 -3
- package/src/compat/next/font/runtime-client.ts +17 -15
- package/src/compat/next/font/runtime.ts +443 -408
- package/src/compat/next/font/shared.ts +120 -108
- package/src/compat/next/form.tsx +63 -63
- package/src/compat/next/head.tsx +2 -2
- package/src/compat/next/headers.ts +103 -95
- package/src/compat/next/image/client.tsx +220 -0
- package/src/compat/next/image/config.ts +56 -58
- package/src/compat/next/image/optimizer.ts +40 -36
- package/src/compat/next/image/patterns.ts +37 -40
- package/src/compat/next/{image-props.ts → image/props.ts} +208 -202
- package/src/compat/next/image/shared.ts +65 -57
- package/src/compat/next/image/static-metadata.ts +98 -107
- package/src/compat/next/image/validate.ts +79 -88
- package/src/compat/next/image.tsx +19 -23
- package/src/compat/next/index.ts +1 -1
- package/src/compat/next/legacy-image.tsx +59 -60
- package/src/compat/next/{link-validation-transform.ts → link-transform.ts} +95 -96
- package/src/compat/next/link.tsx +158 -158
- package/src/compat/next/navigation.cjs +12 -3
- package/src/compat/next/navigation.ts +48 -50
- package/src/compat/next/offline.ts +27 -27
- package/src/compat/next/og.ts +121 -124
- package/src/compat/next/preferred-region.ts +13 -14
- package/src/compat/next/redirects.ts +58 -56
- package/src/compat/next/resource-hints.ts +73 -76
- package/src/compat/next/rewrites.ts +130 -133
- package/src/compat/next/root-params.ts +45 -45
- package/src/compat/next/{optimistic-route-state.ts → route-state.ts} +52 -52
- package/src/compat/next/router.cjs +4 -2
- package/src/compat/next/router.ts +58 -61
- package/src/compat/next/script.tsx +108 -108
- package/src/compat/next/server-only.ts +1 -1
- package/src/compat/next/server.ts +19 -19
- package/src/compat/next/svgr.ts +18 -17
- package/src/compat/next/telemetry.ts +24 -24
- package/src/compat/next/{image-usage.ts → usage.ts} +70 -39
- package/src/compat/next/user-agent.ts +53 -49
- package/src/compat/next/web-vitals.ts +22 -24
- package/src/compat/otel/api.ts +41 -41
- package/src/compat/otel/client-trace-metadata.ts +25 -27
- package/src/compat/otel/fetch-span.ts +29 -29
- package/src/compat/otel/tracer.ts +331 -331
- package/src/compat/pages/api.ts +456 -0
- package/src/compat/pages/client-plugin.ts +36 -36
- package/src/compat/pages/router-state.ts +34 -34
- package/src/compat/pages/{index.ts → router.ts} +130 -135
- package/src/compat/ppr/io.ts +12 -12
- package/src/compat/ppr/missing-root-params.ts +34 -38
- package/src/compat/ppr/root-params-scan.ts +66 -66
- package/src/compat/ppr/root-params-transform.ts +24 -26
- package/src/compat/ppr/root-params.ts +30 -30
- package/src/compat/ppr/segment-config-incompat.ts +6 -7
- package/src/compat/protocol.ts +71 -70
- package/src/compat/react/action-state.ts +47 -48
- package/src/compat/react/client-lite.ts +15 -15
- package/src/compat/react/client.ts +4 -4
- package/src/compat/react/compiler-runtime.ts +11 -11
- package/src/compat/react/dom-client.ts +44 -44
- package/src/compat/react/dom-react-server.ts +10 -16
- package/src/compat/react/dom-server.ts +10 -10
- package/src/compat/react/dom.ts +52 -52
- package/src/compat/react/hooks-extra.ts +34 -35
- package/src/compat/react/parity.ts +64 -61
- package/src/compat/react/preact.ts +81 -82
- package/src/compat/react/react-server.ts +28 -28
- package/src/compat/react/router-shim.ts +1 -1
- package/src/compat/react/server-component-use.ts +8 -8
- package/src/compat/react/server-inserted-html.ts +30 -31
- package/src/compat/react/server.ts +53 -55
- package/src/compat/react/use.ts +32 -32
- package/src/compat/react/view-transition.ts +20 -20
- package/src/compat/register/actions.ts +35 -824
- package/src/compat/register/boot.ts +41 -41
- package/src/compat/register/build-tier.ts +5 -5
- package/src/compat/register/build.ts +74 -70
- package/src/compat/register/bundler.ts +161 -156
- package/src/compat/register/cache.ts +20 -20
- package/src/compat/register/client-errors.ts +3 -3
- package/src/compat/register/config.ts +6 -6
- package/src/compat/register/css-extras.ts +30 -34
- package/src/compat/register/edge-runtime.ts +3 -3
- package/src/compat/register/errors.ts +10 -12
- package/src/compat/register/export.ts +16 -16
- package/src/compat/register/font.ts +11 -11
- package/src/compat/register/hooks.ts +2 -2
- package/src/compat/register/image.ts +40 -40
- package/src/compat/register/index.ts +59 -62
- package/src/compat/register/instrumentation-client.ts +10 -10
- package/src/compat/register/lifecycle.ts +25 -28
- package/src/compat/register/mdx.ts +10 -10
- package/src/compat/register/middleware.ts +226 -16
- package/src/compat/register/otel.ts +80 -88
- package/src/compat/register/pages-api.ts +10 -463
- package/src/compat/register/ppr.ts +16 -16
- package/src/compat/register/protocol.ts +17 -18
- package/src/compat/register/proxy.ts +49 -51
- package/src/compat/register/render.ts +79 -76
- package/src/compat/register/routing.ts +162 -159
- package/src/compat/register/segment.ts +24 -1897
- package/src/compat/register/static-image.ts +3 -3
- package/src/compat/register/{misc.ts → taint.ts} +9 -9
- package/src/compat/register/typed-routes.ts +14 -14
- package/src/compat/register/{usecache.ts → use-cache.ts} +39 -39
- package/src/compat/register/validation.ts +18 -18
- package/src/compat/segment/loading-boundary.ts +43 -45
- package/src/compat/segment/page-slot.ts +69 -69
- package/src/compat/segment/serve.ts +1884 -0
- package/src/compat/segment/tree.ts +113 -112
- package/src/compat/segment/vary-key.ts +38 -38
- package/src/compat/segment/vary-params.ts +138 -142
- package/src/compat/static-params.ts +14 -12
- package/src/compat/tsconfig-defaults.ts +87 -91
- package/src/compat/typecheck/{index.ts → check.ts} +234 -212
- package/src/compat/typecheck/worker.ts +15 -12
- package/src/compat/typed-routes/{index.ts → generate.ts} +36 -36
- package/src/compat/typed-routes/manifest.ts +174 -170
- package/src/compat/typed-routes/typegen.ts +127 -110
- package/src/compat/validation/errors.ts +16 -19
- package/src/compat/validation/prerender-diagnostics.ts +521 -504
- package/src/compat/validation/{index.ts → validate.ts} +647 -648
- package/src/compat-bootstrap.ts +16 -16
- package/src/config.ts +86 -69
- package/src/css/build.ts +230 -227
- package/src/css/postcss.ts +79 -80
- package/src/css/worker.ts +14 -15
- package/src/dev/client-actions.ts +10 -10
- package/src/dev/client-chunk-store.ts +27 -27
- package/src/dev/{client-key-cache.ts → restart/client-key.ts} +76 -76
- package/src/dev/{restart-cache.ts → restart/enabled.ts} +1 -1
- package/src/dev/{global-css-cache.ts → restart/global-css.ts} +91 -83
- package/src/dev/{node-module-bundle-cache.ts → restart/node-modules.ts} +24 -24
- package/src/dev/{route-bundle-key-cache.ts → restart/route-bundle-key.ts} +53 -53
- package/src/dev/{route-facts-cache.ts → restart/route-facts.ts} +82 -82
- package/src/dev/server.ts +804 -820
- package/src/env.ts +46 -43
- package/src/extensions.ts +491 -478
- package/src/index.ts +8 -8
- package/src/internal.ts +20 -23
- package/src/{islands → render}/boundary-error.ts +3 -3
- package/src/render/hooks.ts +71 -71
- package/src/render/island-context.ts +14 -14
- package/src/render/metadata.ts +310 -310
- package/src/{ppr-postpone.ts → render/postpone.ts} +5 -5
- package/src/{ppr.ts → render/ppr.ts} +244 -245
- package/src/render/renderer.ts +2087 -2082
- package/src/render/resource-hints.ts +16 -17
- package/src/render/slots.tsx +224 -235
- package/src/{islands → render}/static-children.ts +9 -12
- package/src/{islands → render}/static-slots.ts +37 -37
- package/src/{cache/context.ts → request/cache.ts} +20 -20
- package/src/request/context.ts +107 -107
- package/src/{dynamic/source.ts → resolve/dynamic.ts} +139 -139
- package/src/resolve/engine.ts +90 -77
- package/src/resolve/imports.ts +475 -463
- package/src/resolve/scan-facts.ts +318 -296
- package/src/resolve/source-text.ts +37 -37
- package/src/{dynamic → resolve}/tree-shake.ts +132 -128
- package/src/routing/forwarded.ts +19 -19
- package/src/routing/handler.ts +84 -91
- package/src/routing/href.ts +87 -70
- package/src/routing/{metadata.ts → metadata-files.ts} +403 -401
- package/src/{proxy.ts → routing/proxy.ts} +306 -312
- package/src/routing/{request-runtime.ts → request-environment.ts} +10 -10
- package/src/routing/routes.ts +827 -815
- package/src/routing/slots.ts +164 -160
- package/src/runtime/loader.ts +954 -0
- package/src/{dev → runtime}/module-cache.ts +309 -287
- package/src/{dev → runtime}/module-generations.ts +9 -9
- package/src/{dev → runtime}/module-transform.ts +81 -72
- package/src/{dev/imports.ts → runtime/modules.ts} +934 -847
- package/src/runtime/{server.ts → vendor-build.ts} +848 -1696
- package/src/runtime/vendor.ts +428 -406
- package/src/styles.d.ts +9 -0
- package/src/types.ts +324 -335
- package/src/utils/ansi.ts +5 -5
- package/src/utils/{source.ts → code.ts} +15 -12
- package/src/utils/content-type.ts +3 -3
- package/src/utils/decode.ts +2 -2
- package/src/utils/dev-profile.ts +13 -13
- package/src/utils/error-log.ts +6 -6
- package/src/utils/esbuild.ts +18 -18
- package/src/utils/fs-cache.ts +13 -13
- package/src/utils/fs.ts +57 -49
- package/src/utils/html.ts +20 -24
- package/src/utils/native-require.ts +8 -8
- package/src/utils/serialize.ts +139 -146
- package/src/utils/verbose.ts +18 -18
- package/reference/overview.md +0 -35
- package/src/cli/analyze-print.ts +0 -181
- package/src/compat/middleware/manifest.ts +0 -210
- package/src/compat/next/image-client.tsx +0 -215
- package/src/compat/next/link-usage.ts +0 -29
- package/src/css/index.ts +0 -2
- package/src/render/index.ts +0 -1
- package/src/style-modules.d.ts +0 -9
|
@@ -1,30 +1,29 @@
|
|
|
1
|
-
import { builtinModules } from 'node:module'
|
|
2
|
-
import os from 'node:os'
|
|
3
|
-
import path from 'node:path'
|
|
4
|
-
import { existsSync, readFileSync, type Dirent } from 'node:fs'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
import { copyFile, mkdir, readdir, symlink, writeFile } from 'node:fs/promises'
|
|
7
|
-
import type { OnResolveResult, Plugin } from 'esbuild'
|
|
8
|
-
import { build } from '../utils/esbuild'
|
|
9
|
-
import { drainPreplanBuilds, vendorTraceEnabled, vendorTraceRow } from '../runtime/vendor'
|
|
1
|
+
import { builtinModules } from 'node:module'
|
|
2
|
+
import os from 'node:os'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { existsSync, readFileSync, type Dirent } from 'node:fs'
|
|
5
|
+
import { fileURLToPath } from 'node:url'
|
|
6
|
+
import { copyFile, mkdir, readdir, symlink, writeFile } from 'node:fs/promises'
|
|
7
|
+
import type { OnResolveResult, Plugin } from 'esbuild'
|
|
8
|
+
import { build } from '../utils/esbuild'
|
|
9
|
+
import { drainPreplanBuilds, vendorTraceEnabled, vendorTraceRow } from '../runtime/vendor'
|
|
10
10
|
import {
|
|
11
11
|
clientReferenceExportNames,
|
|
12
12
|
clientReferenceModuleSource,
|
|
13
13
|
hasUseClientDirective,
|
|
14
|
-
} from '../client/reference-stub'
|
|
15
|
-
import { getClientActionBundler } from '
|
|
16
|
-
import { deferredDynamicImportSpecifiers, devDynamicSplitEnabled } from '../dynamic
|
|
17
|
-
import { noteModuleGeneration, noteModuleImported } from './module-generations'
|
|
14
|
+
} from '../client/reference-stub'
|
|
15
|
+
import { getClientActionBundler } from '../dev/client-actions'
|
|
16
|
+
import { deferredDynamicImportSpecifiers, devDynamicSplitEnabled } from '../resolve/dynamic'
|
|
17
|
+
import { noteModuleGeneration, noteModuleImported } from './module-generations'
|
|
18
18
|
import {
|
|
19
|
-
externalServerPackageHref,
|
|
20
19
|
rewriteServerSource,
|
|
21
|
-
runtimeServerImportTarget,
|
|
22
20
|
serverBundleTargetForRuntime,
|
|
23
21
|
serverBundleRequireConditions,
|
|
24
22
|
type ServerBundleTarget,
|
|
25
|
-
} from '
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
23
|
+
} from './loader'
|
|
24
|
+
import { externalServerPackageHref, runtimeServerImportTarget } from '../runtime/vendor-build'
|
|
25
|
+
import { writeFileAtomic } from '../utils/fs'
|
|
26
|
+
import { createHash } from 'node:crypto'
|
|
28
27
|
import {
|
|
29
28
|
extraLoadableExtensions,
|
|
30
29
|
getAssetExtensions,
|
|
@@ -32,15 +31,15 @@ import {
|
|
|
32
31
|
getCssExtensions,
|
|
33
32
|
getImportAliasExtensions,
|
|
34
33
|
serverDefineOptions,
|
|
35
|
-
} from '../extensions'
|
|
34
|
+
} from '../extensions'
|
|
36
35
|
import {
|
|
37
36
|
frameworkRuntimeAliasEntries,
|
|
38
37
|
pathToFileHref,
|
|
39
38
|
pnextAliases,
|
|
40
39
|
type CompatAliasTarget,
|
|
41
40
|
type ResolvedConfig,
|
|
42
|
-
} from '../config'
|
|
43
|
-
import { readText, toPosixPath } from '../utils/fs'
|
|
41
|
+
} from '../config'
|
|
42
|
+
import { readText, toPosixPath } from '../utils/fs'
|
|
44
43
|
import {
|
|
45
44
|
type ExternalLoadTarget,
|
|
46
45
|
externalPackageImportTarget,
|
|
@@ -50,8 +49,8 @@ import {
|
|
|
50
49
|
resolveImport,
|
|
51
50
|
resolveExternalLoadTarget,
|
|
52
51
|
resolvePackageSpecifier,
|
|
53
|
-
} from '../resolve/imports'
|
|
54
|
-
import { importSpecifiers, rewriteSpecifierLiterals } from '../resolve/scan-facts'
|
|
52
|
+
} from '../resolve/imports'
|
|
53
|
+
import { importSpecifiers, rewriteSpecifierLiterals } from '../resolve/scan-facts'
|
|
55
54
|
import {
|
|
56
55
|
cacheRoot,
|
|
57
56
|
devArtifactUsable,
|
|
@@ -60,27 +59,28 @@ import {
|
|
|
60
59
|
devSourceIdentity,
|
|
61
60
|
noteDevArtifactWritten,
|
|
62
61
|
type DevModuleCache,
|
|
63
|
-
} from './module-cache'
|
|
64
|
-
import { cachedRouteBundlePath, saveRouteBundlePath } from '
|
|
62
|
+
} from './module-cache'
|
|
63
|
+
import { cachedRouteBundlePath, saveRouteBundlePath } from '../dev/restart/route-bundle-key'
|
|
65
64
|
import {
|
|
66
65
|
outputSpecifiers,
|
|
67
66
|
spliceSource,
|
|
68
67
|
transformBailReason,
|
|
69
68
|
transformServerModule,
|
|
70
69
|
type SpecifierKind,
|
|
71
|
-
} from './module-transform'
|
|
72
|
-
import { findShakeableDynamicImports } from '../
|
|
73
|
-
import type { RouteManifestEntry } from '../types'
|
|
74
|
-
import { uniqueIdentifier } from '../utils/
|
|
70
|
+
} from './module-transform'
|
|
71
|
+
import { findShakeableDynamicImports } from '../resolve/tree-shake'
|
|
72
|
+
import type { RouteManifestEntry } from '../types'
|
|
73
|
+
import { uniqueIdentifier } from '../utils/code'
|
|
74
|
+
import { formatDuration } from '../utils/verbose'
|
|
75
75
|
|
|
76
|
-
type AliasMap = Record<string, string
|
|
76
|
+
type AliasMap = Record<string, string>
|
|
77
77
|
|
|
78
78
|
function nextCompatEnabled(config: ResolvedConfig) {
|
|
79
|
-
return Boolean(config.compat?.next)
|
|
79
|
+
return Boolean(config.compat?.next)
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
function reactCompatEnabled(config: ResolvedConfig) {
|
|
83
|
-
return Boolean(config.compat?.next || config.compat?.react || config.compat?.reactCompiler)
|
|
83
|
+
return Boolean(config.compat?.next || config.compat?.react || config.compat?.reactCompiler)
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function coreAliases(config: ResolvedConfig, target: CompatAliasTarget): AliasMap {
|
|
@@ -88,67 +88,73 @@ function coreAliases(config: ResolvedConfig, target: CompatAliasTarget): AliasMa
|
|
|
88
88
|
...frameworkRuntimeAliasEntries(),
|
|
89
89
|
...pnextAliases(target),
|
|
90
90
|
...getImportAliasExtensions().aliases(config, target),
|
|
91
|
-
}
|
|
91
|
+
}
|
|
92
92
|
}
|
|
93
93
|
|
|
94
94
|
// Cap concurrent esbuild builds: the recursive module graph can otherwise fire
|
|
95
95
|
// hundreds of builds at once and saturate the host.
|
|
96
|
-
const BUILD_CONCURRENCY = Math.max(2, (os.availableParallelism?.() ?? os.cpus().length) - 1)
|
|
97
|
-
let buildActive = 0
|
|
98
|
-
const buildQueue: (() => void)[] = []
|
|
99
|
-
const moduleBuilds = new Map<string, Promise<string>>()
|
|
100
|
-
const routeBundleBuilds = new Map<string, Promise<string>>()
|
|
96
|
+
const BUILD_CONCURRENCY = Math.max(2, (os.availableParallelism?.() ?? os.cpus().length) - 1)
|
|
97
|
+
let buildActive = 0
|
|
98
|
+
const buildQueue: (() => void)[] = []
|
|
99
|
+
const moduleBuilds = new Map<string, Promise<string>>()
|
|
100
|
+
const routeBundleBuilds = new Map<string, Promise<string>>()
|
|
101
101
|
|
|
102
102
|
// PNEXT_DEV_MODULE_STATS=1: attribute every per-module compile to its profile,
|
|
103
103
|
// so cross-profile duplicate work is countable rather than inferred. Counts are
|
|
104
104
|
// load-insensitive, which is what makes them usable on a contended box.
|
|
105
|
-
interface ModuleStat {
|
|
105
|
+
interface ModuleStat {
|
|
106
|
+
profile: string
|
|
107
|
+
ms: number
|
|
108
|
+
esbuild: boolean
|
|
109
|
+
}
|
|
106
110
|
// `${profile}:${file}` of modules that fell off the oxc fast path onto esbuild.
|
|
107
|
-
const esbuildFallbacks = new Set<string>()
|
|
111
|
+
const esbuildFallbacks = new Set<string>()
|
|
108
112
|
const moduleStats: Map<string, ModuleStat[]> | undefined =
|
|
109
113
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
110
|
-
process.env.PNEXT_DEV_MODULE_STATS === '1' ? new Map() : undefined
|
|
111
|
-
let moduleStatsFlush: Timer | undefined
|
|
114
|
+
process.env.PNEXT_DEV_MODULE_STATS === '1' ? new Map() : undefined
|
|
115
|
+
let moduleStatsFlush: Timer | undefined
|
|
112
116
|
|
|
113
117
|
function noteModuleCompile(file: string, profile: string, ms: number, esbuild: boolean) {
|
|
114
|
-
if (!moduleStats) return
|
|
115
|
-
const entries = moduleStats.get(file) ?? []
|
|
116
|
-
entries.push({ profile, ms, esbuild })
|
|
117
|
-
moduleStats.set(file, entries)
|
|
118
|
+
if (!moduleStats) return
|
|
119
|
+
const entries = moduleStats.get(file) ?? []
|
|
120
|
+
entries.push({ profile, ms, esbuild })
|
|
121
|
+
moduleStats.set(file, entries)
|
|
118
122
|
// Debounced: stages settle after the response, so a fixed point in the
|
|
119
123
|
// request would miss the preloads this is meant to attribute.
|
|
120
|
-
clearTimeout(moduleStatsFlush)
|
|
121
|
-
moduleStatsFlush = setTimeout(reportModuleStats, 500)
|
|
122
|
-
moduleStatsFlush.unref?.()
|
|
124
|
+
clearTimeout(moduleStatsFlush)
|
|
125
|
+
moduleStatsFlush = setTimeout(reportModuleStats, 500)
|
|
126
|
+
moduleStatsFlush.unref?.()
|
|
123
127
|
}
|
|
124
128
|
|
|
125
129
|
function reportModuleStats() {
|
|
126
|
-
if (!moduleStats) return
|
|
127
|
-
const byProfile = new Map<string, { files: number; ms: number; esbuild: number }>()
|
|
128
|
-
let total = 0
|
|
129
|
-
let duplicated = 0
|
|
130
|
-
let duplicateCompiles = 0
|
|
131
|
-
let duplicateMs = 0
|
|
130
|
+
if (!moduleStats) return
|
|
131
|
+
const byProfile = new Map<string, { files: number; ms: number; esbuild: number }>()
|
|
132
|
+
let total = 0
|
|
133
|
+
let duplicated = 0
|
|
134
|
+
let duplicateCompiles = 0
|
|
135
|
+
let duplicateMs = 0
|
|
132
136
|
for (const entries of moduleStats.values()) {
|
|
133
|
-
total += entries.length
|
|
134
|
-
const profiles = new Set(entries.map(entry => entry.profile))
|
|
137
|
+
total += entries.length
|
|
138
|
+
const profiles = new Set(entries.map(entry => entry.profile))
|
|
135
139
|
if (profiles.size > 1) {
|
|
136
|
-
duplicated += 1
|
|
137
|
-
duplicateCompiles += entries.length - 1
|
|
138
|
-
duplicateMs += entries.slice(1).reduce((sum, entry) => sum + entry.ms, 0)
|
|
140
|
+
duplicated += 1
|
|
141
|
+
duplicateCompiles += entries.length - 1
|
|
142
|
+
duplicateMs += entries.slice(1).reduce((sum, entry) => sum + entry.ms, 0)
|
|
139
143
|
}
|
|
140
144
|
for (const entry of entries) {
|
|
141
|
-
const bucket = byProfile.get(entry.profile) ?? { files: 0, ms: 0, esbuild: 0 }
|
|
142
|
-
bucket.files += 1
|
|
143
|
-
bucket.ms += entry.ms
|
|
144
|
-
bucket.esbuild += entry.esbuild ? 1 : 0
|
|
145
|
-
byProfile.set(entry.profile, bucket)
|
|
145
|
+
const bucket = byProfile.get(entry.profile) ?? { files: 0, ms: 0, esbuild: 0 }
|
|
146
|
+
bucket.files += 1
|
|
147
|
+
bucket.ms += entry.ms
|
|
148
|
+
bucket.esbuild += entry.esbuild ? 1 : 0
|
|
149
|
+
byProfile.set(entry.profile, bucket)
|
|
146
150
|
}
|
|
147
151
|
}
|
|
148
152
|
const rows = [...byProfile.entries()]
|
|
149
153
|
.sort((a, b) => b[1].ms - a[1].ms)
|
|
150
|
-
.map(
|
|
151
|
-
|
|
154
|
+
.map(
|
|
155
|
+
([profile, b]) =>
|
|
156
|
+
` ${profile}: ${b.files} modules, ${b.ms.toFixed(0)} ms nested-wall, ${b.esbuild} via esbuild`,
|
|
157
|
+
)
|
|
152
158
|
console.error(
|
|
153
159
|
[
|
|
154
160
|
`dev-module-stats ${moduleStats.size} distinct files, ${total} compiles`,
|
|
@@ -156,63 +162,64 @@ function reportModuleStats() {
|
|
|
156
162
|
` cross-profile: ${duplicated} files compiled in >1 profile, ` +
|
|
157
163
|
`${duplicateCompiles} redundant compiles, ${duplicateMs.toFixed(0)} ms nested-wall`,
|
|
158
164
|
].join('\n'),
|
|
159
|
-
)
|
|
165
|
+
)
|
|
160
166
|
// PNEXT_DEV_MODULE_STATS_OUT=<path>: also dump the per-file list, so a floor
|
|
161
167
|
// bench can replay the exact module set a route compiles in each profile.
|
|
162
168
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
163
|
-
const out = process.env.PNEXT_DEV_MODULE_STATS_OUT
|
|
164
|
-
if (!out) return
|
|
169
|
+
const out = process.env.PNEXT_DEV_MODULE_STATS_OUT
|
|
170
|
+
if (!out) return
|
|
165
171
|
const files = [...moduleStats.entries()].flatMap(([file, entries]) =>
|
|
166
|
-
entries.map(entry => ({ file, profile: entry.profile, ms: entry.ms, esbuild: entry.esbuild }))
|
|
167
|
-
|
|
172
|
+
entries.map(entry => ({ file, profile: entry.profile, ms: entry.ms, esbuild: entry.esbuild })),
|
|
173
|
+
)
|
|
174
|
+
void writeFile(out, JSON.stringify(files, null, 0)).catch(() => undefined)
|
|
168
175
|
}
|
|
169
|
-
const routeModuleLoaders = new Map<string, Promise<DevRouteModuleLoaders>>()
|
|
176
|
+
const routeModuleLoaders = new Map<string, Promise<DevRouteModuleLoaders>>()
|
|
170
177
|
|
|
171
178
|
// A module that throws while evaluating stays failed in Bun's registry for the process lifetime, and
|
|
172
179
|
// re-importing it surfaces downstream symptoms (a missing bundle entry, TDZ on an export) instead of
|
|
173
180
|
// the original error. Record the first failure per compiled href and re-throw THAT on every later
|
|
174
181
|
// request. Compiled hrefs are content-addressed, so a save that fixes the module yields a new href
|
|
175
182
|
// and the entry is never consulted again.
|
|
176
|
-
const moduleEvalErrors = new Map<string, unknown>()
|
|
183
|
+
const moduleEvalErrors = new Map<string, unknown>()
|
|
177
184
|
|
|
178
185
|
/** Import a compiled dev module, re-throwing its first evaluation error on later imports. */
|
|
179
186
|
export async function importDevModule<T>(href: string): Promise<T> {
|
|
180
|
-
if (moduleEvalErrors.has(href)) throw moduleEvalErrors.get(href)
|
|
187
|
+
if (moduleEvalErrors.has(href)) throw moduleEvalErrors.get(href)
|
|
181
188
|
// Pre-planned vendor builds are enqueued without an awaiter; nothing
|
|
182
189
|
// evaluates until they all settle (no-op when the pipeline is empty).
|
|
183
|
-
await drainPreplanBuilds()
|
|
190
|
+
await drainPreplanBuilds()
|
|
184
191
|
try {
|
|
185
|
-
const module = (await import(href)) as T
|
|
186
|
-
noteModuleImported(href)
|
|
187
|
-
return module
|
|
192
|
+
const module = (await import(href)) as T
|
|
193
|
+
noteModuleImported(href)
|
|
194
|
+
return module
|
|
188
195
|
} catch (error) {
|
|
189
|
-
moduleEvalErrors.set(href, error)
|
|
190
|
-
throw error
|
|
196
|
+
moduleEvalErrors.set(href, error)
|
|
197
|
+
throw error
|
|
191
198
|
}
|
|
192
199
|
}
|
|
193
|
-
const extensionlessDynamicImportExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts'])
|
|
200
|
+
const extensionlessDynamicImportExtensions = new Set(['.ts', '.tsx', '.js', '.jsx', '.mts', '.cts'])
|
|
194
201
|
const externalLoadNamespaces = {
|
|
195
202
|
server: 'pnext-route-external-server',
|
|
196
203
|
client: 'pnext-route-external-client',
|
|
197
204
|
module: 'pnext-module-external',
|
|
198
|
-
} as const
|
|
205
|
+
} as const
|
|
199
206
|
|
|
200
|
-
const moduleCaches = new WeakMap<ResolvedConfig, DevModuleCache>()
|
|
207
|
+
const moduleCaches = new WeakMap<ResolvedConfig, DevModuleCache>()
|
|
201
208
|
|
|
202
209
|
/**
|
|
203
|
-
* The content-addressing cache behind every compiled path (
|
|
210
|
+
* The content-addressing cache behind every compiled path (runtime/module-cache.ts).
|
|
204
211
|
* Artifacts are named by a hash of their whole source graph, so this is also the
|
|
205
212
|
* thing that decides what a save invalidates.
|
|
206
213
|
*/
|
|
207
214
|
export function devModuleGraph(config: ResolvedConfig): DevModuleCache {
|
|
208
|
-
const existing = moduleCaches.get(config)
|
|
209
|
-
if (existing) return existing
|
|
215
|
+
const existing = moduleCaches.get(config)
|
|
216
|
+
if (existing) return existing
|
|
210
217
|
const cache = devModuleCache(config, {
|
|
211
218
|
compileKey: devCompileKey(config),
|
|
212
219
|
edges: (file, source) => graphEdges(config, file, source),
|
|
213
|
-
})
|
|
214
|
-
moduleCaches.set(config, cache)
|
|
215
|
-
return cache
|
|
220
|
+
})
|
|
221
|
+
moduleCaches.set(config, cache)
|
|
222
|
+
return cache
|
|
216
223
|
}
|
|
217
224
|
|
|
218
225
|
/**
|
|
@@ -225,18 +232,22 @@ export function devModuleGraph(config: ResolvedConfig): DevModuleCache {
|
|
|
225
232
|
* registers, and it is memoized from the first read.
|
|
226
233
|
*/
|
|
227
234
|
export async function warmDevModulePipeline(config: ResolvedConfig) {
|
|
228
|
-
const source = 'import "./pnext-warm-import";\nexport const warm: number = 1;\n'
|
|
229
|
-
const file = path.join(config.root, 'pnext-warm.ts')
|
|
235
|
+
const source = 'import "./pnext-warm-import";\nexport const warm: number = 1;\n'
|
|
236
|
+
const file = path.join(config.root, 'pnext-warm.ts')
|
|
230
237
|
await Promise.allSettled([
|
|
231
238
|
// esbuild spawns its Go service child and handshakes on the first build.
|
|
232
|
-
build({
|
|
239
|
+
build({
|
|
240
|
+
stdin: { contents: '0', loader: 'ts', resolveDir: config.root },
|
|
241
|
+
write: false,
|
|
242
|
+
logLevel: 'silent',
|
|
243
|
+
}),
|
|
233
244
|
Promise.resolve().then(() => {
|
|
234
|
-
importSpecifiers(source, file)
|
|
235
|
-
transformServerModule(source, file, serverDefineOptions().define)
|
|
236
|
-
resolveLocalImport(config, file, './pnext-warm-import')
|
|
237
|
-
devModuleGraph(config)
|
|
245
|
+
importSpecifiers(source, file) // oxc-parser
|
|
246
|
+
transformServerModule(source, file, serverDefineOptions().define) // oxc-transform
|
|
247
|
+
resolveLocalImport(config, file, './pnext-warm-import') // oxc-resolver factory
|
|
248
|
+
devModuleGraph(config) // cache marker + graph.json + framework fingerprint
|
|
238
249
|
}),
|
|
239
|
-
])
|
|
250
|
+
])
|
|
240
251
|
}
|
|
241
252
|
|
|
242
253
|
/** Compile inputs that are not sources: a change to any of them renames everything. */
|
|
@@ -249,7 +260,7 @@ function devCompileKey(config: ResolvedConfig) {
|
|
|
249
260
|
nextConfig: nextConfigFingerprint(config),
|
|
250
261
|
define: serverDefineOptions().define ?? null,
|
|
251
262
|
aliases: [...devAliasKeys(config)].sort(),
|
|
252
|
-
})
|
|
263
|
+
})
|
|
253
264
|
}
|
|
254
265
|
|
|
255
266
|
// next.config knobs (modularizeImports, resolve aliases, compiler options) shape compiled output, and only
|
|
@@ -263,35 +274,35 @@ const NEXT_CONFIG_BASENAMES = [
|
|
|
263
274
|
'next.config.js',
|
|
264
275
|
'next.config.mjs',
|
|
265
276
|
'next.config.cjs',
|
|
266
|
-
]
|
|
277
|
+
]
|
|
267
278
|
|
|
268
279
|
function nextConfigFingerprint(config: ResolvedConfig) {
|
|
269
|
-
if (!config.compat?.next) return ''
|
|
280
|
+
if (!config.compat?.next) return ''
|
|
270
281
|
for (const name of NEXT_CONFIG_BASENAMES) {
|
|
271
|
-
const candidate = path.join(config.root, name)
|
|
272
|
-
if (!existsSync(candidate)) continue
|
|
282
|
+
const candidate = path.join(config.root, name)
|
|
283
|
+
if (!existsSync(candidate)) continue
|
|
273
284
|
try {
|
|
274
|
-
return `${name}:${Bun.hash(readFileSync(candidate)).toString(36)}
|
|
285
|
+
return `${name}:${Bun.hash(readFileSync(candidate)).toString(36)}`
|
|
275
286
|
} catch {
|
|
276
|
-
return name
|
|
287
|
+
return name
|
|
277
288
|
}
|
|
278
289
|
}
|
|
279
|
-
return ''
|
|
290
|
+
return ''
|
|
280
291
|
}
|
|
281
292
|
|
|
282
|
-
const aliasKeys = new WeakMap<ResolvedConfig, Set<string>>()
|
|
293
|
+
const aliasKeys = new WeakMap<ResolvedConfig, Set<string>>()
|
|
283
294
|
|
|
284
295
|
/** Specifiers every profile rewrites at build time — never source edges. */
|
|
285
296
|
function devAliasKeys(config: ResolvedConfig) {
|
|
286
|
-
const existing = aliasKeys.get(config)
|
|
287
|
-
if (existing) return existing
|
|
297
|
+
const existing = aliasKeys.get(config)
|
|
298
|
+
if (existing) return existing
|
|
288
299
|
const keys = new Set([
|
|
289
300
|
...Object.keys(coreAliases(config, 'server')),
|
|
290
301
|
...Object.keys(getImportAliasExtensions().reactServerLayerAliases(config)),
|
|
291
302
|
...Object.keys(clientSsrAliases(config)),
|
|
292
|
-
])
|
|
293
|
-
aliasKeys.set(config, keys)
|
|
294
|
-
return keys
|
|
303
|
+
])
|
|
304
|
+
aliasKeys.set(config, keys)
|
|
305
|
+
return keys
|
|
295
306
|
}
|
|
296
307
|
|
|
297
308
|
/**
|
|
@@ -300,45 +311,47 @@ function devAliasKeys(config: ResolvedConfig) {
|
|
|
300
311
|
* its content, so assets and client components are edges here even where the compile walk skips them.
|
|
301
312
|
*/
|
|
302
313
|
function graphEdges(config: ResolvedConfig, file: string, source: string) {
|
|
303
|
-
const aliases = devAliasKeys(config)
|
|
304
|
-
const imports: string[] = []
|
|
305
|
-
const packages: string[] = []
|
|
314
|
+
const aliases = devAliasKeys(config)
|
|
315
|
+
const imports: string[] = []
|
|
316
|
+
const packages: string[] = []
|
|
306
317
|
for (const specifier of importSpecifiers(source, file)) {
|
|
307
|
-
const { sourcePath } = splitHash(specifier)
|
|
308
|
-
if (aliases.has(specifier) || aliases.has(sourcePath)) continue
|
|
309
|
-
const resolved = resolveLocalImport(config, file, sourcePath.replace(/\?.*$/, ''))
|
|
310
|
-
const isLocalSource = sourcePath.startsWith('.') || path.isAbsolute(sourcePath)
|
|
318
|
+
const { sourcePath } = splitHash(specifier)
|
|
319
|
+
if (aliases.has(specifier) || aliases.has(sourcePath)) continue
|
|
320
|
+
const resolved = resolveLocalImport(config, file, sourcePath.replace(/\?.*$/, ''))
|
|
321
|
+
const isLocalSource = sourcePath.startsWith('.') || path.isAbsolute(sourcePath)
|
|
311
322
|
// Compiled output is never a source of the graph that produced it.
|
|
312
|
-
if (resolved && isInside(config.outPath, resolved)) continue
|
|
323
|
+
if (resolved && isInside(config.outPath, resolved)) continue
|
|
313
324
|
if (resolved && (isLocalSource || isInside(config.workspaceRoot, resolved))) {
|
|
314
|
-
imports.push(path.resolve(resolved))
|
|
315
|
-
continue
|
|
325
|
+
imports.push(path.resolve(resolved))
|
|
326
|
+
continue
|
|
316
327
|
}
|
|
317
328
|
// Registry package demand: recorded here so a route's vendor bundles can be
|
|
318
329
|
// started as their own stage instead of blocking the module pass at first
|
|
319
330
|
// use.
|
|
320
|
-
if (!isLocalSource && isPackageSpecifier(sourcePath)) packages.push(sourcePath)
|
|
331
|
+
if (!isLocalSource && isPackageSpecifier(sourcePath)) packages.push(sourcePath)
|
|
321
332
|
}
|
|
322
|
-
return { imports, packages, client: hasUseClientDirective(source) }
|
|
333
|
+
return { imports, packages, client: hasUseClientDirective(source) }
|
|
323
334
|
}
|
|
324
335
|
|
|
325
336
|
function withBuildSlot<T>(task: () => Promise<T>): Promise<T> {
|
|
326
337
|
return new Promise<T>((resolve, reject) => {
|
|
327
338
|
const run = () => {
|
|
328
|
-
buildActive += 1
|
|
329
|
-
task()
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
339
|
+
buildActive += 1
|
|
340
|
+
task()
|
|
341
|
+
.then(resolve, reject)
|
|
342
|
+
.finally(() => {
|
|
343
|
+
buildActive -= 1
|
|
344
|
+
buildQueue.shift()?.()
|
|
345
|
+
})
|
|
346
|
+
}
|
|
347
|
+
if (buildActive < BUILD_CONCURRENCY) run()
|
|
348
|
+
else buildQueue.push(run)
|
|
349
|
+
})
|
|
337
350
|
}
|
|
338
351
|
|
|
339
352
|
export interface DevServerModuleOptions {
|
|
340
|
-
conditionTarget?: ServerBundleTarget
|
|
341
|
-
externalLoadTarget?: ExternalLoadTarget
|
|
353
|
+
conditionTarget?: ServerBundleTarget
|
|
354
|
+
externalLoadTarget?: ExternalLoadTarget
|
|
342
355
|
/**
|
|
343
356
|
* True for modules compiled under the true `react-server` layer (App RSC,
|
|
344
357
|
* route handlers, proxy/middleware): layers stricter React alias overrides
|
|
@@ -346,7 +359,7 @@ export interface DevServerModuleOptions {
|
|
|
346
359
|
* Pages/api and other server modules keep the base aliases (full-hooks
|
|
347
360
|
* `react`, kept for backward compatibility).
|
|
348
361
|
*/
|
|
349
|
-
reactServerLayer?: boolean
|
|
362
|
+
reactServerLayer?: boolean
|
|
350
363
|
}
|
|
351
364
|
|
|
352
365
|
export async function devServerModuleHref(
|
|
@@ -361,29 +374,25 @@ export async function devServerModuleHref(
|
|
|
361
374
|
config,
|
|
362
375
|
file,
|
|
363
376
|
moduleOptions.conditionTarget ?? 'server',
|
|
364
|
-
)
|
|
377
|
+
)
|
|
365
378
|
const reactServerLayer =
|
|
366
|
-
moduleOptions.reactServerLayer ??
|
|
367
|
-
|
|
368
|
-
const release = devModuleGraph(config).hold();
|
|
379
|
+
moduleOptions.reactServerLayer ?? (conditionTarget === 'server' || conditionTarget === 'edge')
|
|
380
|
+
const release = devModuleGraph(config).hold()
|
|
369
381
|
const compiled = await writeDevModule(config, file, new Set(), {
|
|
370
382
|
aliases: {
|
|
371
383
|
...coreAliases(config, 'server'),
|
|
372
|
-
...(reactServerLayer
|
|
373
|
-
? getImportAliasExtensions().reactServerLayerAliases(config)
|
|
374
|
-
: {}),
|
|
384
|
+
...(reactServerLayer ? getImportAliasExtensions().reactServerLayerAliases(config) : {}),
|
|
375
385
|
},
|
|
376
386
|
profile: reactCompatEnabled(config) ? 'compat' : 'server',
|
|
377
387
|
conditionTarget,
|
|
378
388
|
reactServerLayer,
|
|
379
389
|
externalLoadTarget:
|
|
380
|
-
moduleOptions.externalLoadTarget ??
|
|
381
|
-
externalLoadTargetForConditionTarget(conditionTarget),
|
|
390
|
+
moduleOptions.externalLoadTarget ?? externalLoadTargetForConditionTarget(conditionTarget),
|
|
382
391
|
stubClientImports: reactCompatEnabled(config),
|
|
383
392
|
rewriteExternalServerImports: true,
|
|
384
393
|
bundleExternalPackages: reactCompatEnabled(config),
|
|
385
|
-
}).finally(release)
|
|
386
|
-
return pathToFileHref(compiled)
|
|
394
|
+
}).finally(release)
|
|
395
|
+
return pathToFileHref(compiled)
|
|
387
396
|
}
|
|
388
397
|
|
|
389
398
|
export async function devClientModuleHref(
|
|
@@ -392,17 +401,17 @@ export async function devClientModuleHref(
|
|
|
392
401
|
_version?: string,
|
|
393
402
|
serverTarget?: ServerBundleTarget,
|
|
394
403
|
) {
|
|
395
|
-
const conditionTarget = clientLayerConditionTarget(config, file, serverTarget ?? 'server')
|
|
396
|
-
const throughPackage = await packageClientModuleHref(config, file, conditionTarget)
|
|
397
|
-
if (throughPackage) return throughPackage
|
|
398
|
-
const release = devModuleGraph(config).hold()
|
|
404
|
+
const conditionTarget = clientLayerConditionTarget(config, file, serverTarget ?? 'server')
|
|
405
|
+
const throughPackage = await packageClientModuleHref(config, file, conditionTarget)
|
|
406
|
+
if (throughPackage) return throughPackage
|
|
407
|
+
const release = devModuleGraph(config).hold()
|
|
399
408
|
const compiled = await writeDevModule(
|
|
400
409
|
config,
|
|
401
410
|
file,
|
|
402
411
|
new Set(),
|
|
403
412
|
clientLayerOptions(config, conditionTarget),
|
|
404
|
-
).finally(release)
|
|
405
|
-
return pathToFileHref(compiled)
|
|
413
|
+
).finally(release)
|
|
414
|
+
return pathToFileHref(compiled)
|
|
406
415
|
}
|
|
407
416
|
|
|
408
417
|
/**
|
|
@@ -422,18 +431,18 @@ async function packageClientModuleHref(
|
|
|
422
431
|
file: string,
|
|
423
432
|
conditionTarget: ServerBundleTarget,
|
|
424
433
|
) {
|
|
425
|
-
if (!reactCompatEnabled(config)) return undefined
|
|
426
|
-
const packageDir = nodeModulesPackageDir(file)
|
|
427
|
-
if (!packageDir) return undefined
|
|
428
|
-
const specifier = await packageSpecifierPublishing(config, packageDir, file)
|
|
429
|
-
if (!specifier) return undefined
|
|
434
|
+
if (!reactCompatEnabled(config)) return undefined
|
|
435
|
+
const packageDir = nodeModulesPackageDir(file)
|
|
436
|
+
if (!packageDir) return undefined
|
|
437
|
+
const specifier = await packageSpecifierPublishing(config, packageDir, file)
|
|
438
|
+
if (!specifier) return undefined
|
|
430
439
|
return externalServerPackageHref(
|
|
431
440
|
config,
|
|
432
441
|
specifier,
|
|
433
442
|
'client',
|
|
434
443
|
path.dirname(file),
|
|
435
444
|
conditionTarget,
|
|
436
|
-
).catch(() => undefined)
|
|
445
|
+
).catch(() => undefined)
|
|
437
446
|
}
|
|
438
447
|
|
|
439
448
|
/**
|
|
@@ -446,70 +455,70 @@ async function packageSpecifierPublishing(
|
|
|
446
455
|
packageDir: string,
|
|
447
456
|
file: string,
|
|
448
457
|
) {
|
|
449
|
-
const packageName = packageNameOfDirectory(packageDir)
|
|
450
|
-
if (!packageName) return undefined
|
|
458
|
+
const packageName = packageNameOfDirectory(packageDir)
|
|
459
|
+
if (!packageName) return undefined
|
|
451
460
|
for (const subpath of packageExportSubpaths(packageDir)) {
|
|
452
|
-
const specifier = subpath === '.' ? packageName : `${packageName}/${subpath.slice(2)}
|
|
453
|
-
if (resolvePackageFile(config, file, specifier) === file) return specifier
|
|
461
|
+
const specifier = subpath === '.' ? packageName : `${packageName}/${subpath.slice(2)}`
|
|
462
|
+
if (resolvePackageFile(config, file, specifier) === file) return specifier
|
|
454
463
|
}
|
|
455
|
-
const entry = resolvePackageFile(config, file, packageName)
|
|
456
|
-
if (!entry || entry === file) return entry ? packageName : undefined
|
|
457
|
-
const published = new Set(await clientReferenceExportNames(config, entry))
|
|
458
|
-
const names = await clientReferenceExportNames(config, file)
|
|
459
|
-
return names.length > 0 && names.every(name => published.has(name)) ? packageName : undefined
|
|
464
|
+
const entry = resolvePackageFile(config, file, packageName)
|
|
465
|
+
if (!entry || entry === file) return entry ? packageName : undefined
|
|
466
|
+
const published = new Set(await clientReferenceExportNames(config, entry))
|
|
467
|
+
const names = await clientReferenceExportNames(config, file)
|
|
468
|
+
return names.length > 0 && names.every(name => published.has(name)) ? packageName : undefined
|
|
460
469
|
}
|
|
461
470
|
|
|
462
471
|
/** The client layer's own conditions, so the file matches the bundle's entry. */
|
|
463
472
|
function resolvePackageFile(config: ResolvedConfig, fromFile: string, specifier: string) {
|
|
464
|
-
return resolvePackageSpecifier(config.root, fromFile, specifier, ['module', 'import'])
|
|
473
|
+
return resolvePackageSpecifier(config.root, fromFile, specifier, ['module', 'import'])
|
|
465
474
|
}
|
|
466
475
|
|
|
467
476
|
/** Every `exports` key of a package manifest; `{}` and sugar forms included. */
|
|
468
477
|
function packageExportSubpaths(packageDir: string) {
|
|
469
478
|
try {
|
|
470
479
|
const manifest = JSON.parse(readFileSync(path.join(packageDir, 'package.json'), 'utf8')) as {
|
|
471
|
-
exports?: unknown
|
|
472
|
-
}
|
|
473
|
-
if (typeof manifest.exports !== 'object' || manifest.exports === null) return ['.']
|
|
474
|
-
const keys = Object.keys(manifest.exports).filter(key => key.startsWith('.'))
|
|
480
|
+
exports?: unknown
|
|
481
|
+
}
|
|
482
|
+
if (typeof manifest.exports !== 'object' || manifest.exports === null) return ['.']
|
|
483
|
+
const keys = Object.keys(manifest.exports).filter(key => key.startsWith('.'))
|
|
475
484
|
// A conditions-only map (`{ import: ... }`) has no subpath keys at all.
|
|
476
|
-
return keys.length > 0 ? keys.filter(key => !key.includes('*')) : ['.']
|
|
485
|
+
return keys.length > 0 ? keys.filter(key => !key.includes('*')) : ['.']
|
|
477
486
|
} catch {
|
|
478
|
-
return ['.']
|
|
487
|
+
return ['.']
|
|
479
488
|
}
|
|
480
489
|
}
|
|
481
490
|
|
|
482
491
|
/** `…/node_modules/@scope/name` -> `@scope/name`. */
|
|
483
492
|
function packageNameOfDirectory(packageDir: string) {
|
|
484
|
-
const parts = packageDir.split(path.sep)
|
|
485
|
-
const index = parts.lastIndexOf('node_modules')
|
|
486
|
-
if (index === -1) return undefined
|
|
487
|
-
return parts.slice(index + 1).join('/')
|
|
493
|
+
const parts = packageDir.split(path.sep)
|
|
494
|
+
const index = parts.lastIndexOf('node_modules')
|
|
495
|
+
if (index === -1) return undefined
|
|
496
|
+
return parts.slice(index + 1).join('/')
|
|
488
497
|
}
|
|
489
498
|
|
|
490
499
|
/** `…/node_modules/@scope/name/build/x.js` -> `…/node_modules/@scope/name`. */
|
|
491
500
|
function nodeModulesPackageDir(file: string) {
|
|
492
|
-
const parts = file.split(path.sep)
|
|
493
|
-
const index = parts.lastIndexOf('node_modules')
|
|
494
|
-
if (index === -1) return undefined
|
|
495
|
-
const end = index + (parts[index + 1]?.startsWith('@') ? 3 : 2)
|
|
496
|
-
return end <= parts.length ? parts.slice(0, end).join(path.sep) : undefined
|
|
501
|
+
const parts = file.split(path.sep)
|
|
502
|
+
const index = parts.lastIndexOf('node_modules')
|
|
503
|
+
if (index === -1) return undefined
|
|
504
|
+
const end = index + (parts[index + 1]?.startsWith('@') ? 3 : 2)
|
|
505
|
+
return end <= parts.length ? parts.slice(0, end).join(path.sep) : undefined
|
|
497
506
|
}
|
|
498
507
|
|
|
499
508
|
export interface DevRouteModuleLoaders {
|
|
500
|
-
moduleLoader: (file: string) => Promise<Record<string, unknown
|
|
501
|
-
clientModuleLoader: (file: string) => Promise<Record<string, unknown
|
|
509
|
+
moduleLoader: (file: string) => Promise<Record<string, unknown>>
|
|
510
|
+
clientModuleLoader: (file: string) => Promise<Record<string, unknown>>
|
|
502
511
|
}
|
|
503
512
|
|
|
504
513
|
interface DevModuleOptions {
|
|
505
|
-
aliases: AliasMap
|
|
506
|
-
profile: 'server' | 'compat' | 'client'
|
|
507
|
-
conditionTarget: ServerBundleTarget
|
|
508
|
-
reactServerLayer?: boolean
|
|
509
|
-
externalLoadTarget: ExternalLoadTarget
|
|
510
|
-
stubClientImports: boolean
|
|
511
|
-
rewriteExternalServerImports: boolean
|
|
512
|
-
bundleExternalPackages: boolean
|
|
514
|
+
aliases: AliasMap
|
|
515
|
+
profile: 'server' | 'compat' | 'client'
|
|
516
|
+
conditionTarget: ServerBundleTarget
|
|
517
|
+
reactServerLayer?: boolean
|
|
518
|
+
externalLoadTarget: ExternalLoadTarget
|
|
519
|
+
stubClientImports: boolean
|
|
520
|
+
rewriteExternalServerImports: boolean
|
|
521
|
+
bundleExternalPackages: boolean
|
|
513
522
|
}
|
|
514
523
|
|
|
515
524
|
/**
|
|
@@ -522,9 +531,9 @@ interface DevModuleOptions {
|
|
|
522
531
|
function startRouteVendorStage(config: ResolvedConfig, route: RouteManifestEntry, files: string[]) {
|
|
523
532
|
// Kill switch for bisecting a suspected stage-overlap difference.
|
|
524
533
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
525
|
-
if (process.env.PNEXT_DISABLE_VENDOR_STAGE) return
|
|
526
|
-
if (!reactCompatEnabled(config)) return
|
|
527
|
-
const conditionTarget = serverBundleTargetForRuntime(route.segmentConfig?.runtime)
|
|
534
|
+
if (process.env.PNEXT_DISABLE_VENDOR_STAGE) return
|
|
535
|
+
if (!reactCompatEnabled(config)) return
|
|
536
|
+
const conditionTarget = serverBundleTargetForRuntime(route.segmentConfig?.runtime)
|
|
528
537
|
const options: DevModuleOptions = {
|
|
529
538
|
aliases: coreAliases(config, 'server'),
|
|
530
539
|
profile: 'compat',
|
|
@@ -533,40 +542,40 @@ function startRouteVendorStage(config: ResolvedConfig, route: RouteManifestEntry
|
|
|
533
542
|
stubClientImports: true,
|
|
534
543
|
rewriteExternalServerImports: true,
|
|
535
544
|
bundleExternalPackages: true,
|
|
536
|
-
}
|
|
545
|
+
}
|
|
537
546
|
// Only the SERVER vendor layer is seeded here: the client-reference walk already fans the
|
|
538
547
|
// client-SSR demand out, so seeding that layer too only competes for the JS thread.
|
|
539
548
|
//
|
|
540
549
|
// Layered seeding: a separate walk pruned at 'use client' boundaries, so client-only packages get
|
|
541
550
|
// no server-target builds. On-demand fallback covers anything needed sooner.
|
|
542
551
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
543
|
-
const layered = process.env.PNEXT_VENDOR_SEED_LAYERED !== '0'
|
|
544
|
-
const seedT0 = performance.now()
|
|
552
|
+
const layered = process.env.PNEXT_VENDOR_SEED_LAYERED !== '0'
|
|
553
|
+
const seedT0 = performance.now()
|
|
545
554
|
void (async () => {
|
|
546
|
-
const graph = devModuleGraph(config)
|
|
547
|
-
const demand = new Set<string>()
|
|
548
|
-
const dropped = new Set<string>()
|
|
555
|
+
const graph = devModuleGraph(config)
|
|
556
|
+
const demand = new Set<string>()
|
|
557
|
+
const dropped = new Set<string>()
|
|
549
558
|
// The layer of a source is part of the record the naming walk just scanned,
|
|
550
559
|
// so the seed reads it instead of re-reading the whole route graph.
|
|
551
560
|
const prune = (file: string) =>
|
|
552
561
|
(devHeadTrimEnabled() ? graph.isClientSource(file) : fileHasUseClientDirective(file)).catch(
|
|
553
562
|
() => false,
|
|
554
|
-
)
|
|
563
|
+
)
|
|
555
564
|
for (const file of files) {
|
|
556
|
-
const full = await graph.packageDemand(file)
|
|
557
|
-
const serverReachable = layered ? new Set(await graph.packageDemand(file, prune)) : undefined
|
|
565
|
+
const full = await graph.packageDemand(file)
|
|
566
|
+
const serverReachable = layered ? new Set(await graph.packageDemand(file, prune)) : undefined
|
|
558
567
|
for (const specifier of full) {
|
|
559
|
-
if (!shouldBundleExternalPackage(specifier, config, file, options)) continue
|
|
560
|
-
if (serverReachable && !serverReachable.has(specifier)) dropped.add(specifier)
|
|
561
|
-
else demand.add(specifier)
|
|
568
|
+
if (!shouldBundleExternalPackage(specifier, config, file, options)) continue
|
|
569
|
+
if (serverReachable && !serverReachable.has(specifier)) dropped.add(specifier)
|
|
570
|
+
else demand.add(specifier)
|
|
562
571
|
}
|
|
563
572
|
}
|
|
564
|
-
for (const specifier of demand) dropped.delete(specifier)
|
|
573
|
+
for (const specifier of demand) dropped.delete(specifier)
|
|
565
574
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
566
575
|
if (process.env.PNEXT_DEV_PROFILE) {
|
|
567
576
|
console.log(
|
|
568
577
|
`dev-import vendor seed walk ${route.route} in ${formatDuration(performance.now() - seedT0)} (kept ${demand.size}, dropped ${dropped.size})`,
|
|
569
|
-
)
|
|
578
|
+
)
|
|
570
579
|
}
|
|
571
580
|
if (vendorTraceEnabled()) {
|
|
572
581
|
vendorTraceRow({
|
|
@@ -576,18 +585,22 @@ function startRouteVendorStage(config: ResolvedConfig, route: RouteManifestEntry
|
|
|
576
585
|
kept: [...demand].sort(),
|
|
577
586
|
dropped: [...dropped].sort(),
|
|
578
587
|
atMs: performance.now(),
|
|
579
|
-
})
|
|
588
|
+
})
|
|
580
589
|
}
|
|
581
590
|
const warm = (specifiers: Iterable<string>) =>
|
|
582
591
|
Promise.all(
|
|
583
592
|
[...specifiers].map(specifier =>
|
|
584
|
-
externalServerPackageHref(
|
|
585
|
-
|
|
586
|
-
|
|
593
|
+
externalServerPackageHref(
|
|
594
|
+
config,
|
|
595
|
+
specifier,
|
|
596
|
+
'server',
|
|
597
|
+
config.root,
|
|
598
|
+
conditionTarget,
|
|
599
|
+
).catch(() => undefined),
|
|
587
600
|
),
|
|
588
|
-
)
|
|
589
|
-
await warm(demand)
|
|
590
|
-
})().catch(() => undefined)
|
|
601
|
+
)
|
|
602
|
+
await warm(demand)
|
|
603
|
+
})().catch(() => undefined)
|
|
591
604
|
}
|
|
592
605
|
|
|
593
606
|
export async function devRouteModuleLoaders(
|
|
@@ -599,35 +612,35 @@ export async function devRouteModuleLoaders(
|
|
|
599
612
|
// The bundle's own content-addressed path is the cache key: a save that
|
|
600
613
|
// changes nothing this route imports keeps it, one that does gives a new
|
|
601
614
|
// path — no version bump, no wholesale eviction.
|
|
602
|
-
const release = devModuleGraph(config).hold()
|
|
615
|
+
const release = devModuleGraph(config).hold()
|
|
603
616
|
const key = await profileDevImport(`route bundle key ${route.route}`, () =>
|
|
604
617
|
devRouteBundlePath(config, route, layoutFiles),
|
|
605
618
|
).catch(error => {
|
|
606
|
-
release()
|
|
607
|
-
throw error
|
|
608
|
-
})
|
|
609
|
-
noteModuleGeneration(`bundle:${route.route}`, key)
|
|
610
|
-
const existing = routeModuleLoaders.get(key)
|
|
619
|
+
release()
|
|
620
|
+
throw error
|
|
621
|
+
})
|
|
622
|
+
noteModuleGeneration(`bundle:${route.route}`, key)
|
|
623
|
+
const existing = routeModuleLoaders.get(key)
|
|
611
624
|
if (existing) {
|
|
612
|
-
release()
|
|
613
|
-
return existing
|
|
625
|
+
release()
|
|
626
|
+
return existing
|
|
614
627
|
}
|
|
615
628
|
// A bundle already on disk was built against vendor artifacts the previous process left next to it, so
|
|
616
629
|
// seeding the stage would walk the whole graph again to demand what is already there - and it would do it
|
|
617
630
|
// on the JS thread the restart's first response is waiting on. Anything genuinely missing is still built
|
|
618
631
|
// on demand by the resolve path.
|
|
619
632
|
if (!devArtifactUsable(key)) {
|
|
620
|
-
startRouteVendorStage(config, route, uniqueFiles([route.file, ...layoutFiles]))
|
|
633
|
+
startRouteVendorStage(config, route, uniqueFiles([route.file, ...layoutFiles]))
|
|
621
634
|
}
|
|
622
635
|
|
|
623
636
|
const next = createDevRouteModuleLoaders(config, route, layoutFiles, key)
|
|
624
637
|
.catch(error => {
|
|
625
|
-
routeModuleLoaders.delete(key)
|
|
626
|
-
throw error
|
|
638
|
+
routeModuleLoaders.delete(key)
|
|
639
|
+
throw error
|
|
627
640
|
})
|
|
628
|
-
.finally(release)
|
|
629
|
-
routeModuleLoaders.set(key, next)
|
|
630
|
-
return next
|
|
641
|
+
.finally(release)
|
|
642
|
+
routeModuleLoaders.set(key, next)
|
|
643
|
+
return next
|
|
631
644
|
}
|
|
632
645
|
|
|
633
646
|
async function createDevRouteModuleLoaders(
|
|
@@ -636,21 +649,22 @@ async function createDevRouteModuleLoaders(
|
|
|
636
649
|
layoutFiles: string[],
|
|
637
650
|
outFile: string,
|
|
638
651
|
): Promise<DevRouteModuleLoaders> {
|
|
639
|
-
const bundleFile = await writeDevRouteBundle(config, route, layoutFiles, outFile)
|
|
652
|
+
const bundleFile = await writeDevRouteBundle(config, route, layoutFiles, outFile)
|
|
640
653
|
const bundle = await profileDevImport(`import route ${route.route}`, () =>
|
|
641
|
-
importDevModule<DevRouteBundle>(pathToFileHref(bundleFile))
|
|
654
|
+
importDevModule<DevRouteBundle>(pathToFileHref(bundleFile)),
|
|
655
|
+
)
|
|
642
656
|
const moduleLoader = async (file: string) => {
|
|
643
|
-
const module = bundle.modules[file]
|
|
644
|
-
if (module) return module
|
|
657
|
+
const module = bundle.modules[file]
|
|
658
|
+
if (module) return module
|
|
645
659
|
return importDevModule<Record<string, unknown>>(
|
|
646
660
|
await devServerModuleHref(config, file, undefined, {
|
|
647
661
|
conditionTarget: serverBundleTargetForRuntime(route.segmentConfig?.runtime),
|
|
648
662
|
}),
|
|
649
|
-
)
|
|
650
|
-
}
|
|
663
|
+
)
|
|
664
|
+
}
|
|
651
665
|
const clientModuleLoader = async (file: string) => {
|
|
652
|
-
const module = bundle.clientModules[file]
|
|
653
|
-
if (module) return module
|
|
666
|
+
const module = bundle.clientModules[file]
|
|
667
|
+
if (module) return module
|
|
654
668
|
return importDevModule<Record<string, unknown>>(
|
|
655
669
|
await devClientModuleHref(
|
|
656
670
|
config,
|
|
@@ -658,14 +672,14 @@ async function createDevRouteModuleLoaders(
|
|
|
658
672
|
undefined,
|
|
659
673
|
serverBundleTargetForRuntime(route.segmentConfig?.runtime),
|
|
660
674
|
),
|
|
661
|
-
)
|
|
662
|
-
}
|
|
663
|
-
return { moduleLoader, clientModuleLoader }
|
|
675
|
+
)
|
|
676
|
+
}
|
|
677
|
+
return { moduleLoader, clientModuleLoader }
|
|
664
678
|
}
|
|
665
679
|
|
|
666
680
|
interface DevRouteBundle {
|
|
667
|
-
modules: Record<string, Record<string, unknown
|
|
668
|
-
clientModules: Record<string, Record<string, unknown
|
|
681
|
+
modules: Record<string, Record<string, unknown>>
|
|
682
|
+
clientModules: Record<string, Record<string, unknown>>
|
|
669
683
|
}
|
|
670
684
|
|
|
671
685
|
async function writeDevRouteBundle(
|
|
@@ -674,80 +688,79 @@ async function writeDevRouteBundle(
|
|
|
674
688
|
layoutFiles: string[],
|
|
675
689
|
outFile: string,
|
|
676
690
|
) {
|
|
677
|
-
if (devArtifactUsable(outFile)) return outFile
|
|
691
|
+
if (devArtifactUsable(outFile)) return outFile
|
|
678
692
|
|
|
679
|
-
const existing = routeBundleBuilds.get(outFile)
|
|
680
|
-
if (existing) return existing
|
|
693
|
+
const existing = routeBundleBuilds.get(outFile)
|
|
694
|
+
if (existing) return existing
|
|
681
695
|
|
|
682
696
|
const next = profileDevImport(`build route ${route.route}`, async () => {
|
|
683
|
-
await mkdir(path.dirname(outFile), { recursive: true })
|
|
684
|
-
const serverFiles = uniqueFiles([
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
return outFile;
|
|
697
|
+
await mkdir(path.dirname(outFile), { recursive: true })
|
|
698
|
+
const serverFiles = uniqueFiles([...(route.client ? [] : [route.file]), ...layoutFiles])
|
|
699
|
+
const result = await withBuildSlot(() =>
|
|
700
|
+
build({
|
|
701
|
+
stdin: {
|
|
702
|
+
contents: routeBundleEntrySource(serverFiles, []),
|
|
703
|
+
loader: 'ts',
|
|
704
|
+
resolveDir: config.root,
|
|
705
|
+
sourcefile: `${route.id}.route-bundle.ts`,
|
|
706
|
+
},
|
|
707
|
+
bundle: true,
|
|
708
|
+
write: false,
|
|
709
|
+
format: 'esm',
|
|
710
|
+
platform: 'neutral',
|
|
711
|
+
target: 'es2022',
|
|
712
|
+
// Workspace .js/.mjs app modules may contain JSX (packages stay
|
|
713
|
+
// external, so node_modules never reach these loaders).
|
|
714
|
+
loader: { '.js': 'jsx', '.mjs': 'jsx' },
|
|
715
|
+
jsx: 'automatic',
|
|
716
|
+
jsxImportSource: 'preact',
|
|
717
|
+
packages: 'external',
|
|
718
|
+
logLevel: 'silent',
|
|
719
|
+
...serverDefineOptions(),
|
|
720
|
+
plugins: [
|
|
721
|
+
serverAssetPlugin(config),
|
|
722
|
+
// Ahead of the extension plugins: they resolve without a namespace, so a tsconfig-`paths`
|
|
723
|
+
// import from a route-bundle module would land in esbuild's default namespace and take its
|
|
724
|
+
// whole subtree with it - out of this build's hooks, resolving bare specifiers like
|
|
725
|
+
// `next/dynamic` against real node_modules instead of the compat aliases.
|
|
726
|
+
devRouteBundlePlugin(config, route),
|
|
727
|
+
...getBundlerExtensions().serverEsbuildPlugins(config),
|
|
728
|
+
],
|
|
729
|
+
}),
|
|
730
|
+
)
|
|
731
|
+
const output = result.outputFiles[0]
|
|
732
|
+
if (!output) throw new Error(`Failed to build dev route bundle for ${route.route}`)
|
|
733
|
+
await writeCompiledFile(outFile, output.text)
|
|
734
|
+
return outFile
|
|
722
735
|
}).catch(error => {
|
|
723
|
-
routeBundleBuilds.delete(outFile)
|
|
724
|
-
throw error
|
|
725
|
-
})
|
|
736
|
+
routeBundleBuilds.delete(outFile)
|
|
737
|
+
throw error
|
|
738
|
+
})
|
|
726
739
|
|
|
727
|
-
routeBundleBuilds.set(outFile, next)
|
|
728
|
-
return next
|
|
740
|
+
routeBundleBuilds.set(outFile, next)
|
|
741
|
+
return next
|
|
729
742
|
}
|
|
730
743
|
|
|
731
744
|
function routeBundleEntrySource(serverFiles: string[], clientFiles: string[]) {
|
|
732
|
-
const serverImports = serverFiles.map(
|
|
733
|
-
`import * as server${index} from ${JSON.stringify(`pnext-server:${file}`)}
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
const
|
|
739
|
-
|
|
745
|
+
const serverImports = serverFiles.map(
|
|
746
|
+
(file, index) => `import * as server${index} from ${JSON.stringify(`pnext-server:${file}`)};`,
|
|
747
|
+
)
|
|
748
|
+
const clientImports = clientFiles.map(
|
|
749
|
+
(file, index) => `import * as client${index} from ${JSON.stringify(`pnext-client:${file}`)};`,
|
|
750
|
+
)
|
|
751
|
+
const serverEntries = serverFiles.map((file, index) => `${JSON.stringify(file)}: server${index},`)
|
|
752
|
+
const clientEntries = clientFiles.map((file, index) => `${JSON.stringify(file)}: client${index},`)
|
|
740
753
|
|
|
741
754
|
return [
|
|
742
755
|
...serverImports,
|
|
743
756
|
...clientImports,
|
|
744
757
|
`export const modules = {${serverEntries.join('')}};`,
|
|
745
758
|
`export const clientModules = {${clientEntries.join('')}};`,
|
|
746
|
-
].join('\n')
|
|
759
|
+
].join('\n')
|
|
747
760
|
}
|
|
748
761
|
|
|
749
762
|
function devRouteBundlePlugin(config: ResolvedConfig, route: RouteManifestEntry): Plugin {
|
|
750
|
-
const conditionTarget = serverBundleTargetForRuntime(route.segmentConfig?.runtime)
|
|
763
|
+
const conditionTarget = serverBundleTargetForRuntime(route.segmentConfig?.runtime)
|
|
751
764
|
const serverOptions: DevModuleOptions = {
|
|
752
765
|
aliases: {
|
|
753
766
|
...coreAliases(config, 'server'),
|
|
@@ -763,7 +776,7 @@ function devRouteBundlePlugin(config: ResolvedConfig, route: RouteManifestEntry)
|
|
|
763
776
|
stubClientImports: true,
|
|
764
777
|
rewriteExternalServerImports: true,
|
|
765
778
|
bundleExternalPackages: reactCompatEnabled(config),
|
|
766
|
-
}
|
|
779
|
+
}
|
|
767
780
|
const clientOptions: DevModuleOptions = {
|
|
768
781
|
aliases: clientSsrAliases(config),
|
|
769
782
|
profile: 'client',
|
|
@@ -772,7 +785,7 @@ function devRouteBundlePlugin(config: ResolvedConfig, route: RouteManifestEntry)
|
|
|
772
785
|
stubClientImports: false,
|
|
773
786
|
rewriteExternalServerImports: false,
|
|
774
787
|
bundleExternalPackages: reactCompatEnabled(config),
|
|
775
|
-
}
|
|
788
|
+
}
|
|
776
789
|
|
|
777
790
|
return {
|
|
778
791
|
name: 'pnext-dev-route-bundle',
|
|
@@ -780,25 +793,37 @@ function devRouteBundlePlugin(config: ResolvedConfig, route: RouteManifestEntry)
|
|
|
780
793
|
// Ensure the compat client-action module set is discovered for the active
|
|
781
794
|
// dev version before any route-client module loads (dev startup/reload used
|
|
782
795
|
// to run this inline). No-op for pure-core / non-next apps.
|
|
783
|
-
if (nextCompatEnabled(config)) await getClientActionBundler()?.ensureArmed()
|
|
784
|
-
build.onResolve({ filter: /^file:\/\// }, args => ({ path: args.path, external: true }))
|
|
796
|
+
if (nextCompatEnabled(config)) await getClientActionBundler()?.ensureArmed()
|
|
797
|
+
build.onResolve({ filter: /^file:\/\// }, args => ({ path: args.path, external: true }))
|
|
785
798
|
build.onResolve({ filter: /^pnext-server:/ }, args => ({
|
|
786
799
|
path: args.path.slice('pnext-server:'.length),
|
|
787
800
|
namespace: 'pnext-route-server',
|
|
788
|
-
}))
|
|
801
|
+
}))
|
|
789
802
|
build.onResolve({ filter: /^pnext-client:/ }, args => ({
|
|
790
803
|
path: args.path.slice('pnext-client:'.length),
|
|
791
804
|
namespace: 'pnext-route-client',
|
|
792
|
-
}))
|
|
805
|
+
}))
|
|
793
806
|
|
|
794
807
|
build.onResolve({ filter: /.*/, namespace: 'pnext-route-server' }, args =>
|
|
795
|
-
resolveRouteBundleSpecifier(
|
|
796
|
-
|
|
808
|
+
resolveRouteBundleSpecifier(
|
|
809
|
+
config,
|
|
810
|
+
args.path,
|
|
811
|
+
args.importer,
|
|
812
|
+
'pnext-route-server',
|
|
813
|
+
serverOptions,
|
|
814
|
+
),
|
|
815
|
+
)
|
|
797
816
|
build.onResolve({ filter: /.*/, namespace: 'pnext-route-client' }, args =>
|
|
798
|
-
resolveRouteBundleSpecifier(
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
817
|
+
resolveRouteBundleSpecifier(
|
|
818
|
+
config,
|
|
819
|
+
args.path,
|
|
820
|
+
args.importer,
|
|
821
|
+
'pnext-route-client',
|
|
822
|
+
clientOptions,
|
|
823
|
+
),
|
|
824
|
+
)
|
|
825
|
+
registerExternalLoadHandlers(build, config, serverOptions, externalLoadNamespaces.server)
|
|
826
|
+
registerExternalLoadHandlers(build, config, clientOptions, externalLoadNamespaces.client)
|
|
802
827
|
|
|
803
828
|
build.onLoad({ filter: /.*/, namespace: 'pnext-route-server' }, async args => ({
|
|
804
829
|
contents: rewriteServerSource(await readText(args.path), args.path, {
|
|
@@ -806,15 +831,15 @@ function devRouteBundlePlugin(config: ResolvedConfig, route: RouteManifestEntry)
|
|
|
806
831
|
root: config.root,
|
|
807
832
|
}),
|
|
808
833
|
loader: esbuildLoader(args.path),
|
|
809
|
-
}))
|
|
834
|
+
}))
|
|
810
835
|
build.onLoad({ filter: /.*/, namespace: 'pnext-route-client' }, async args => {
|
|
811
836
|
// A client-side import of a 'use server' module becomes the RPC stub.
|
|
812
|
-
const clientActions = nextCompatEnabled(config) ? getClientActionBundler() : undefined
|
|
837
|
+
const clientActions = nextCompatEnabled(config) ? getClientActionBundler() : undefined
|
|
813
838
|
if (clientActions?.isClientActionModule(args.path)) {
|
|
814
839
|
return {
|
|
815
840
|
contents: await clientActions.stubSource(args.path),
|
|
816
841
|
loader: 'js',
|
|
817
|
-
}
|
|
842
|
+
}
|
|
818
843
|
}
|
|
819
844
|
return {
|
|
820
845
|
contents: rewriteServerSource(await readText(args.path), args.path, {
|
|
@@ -822,14 +847,17 @@ function devRouteBundlePlugin(config: ResolvedConfig, route: RouteManifestEntry)
|
|
|
822
847
|
root: config.root,
|
|
823
848
|
}),
|
|
824
849
|
loader: esbuildLoader(args.path),
|
|
825
|
-
}
|
|
826
|
-
})
|
|
850
|
+
}
|
|
851
|
+
})
|
|
827
852
|
build.onLoad({ filter: /.*/, namespace: 'pnext-client-reference' }, async args => ({
|
|
828
|
-
contents: clientReferenceModuleSource(
|
|
853
|
+
contents: clientReferenceModuleSource(
|
|
854
|
+
args.path,
|
|
855
|
+
await clientReferenceExportNames(config, args.path),
|
|
856
|
+
),
|
|
829
857
|
loader: 'js',
|
|
830
|
-
}))
|
|
858
|
+
}))
|
|
831
859
|
},
|
|
832
|
-
}
|
|
860
|
+
}
|
|
833
861
|
}
|
|
834
862
|
|
|
835
863
|
/**
|
|
@@ -845,13 +873,12 @@ function pagesCompatSourceAlias(
|
|
|
845
873
|
importer: string | undefined,
|
|
846
874
|
options: DevModuleOptions,
|
|
847
875
|
): string | undefined {
|
|
848
|
-
if (!options.reactServerLayer) return undefined
|
|
849
|
-
if (specifier !== 'react' && specifier !== 'react-dom') return undefined
|
|
850
|
-
if (!importer) return undefined
|
|
851
|
-
const posix = importer.replace(/\\/g, '/')
|
|
852
|
-
if (!posix.includes('/pnext-pages-compat/') || !posix.includes('/source-pages/'))
|
|
853
|
-
|
|
854
|
-
return coreAliases(config, 'server')[specifier];
|
|
876
|
+
if (!options.reactServerLayer) return undefined
|
|
877
|
+
if (specifier !== 'react' && specifier !== 'react-dom') return undefined
|
|
878
|
+
if (!importer) return undefined
|
|
879
|
+
const posix = importer.replace(/\\/g, '/')
|
|
880
|
+
if (!posix.includes('/pnext-pages-compat/') || !posix.includes('/source-pages/')) return undefined
|
|
881
|
+
return coreAliases(config, 'server')[specifier]
|
|
855
882
|
}
|
|
856
883
|
|
|
857
884
|
async function resolveRouteBundleSpecifier(
|
|
@@ -862,24 +889,24 @@ async function resolveRouteBundleSpecifier(
|
|
|
862
889
|
options: DevModuleOptions,
|
|
863
890
|
): Promise<OnResolveResult | undefined> {
|
|
864
891
|
const compatAlias =
|
|
865
|
-
pagesCompatSourceAlias(config, specifier, importer, options) ?? options.aliases[specifier]
|
|
892
|
+
pagesCompatSourceAlias(config, specifier, importer, options) ?? options.aliases[specifier]
|
|
866
893
|
if (compatAlias) {
|
|
867
894
|
return {
|
|
868
895
|
path: path.isAbsolute(compatAlias) ? pathToFileHref(compatAlias) : compatAlias,
|
|
869
896
|
external: true,
|
|
870
|
-
}
|
|
897
|
+
}
|
|
871
898
|
}
|
|
872
899
|
|
|
873
900
|
// A `turbopack.rules` loader chain owns this specifier (see the same check in
|
|
874
901
|
// resolveDevModuleSpecifier): import its materialized output.
|
|
875
|
-
const pendingRuleModule = getAssetExtensions().loaderRuleModule(specifier, importer)
|
|
876
|
-
const ruleModule = pendingRuleModule === undefined ? undefined : await pendingRuleModule
|
|
877
|
-
if (ruleModule) return { path: pathToFileHref(ruleModule), external: true }
|
|
902
|
+
const pendingRuleModule = getAssetExtensions().loaderRuleModule(specifier, importer)
|
|
903
|
+
const ruleModule = pendingRuleModule === undefined ? undefined : await pendingRuleModule
|
|
904
|
+
if (ruleModule) return { path: pathToFileHref(ruleModule), external: true }
|
|
878
905
|
|
|
879
|
-
if (isServerIgnoredAssetSpecifier(specifier)) return undefined
|
|
906
|
+
if (isServerIgnoredAssetSpecifier(specifier)) return undefined
|
|
880
907
|
|
|
881
|
-
const { sourcePath, hash } = splitHash(specifier)
|
|
882
|
-
const resolved = resolveLocalImport(config, importer, sourcePath)
|
|
908
|
+
const { sourcePath, hash } = splitHash(specifier)
|
|
909
|
+
const resolved = resolveLocalImport(config, importer, sourcePath)
|
|
883
910
|
// `resolveExtensions` can land an extensionless specifier on an asset
|
|
884
911
|
// (`import img from './image'` -> image.png), which the specifier-keyed check
|
|
885
912
|
// above and serverAssetPlugin's own filter both miss. Hand it to the asset
|
|
@@ -889,17 +916,17 @@ async function resolveRouteBundleSpecifier(
|
|
|
889
916
|
isServerIgnoredAssetSpecifier(resolved) &&
|
|
890
917
|
!getAssetExtensions().hasLoaderRuleFor(resolved)
|
|
891
918
|
) {
|
|
892
|
-
return { path: resolved, namespace: serverAssetNamespace }
|
|
919
|
+
return { path: resolved, namespace: serverAssetNamespace }
|
|
893
920
|
}
|
|
894
921
|
if (resolved && isInside(config.workspaceRoot, resolved)) {
|
|
895
922
|
if (
|
|
896
923
|
options.stubClientImports &&
|
|
897
924
|
!isCssFile(resolved) &&
|
|
898
|
-
await fileHasUseClientDirective(resolved)
|
|
925
|
+
(await fileHasUseClientDirective(resolved))
|
|
899
926
|
) {
|
|
900
|
-
return { path: resolved, namespace: 'pnext-client-reference' }
|
|
927
|
+
return { path: resolved, namespace: 'pnext-client-reference' }
|
|
901
928
|
}
|
|
902
|
-
return { path: resolved, namespace }
|
|
929
|
+
return { path: resolved, namespace }
|
|
903
930
|
}
|
|
904
931
|
// An absolute path that lands outside the workspace is framework runtime a rewrite injected by path
|
|
905
932
|
// rather than by alias - pnext's own source is not part of the app's module graph, so it loads
|
|
@@ -907,10 +934,13 @@ async function resolveRouteBundleSpecifier(
|
|
|
907
934
|
// esbuild does not fall back to the filesystem for an import raised from a plugin namespace, so the
|
|
908
935
|
// whole route bundle fails and the route silently drops to per-module loading.
|
|
909
936
|
if (resolved && path.isAbsolute(sourcePath)) {
|
|
910
|
-
return { path: `${pathToFileHref(resolved)}${hash}`, external: true }
|
|
937
|
+
return { path: `${pathToFileHref(resolved)}${hash}`, external: true }
|
|
911
938
|
}
|
|
912
939
|
|
|
913
|
-
if (
|
|
940
|
+
if (
|
|
941
|
+
options.bundleExternalPackages &&
|
|
942
|
+
shouldBundleExternalPackage(sourcePath, config, importer, options)
|
|
943
|
+
) {
|
|
914
944
|
return {
|
|
915
945
|
path: await externalServerPackageHref(
|
|
916
946
|
config,
|
|
@@ -920,7 +950,7 @@ async function resolveRouteBundleSpecifier(
|
|
|
920
950
|
options.conditionTarget,
|
|
921
951
|
),
|
|
922
952
|
external: true,
|
|
923
|
-
}
|
|
953
|
+
}
|
|
924
954
|
}
|
|
925
955
|
|
|
926
956
|
if (isPackageSpecifier(sourcePath) || isBuiltinSpecifier(sourcePath)) {
|
|
@@ -929,17 +959,18 @@ async function resolveRouteBundleSpecifier(
|
|
|
929
959
|
fromFile: importer,
|
|
930
960
|
specifier: sourcePath,
|
|
931
961
|
target: options.externalLoadTarget,
|
|
932
|
-
})
|
|
933
|
-
if (resolvedExternalLoad && await externalLoadNeedsFacade(resolvedExternalLoad)) {
|
|
962
|
+
})
|
|
963
|
+
if (resolvedExternalLoad && (await externalLoadNeedsFacade(resolvedExternalLoad))) {
|
|
934
964
|
return {
|
|
935
965
|
path: resolvedExternalLoad,
|
|
936
|
-
namespace:
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
966
|
+
namespace:
|
|
967
|
+
options.profile === 'client'
|
|
968
|
+
? externalLoadNamespaces.client
|
|
969
|
+
: externalLoadNamespaces.server,
|
|
970
|
+
}
|
|
940
971
|
}
|
|
941
972
|
const externalTarget =
|
|
942
|
-
resolvedExternalLoad ?? externalPackageImportTarget(config.root, importer, sourcePath)
|
|
973
|
+
resolvedExternalLoad ?? externalPackageImportTarget(config.root, importer, sourcePath)
|
|
943
974
|
return {
|
|
944
975
|
path: externalTarget
|
|
945
976
|
? `${pathToFileHref(externalTarget)}${hash}`
|
|
@@ -947,48 +978,48 @@ async function resolveRouteBundleSpecifier(
|
|
|
947
978
|
? runtimeServerImportTarget(specifier)
|
|
948
979
|
: specifier,
|
|
949
980
|
external: true,
|
|
950
|
-
}
|
|
981
|
+
}
|
|
951
982
|
}
|
|
952
983
|
|
|
953
|
-
return undefined
|
|
984
|
+
return undefined
|
|
954
985
|
}
|
|
955
986
|
|
|
956
987
|
// Sidecar of each artifact's raw import specifiers so the vercel trace reads
|
|
957
988
|
// them back instead of re-parsing artifacts. Off outside vercel prod builds.
|
|
958
|
-
let emitCompiledSpecifiersManifest = false
|
|
989
|
+
let emitCompiledSpecifiersManifest = false
|
|
959
990
|
|
|
960
991
|
/** @internal Test-only. Returns a restore function. */
|
|
961
992
|
export function setEmitCompiledSpecifiersManifest(enabled: boolean) {
|
|
962
|
-
const previous = emitCompiledSpecifiersManifest
|
|
963
|
-
emitCompiledSpecifiersManifest = enabled
|
|
993
|
+
const previous = emitCompiledSpecifiersManifest
|
|
994
|
+
emitCompiledSpecifiersManifest = enabled
|
|
964
995
|
return () => {
|
|
965
|
-
emitCompiledSpecifiersManifest = previous
|
|
966
|
-
}
|
|
996
|
+
emitCompiledSpecifiersManifest = previous
|
|
997
|
+
}
|
|
967
998
|
}
|
|
968
999
|
|
|
969
1000
|
/** Sidecar suffix for a compiled artifact's recorded specifiers. */
|
|
970
|
-
export const compiledSpecifiersManifestSuffix = '.pnext-specifiers.json'
|
|
1001
|
+
export const compiledSpecifiersManifestSuffix = '.pnext-specifiers.json'
|
|
971
1002
|
|
|
972
|
-
const compiledScriptFilePattern = /\.(?:m?js|cjs|jsx|tsx?)
|
|
1003
|
+
const compiledScriptFilePattern = /\.(?:m?js|cjs|jsx|tsx?)$/
|
|
973
1004
|
|
|
974
1005
|
async function writeCompiledFile(file: string, contents: string) {
|
|
975
|
-
await writeFileAtomic(file, contents)
|
|
976
|
-
noteDevArtifactWritten(file)
|
|
1006
|
+
await writeFileAtomic(file, contents)
|
|
1007
|
+
noteDevArtifactWritten(file)
|
|
977
1008
|
if (emitCompiledSpecifiersManifest && compiledScriptFilePattern.test(file)) {
|
|
978
1009
|
await writeFileAtomic(
|
|
979
1010
|
`${file}${compiledSpecifiersManifestSuffix}`,
|
|
980
1011
|
JSON.stringify(importSpecifiers(contents, file)),
|
|
981
|
-
)
|
|
1012
|
+
)
|
|
982
1013
|
}
|
|
983
1014
|
}
|
|
984
1015
|
|
|
985
1016
|
/** Where `file` compiles to under `profile`, named by its current source graph. */
|
|
986
1017
|
async function devModulePathFor(config: ResolvedConfig, file: string, profile: string) {
|
|
987
|
-
return devServerPath(config, file, profile, await devModuleGraph(config).graphHash(file))
|
|
1018
|
+
return devServerPath(config, file, profile, await devModuleGraph(config).graphHash(file))
|
|
988
1019
|
}
|
|
989
1020
|
|
|
990
1021
|
function devModulePath(config: ResolvedConfig, file: string, options: DevModuleOptions) {
|
|
991
|
-
return devModulePathFor(config, file, devModuleProfile(options))
|
|
1022
|
+
return devModulePathFor(config, file, devModuleProfile(options))
|
|
992
1023
|
}
|
|
993
1024
|
|
|
994
1025
|
async function writeDevModule(
|
|
@@ -997,39 +1028,44 @@ async function writeDevModule(
|
|
|
997
1028
|
visited: Set<string>,
|
|
998
1029
|
options: DevModuleOptions,
|
|
999
1030
|
) {
|
|
1000
|
-
const profile = devModuleProfile(options)
|
|
1001
|
-
const outFile = await devModulePath(config, file, options)
|
|
1002
|
-
const visitedKey = `${profile}:${file}
|
|
1003
|
-
noteModuleGeneration(visitedKey, outFile)
|
|
1004
|
-
if (visited.has(visitedKey)) return outFile
|
|
1031
|
+
const profile = devModuleProfile(options)
|
|
1032
|
+
const outFile = await devModulePath(config, file, options)
|
|
1033
|
+
const visitedKey = `${profile}:${file}`
|
|
1034
|
+
noteModuleGeneration(visitedKey, outFile)
|
|
1035
|
+
if (visited.has(visitedKey)) return outFile
|
|
1005
1036
|
|
|
1006
1037
|
// The name carries the hash of this module's whole source graph, so an existing artifact is by
|
|
1007
1038
|
// construction current. An unusable one (the cache folder was wiped from outside) falls through to
|
|
1008
1039
|
// recompile, which overwrites it in place - never delete first, since Bun caches a failed resolution for
|
|
1009
1040
|
// the life of the process.
|
|
1010
|
-
if (devArtifactUsable(outFile)) return outFile
|
|
1041
|
+
if (devArtifactUsable(outFile)) return outFile
|
|
1011
1042
|
|
|
1012
|
-
const existing = moduleBuilds.get(outFile)
|
|
1013
|
-
if (existing) return existing
|
|
1043
|
+
const existing = moduleBuilds.get(outFile)
|
|
1044
|
+
if (existing) return existing
|
|
1014
1045
|
|
|
1015
|
-
visited.add(visitedKey)
|
|
1016
|
-
const started = moduleStats ? performance.now() : 0
|
|
1046
|
+
visited.add(visitedKey)
|
|
1047
|
+
const started = moduleStats ? performance.now() : 0
|
|
1017
1048
|
const next = writeDevModuleUncached(config, file, visited, options, outFile)
|
|
1018
1049
|
.then(result => {
|
|
1019
1050
|
if (moduleStats) {
|
|
1020
|
-
noteModuleCompile(
|
|
1051
|
+
noteModuleCompile(
|
|
1052
|
+
file,
|
|
1053
|
+
profile,
|
|
1054
|
+
performance.now() - started,
|
|
1055
|
+
esbuildFallbacks.delete(visitedKey),
|
|
1056
|
+
)
|
|
1021
1057
|
}
|
|
1022
|
-
return result
|
|
1058
|
+
return result
|
|
1023
1059
|
})
|
|
1024
1060
|
.catch(error => {
|
|
1025
|
-
moduleBuilds.delete(outFile)
|
|
1026
|
-
throw error
|
|
1061
|
+
moduleBuilds.delete(outFile)
|
|
1062
|
+
throw error
|
|
1027
1063
|
})
|
|
1028
1064
|
.finally(() => {
|
|
1029
|
-
moduleBuilds.delete(outFile)
|
|
1030
|
-
})
|
|
1031
|
-
moduleBuilds.set(outFile, next)
|
|
1032
|
-
return next
|
|
1065
|
+
moduleBuilds.delete(outFile)
|
|
1066
|
+
})
|
|
1067
|
+
moduleBuilds.set(outFile, next)
|
|
1068
|
+
return next
|
|
1033
1069
|
}
|
|
1034
1070
|
|
|
1035
1071
|
async function writeDevModuleUncached(
|
|
@@ -1039,44 +1075,49 @@ async function writeDevModuleUncached(
|
|
|
1039
1075
|
options: DevModuleOptions,
|
|
1040
1076
|
outFile: string,
|
|
1041
1077
|
) {
|
|
1042
|
-
await mkdir(path.dirname(outFile), { recursive: true })
|
|
1078
|
+
await mkdir(path.dirname(outFile), { recursive: true })
|
|
1043
1079
|
if (isCssFile(file)) {
|
|
1044
|
-
await copyFile(file, outFile)
|
|
1045
|
-
return outFile
|
|
1080
|
+
await copyFile(file, outFile)
|
|
1081
|
+
return outFile
|
|
1046
1082
|
}
|
|
1047
1083
|
|
|
1048
1084
|
// A client-profile compile of a 'use server' module emits the RPC stub, so
|
|
1049
1085
|
// the browser bundle POSTs to the endpoint instead of shipping server code.
|
|
1050
1086
|
if (options.profile === 'client' && nextCompatEnabled(config)) {
|
|
1051
|
-
const clientActions = getClientActionBundler()
|
|
1087
|
+
const clientActions = getClientActionBundler()
|
|
1052
1088
|
if (clientActions) {
|
|
1053
|
-
await clientActions.ensureArmed()
|
|
1089
|
+
await clientActions.ensureArmed()
|
|
1054
1090
|
if (clientActions.isClientActionModule(file)) {
|
|
1055
|
-
await writeCompiledFile(outFile, await clientActions.stubSource(file))
|
|
1056
|
-
return outFile
|
|
1091
|
+
await writeCompiledFile(outFile, await clientActions.stubSource(file))
|
|
1092
|
+
return outFile
|
|
1057
1093
|
}
|
|
1058
1094
|
}
|
|
1059
1095
|
}
|
|
1060
1096
|
|
|
1061
|
-
let source = await readText(file)
|
|
1097
|
+
let source = await readText(file)
|
|
1062
1098
|
if (/\.mdx?$/.test(file) && nextCompatEnabled(config)) {
|
|
1063
1099
|
// Markdown modules must be compiled to JS before import analysis and the
|
|
1064
1100
|
// stdin-based bundle pass (which bypasses file-resolved onLoad plugins).
|
|
1065
|
-
const { compileMdx } = await import('../compat/mdx/compile')
|
|
1066
|
-
source = (await compileMdx(source, file, config.root)).code
|
|
1101
|
+
const { compileMdx } = await import('../compat/mdx/compile')
|
|
1102
|
+
source = (await compileMdx(source, file, config.root)).code
|
|
1067
1103
|
}
|
|
1068
1104
|
const transformedSource = rewriteServerSource(source, file, {
|
|
1069
1105
|
nextFonts: nextCompatEnabled(config),
|
|
1070
1106
|
root: config.root,
|
|
1071
|
-
})
|
|
1107
|
+
})
|
|
1072
1108
|
const effectiveOptions =
|
|
1073
|
-
reactCompatEnabled(config) &&
|
|
1074
|
-
|
|
1075
|
-
|
|
1109
|
+
reactCompatEnabled(config) &&
|
|
1110
|
+
options.profile !== 'client' &&
|
|
1111
|
+
hasUseClientDirective(transformedSource)
|
|
1112
|
+
? clientLayerOptions(
|
|
1113
|
+
config,
|
|
1114
|
+
clientLayerConditionTarget(config, file, options.conditionTarget),
|
|
1115
|
+
)
|
|
1116
|
+
: options
|
|
1076
1117
|
// The asset-context link walk is pure I/O against directories nothing below
|
|
1077
1118
|
// reads, so it overlaps the shaking/scan/child-compile work and is only
|
|
1078
1119
|
// awaited before the write.
|
|
1079
|
-
const assetContext = linkAssetContext(config, file, devModuleProfile(effectiveOptions))
|
|
1120
|
+
const assetContext = linkAssetContext(config, file, devModuleProfile(effectiveOptions))
|
|
1080
1121
|
// Rewrite destructured dynamic imports (`const { used } = await import('x')`)
|
|
1081
1122
|
// to point at a tree-shaken facade so the target's unused exports are dropped
|
|
1082
1123
|
// (compat: mirrors webpack's dynamic-import export usage analysis). Done
|
|
@@ -1088,7 +1129,7 @@ async function writeDevModuleUncached(
|
|
|
1088
1129
|
const deferredDynamic =
|
|
1089
1130
|
effectiveOptions.profile === 'client' && devDynamicSplitEnabled()
|
|
1090
1131
|
? deferredDynamicTargetFiles(transformedSource, config, file)
|
|
1091
|
-
: undefined
|
|
1132
|
+
: undefined
|
|
1092
1133
|
const shakenSource = await applyDynamicImportTreeShaking(
|
|
1093
1134
|
transformedSource,
|
|
1094
1135
|
config,
|
|
@@ -1096,10 +1137,13 @@ async function writeDevModuleUncached(
|
|
|
1096
1137
|
visited,
|
|
1097
1138
|
effectiveOptions,
|
|
1098
1139
|
deferredDynamic,
|
|
1099
|
-
)
|
|
1100
|
-
const clientReferences =
|
|
1101
|
-
|
|
1102
|
-
|
|
1140
|
+
)
|
|
1141
|
+
const clientReferences =
|
|
1142
|
+
reactCompatEnabled(config) &&
|
|
1143
|
+
effectiveOptions.stubClientImports &&
|
|
1144
|
+
!hasUseClientDirective(shakenSource)
|
|
1145
|
+
? await writeClientReferenceModules(shakenSource, config, file, effectiveOptions.aliases)
|
|
1146
|
+
: { specifiers: new Map<string, string>(), files: new Set<string>() }
|
|
1103
1147
|
// Compiling this module only needs its imports' *paths*, so it runs while the
|
|
1104
1148
|
// targets themselves compile. The write still waits for them: a compiled
|
|
1105
1149
|
// module must never become loadable before its edges exist on disk.
|
|
@@ -1109,10 +1153,10 @@ async function writeDevModuleUncached(
|
|
|
1109
1153
|
file,
|
|
1110
1154
|
clientReferences.specifiers,
|
|
1111
1155
|
effectiveOptions,
|
|
1112
|
-
)
|
|
1156
|
+
)
|
|
1113
1157
|
// The real rejection is reported below; this only keeps it from surfacing as
|
|
1114
1158
|
// an unhandled rejection while the imports compile.
|
|
1115
|
-
compiled.catch(() => undefined)
|
|
1159
|
+
compiled.catch(() => undefined)
|
|
1116
1160
|
await Promise.all([
|
|
1117
1161
|
assetContext,
|
|
1118
1162
|
...localImportTargets(shakenSource, config, file, effectiveOptions.aliases)
|
|
@@ -1127,17 +1171,17 @@ async function writeDevModuleUncached(
|
|
|
1127
1171
|
!deferredDynamic?.has(path.resolve(target)),
|
|
1128
1172
|
)
|
|
1129
1173
|
.map(({ target }) => writeDevModule(config, target, visited, effectiveOptions)),
|
|
1130
|
-
])
|
|
1174
|
+
])
|
|
1131
1175
|
|
|
1132
|
-
await writeCompiledFile(outFile, await compiled)
|
|
1133
|
-
return outFile
|
|
1176
|
+
await writeCompiledFile(outFile, await compiled)
|
|
1177
|
+
return outFile
|
|
1134
1178
|
}
|
|
1135
1179
|
|
|
1136
|
-
const shakeEntrySpecifier = 'pnext-shake-entry'
|
|
1137
|
-
const shakeEntryNamespace = 'pnext-shake-entry'
|
|
1180
|
+
const shakeEntrySpecifier = 'pnext-shake-entry'
|
|
1181
|
+
const shakeEntryNamespace = 'pnext-shake-entry'
|
|
1138
1182
|
|
|
1139
1183
|
// Rewrite each shakeable destructured dynamic import in `source` so its
|
|
1140
|
-
// specifier points at a tree-shaken facade module (see
|
|
1184
|
+
// specifier points at a tree-shaken facade module (see resolve/tree-shake.ts).
|
|
1141
1185
|
// Only local workspace modules are shaken; 'use client' targets keep their
|
|
1142
1186
|
// client-reference stubbing path. Returns the source unchanged when nothing
|
|
1143
1187
|
// qualifies.
|
|
@@ -1149,45 +1193,45 @@ async function applyDynamicImportTreeShaking(
|
|
|
1149
1193
|
options: DevModuleOptions,
|
|
1150
1194
|
deferredDynamic?: Set<string>,
|
|
1151
1195
|
): Promise<string> {
|
|
1152
|
-
if (!nextCompatEnabled(config)) return source
|
|
1153
|
-
const candidates = findShakeableDynamicImports(source)
|
|
1154
|
-
if (candidates.length === 0) return source
|
|
1196
|
+
if (!nextCompatEnabled(config)) return source
|
|
1197
|
+
const candidates = findShakeableDynamicImports(source)
|
|
1198
|
+
if (candidates.length === 0) return source
|
|
1155
1199
|
|
|
1156
|
-
const edits: { start: number; end: number; value: string }[] = []
|
|
1200
|
+
const edits: { start: number; end: number; value: string }[] = []
|
|
1157
1201
|
for (const candidate of candidates) {
|
|
1158
|
-
const { sourcePath } = splitHash(candidate.specifier)
|
|
1159
|
-
if (isServerIgnoredAssetSpecifier(sourcePath) && !isCssFile(sourcePath)) continue
|
|
1160
|
-
const resolved = resolveLocalImport(config, file, sourcePath)
|
|
1161
|
-
if (!resolved || !isInside(config.workspaceRoot, resolved) || isCssFile(resolved)) continue
|
|
1202
|
+
const { sourcePath } = splitHash(candidate.specifier)
|
|
1203
|
+
if (isServerIgnoredAssetSpecifier(sourcePath) && !isCssFile(sourcePath)) continue
|
|
1204
|
+
const resolved = resolveLocalImport(config, file, sourcePath)
|
|
1205
|
+
if (!resolved || !isInside(config.workspaceRoot, resolved) || isCssFile(resolved)) continue
|
|
1162
1206
|
// A deferred dynamic() target must not compile eagerly through the facade.
|
|
1163
|
-
if (deferredDynamic?.has(path.resolve(resolved))) continue
|
|
1164
|
-
if (options.stubClientImports && (await fileHasUseClientDirective(resolved))) continue
|
|
1207
|
+
if (deferredDynamic?.has(path.resolve(resolved))) continue
|
|
1208
|
+
if (options.stubClientImports && (await fileHasUseClientDirective(resolved))) continue
|
|
1165
1209
|
const facade = await writeShakenDynamicModule(
|
|
1166
1210
|
config,
|
|
1167
1211
|
resolved,
|
|
1168
1212
|
candidate.usedExports,
|
|
1169
1213
|
visited,
|
|
1170
1214
|
options,
|
|
1171
|
-
)
|
|
1215
|
+
)
|
|
1172
1216
|
edits.push({
|
|
1173
1217
|
start: candidate.literalStart,
|
|
1174
1218
|
end: candidate.literalEnd,
|
|
1175
1219
|
value: JSON.stringify(pathToFileHref(facade)),
|
|
1176
|
-
})
|
|
1220
|
+
})
|
|
1177
1221
|
}
|
|
1178
1222
|
|
|
1179
|
-
if (edits.length === 0) return source
|
|
1180
|
-
let next = source
|
|
1223
|
+
if (edits.length === 0) return source
|
|
1224
|
+
let next = source
|
|
1181
1225
|
for (const edit of edits.sort((a, b) => b.start - a.start)) {
|
|
1182
|
-
next = `${next.slice(0, edit.start)}${edit.value}${next.slice(edit.end)}
|
|
1226
|
+
next = `${next.slice(0, edit.start)}${edit.value}${next.slice(edit.end)}`
|
|
1183
1227
|
}
|
|
1184
|
-
return next
|
|
1228
|
+
return next
|
|
1185
1229
|
}
|
|
1186
1230
|
|
|
1187
1231
|
function shakeExportsKey(usedExports: string[]): string {
|
|
1188
|
-
const sorted = [...new Set(usedExports)].sort()
|
|
1189
|
-
if (sorted.length === 0) return 'none'
|
|
1190
|
-
return createHash('sha256').update(sorted.join(',')).digest('hex').slice(0, 8)
|
|
1232
|
+
const sorted = [...new Set(usedExports)].sort()
|
|
1233
|
+
if (sorted.length === 0) return 'none'
|
|
1234
|
+
return createHash('sha256').update(sorted.join(',')).digest('hex').slice(0, 8)
|
|
1191
1235
|
}
|
|
1192
1236
|
|
|
1193
1237
|
async function shakenModulePath(
|
|
@@ -1196,26 +1240,26 @@ async function shakenModulePath(
|
|
|
1196
1240
|
profile: string,
|
|
1197
1241
|
key: string,
|
|
1198
1242
|
): Promise<string> {
|
|
1199
|
-
const base = await devModulePathFor(config, targetFile, profile)
|
|
1200
|
-
const ext = path.extname(base) || '.js'
|
|
1201
|
-
return `${base.slice(0, base.length - ext.length)}.pnext-shake-${key}${ext}
|
|
1243
|
+
const base = await devModulePathFor(config, targetFile, profile)
|
|
1244
|
+
const ext = path.extname(base) || '.js'
|
|
1245
|
+
return `${base.slice(0, base.length - ext.length)}.pnext-shake-${key}${ext}`
|
|
1202
1246
|
}
|
|
1203
1247
|
|
|
1204
1248
|
// Facade entry that re-exports only the destructured names from the target
|
|
1205
1249
|
// (loaded inline via the shake-entry namespace), so esbuild tree-shakes the
|
|
1206
1250
|
// rest. An empty set keeps side effects only.
|
|
1207
1251
|
function shakenEntrySource(usedExports: string[]): string {
|
|
1208
|
-
const names = [...new Set(usedExports)]
|
|
1209
|
-
if (names.length === 0) return `import ${JSON.stringify(shakeEntrySpecifier)};\n
|
|
1210
|
-
const named = names.filter(name => name !== 'default')
|
|
1211
|
-
const lines: string[] = []
|
|
1252
|
+
const names = [...new Set(usedExports)]
|
|
1253
|
+
if (names.length === 0) return `import ${JSON.stringify(shakeEntrySpecifier)};\n`
|
|
1254
|
+
const named = names.filter(name => name !== 'default')
|
|
1255
|
+
const lines: string[] = []
|
|
1212
1256
|
if (named.length > 0) {
|
|
1213
|
-
lines.push(`export { ${named.join(', ')} } from ${JSON.stringify(shakeEntrySpecifier)};`)
|
|
1257
|
+
lines.push(`export { ${named.join(', ')} } from ${JSON.stringify(shakeEntrySpecifier)};`)
|
|
1214
1258
|
}
|
|
1215
1259
|
if (names.includes('default')) {
|
|
1216
|
-
lines.push(`export { default } from ${JSON.stringify(shakeEntrySpecifier)};`)
|
|
1260
|
+
lines.push(`export { default } from ${JSON.stringify(shakeEntrySpecifier)};`)
|
|
1217
1261
|
}
|
|
1218
|
-
return `${lines.join('\n')}\n
|
|
1262
|
+
return `${lines.join('\n')}\n`
|
|
1219
1263
|
}
|
|
1220
1264
|
|
|
1221
1265
|
async function writeShakenDynamicModule(
|
|
@@ -1225,24 +1269,31 @@ async function writeShakenDynamicModule(
|
|
|
1225
1269
|
visited: Set<string>,
|
|
1226
1270
|
options: DevModuleOptions,
|
|
1227
1271
|
): Promise<string> {
|
|
1228
|
-
const profile = devModuleProfile(options)
|
|
1229
|
-
const outFile = await shakenModulePath(config, targetFile, profile, shakeExportsKey(usedExports))
|
|
1230
|
-
if (devArtifactUsable(outFile)) return outFile
|
|
1272
|
+
const profile = devModuleProfile(options)
|
|
1273
|
+
const outFile = await shakenModulePath(config, targetFile, profile, shakeExportsKey(usedExports))
|
|
1274
|
+
if (devArtifactUsable(outFile)) return outFile
|
|
1231
1275
|
|
|
1232
|
-
const key = `shake\0${outFile}
|
|
1233
|
-
const existing = moduleBuilds.get(key)
|
|
1234
|
-
if (existing) return existing
|
|
1276
|
+
const key = `shake\0${outFile}`
|
|
1277
|
+
const existing = moduleBuilds.get(key)
|
|
1278
|
+
if (existing) return existing
|
|
1235
1279
|
|
|
1236
|
-
const next = writeShakenDynamicModuleUncached(
|
|
1280
|
+
const next = writeShakenDynamicModuleUncached(
|
|
1281
|
+
config,
|
|
1282
|
+
targetFile,
|
|
1283
|
+
usedExports,
|
|
1284
|
+
visited,
|
|
1285
|
+
options,
|
|
1286
|
+
outFile,
|
|
1287
|
+
)
|
|
1237
1288
|
.catch(error => {
|
|
1238
|
-
moduleBuilds.delete(key)
|
|
1239
|
-
throw error
|
|
1289
|
+
moduleBuilds.delete(key)
|
|
1290
|
+
throw error
|
|
1240
1291
|
})
|
|
1241
1292
|
.finally(() => {
|
|
1242
|
-
moduleBuilds.delete(key)
|
|
1243
|
-
})
|
|
1244
|
-
moduleBuilds.set(key, next)
|
|
1245
|
-
return next
|
|
1293
|
+
moduleBuilds.delete(key)
|
|
1294
|
+
})
|
|
1295
|
+
moduleBuilds.set(key, next)
|
|
1296
|
+
return next
|
|
1246
1297
|
}
|
|
1247
1298
|
|
|
1248
1299
|
async function writeShakenDynamicModuleUncached(
|
|
@@ -1253,20 +1304,22 @@ async function writeShakenDynamicModuleUncached(
|
|
|
1253
1304
|
options: DevModuleOptions,
|
|
1254
1305
|
outFile: string,
|
|
1255
1306
|
): Promise<string> {
|
|
1256
|
-
await mkdir(path.dirname(outFile), { recursive: true })
|
|
1307
|
+
await mkdir(path.dirname(outFile), { recursive: true })
|
|
1257
1308
|
const targetSource = rewriteServerSource(await readText(targetFile), targetFile, {
|
|
1258
1309
|
nextFonts: nextCompatEnabled(config),
|
|
1259
1310
|
root: config.root,
|
|
1260
|
-
})
|
|
1311
|
+
})
|
|
1261
1312
|
// The target is inlined into the facade, but its own transitive imports stay
|
|
1262
1313
|
// external file:// modules — compile them the same way the normal graph walk
|
|
1263
1314
|
// would so those hrefs resolve on disk.
|
|
1264
1315
|
await Promise.all(
|
|
1265
1316
|
localImportTargets(targetSource, config, targetFile, options.aliases)
|
|
1266
|
-
.filter(
|
|
1317
|
+
.filter(
|
|
1318
|
+
({ target, isLocalSource }) => isLocalSource || isInside(config.workspaceRoot, target),
|
|
1319
|
+
)
|
|
1267
1320
|
.map(({ target }) => writeDevModule(config, target, visited, options)),
|
|
1268
|
-
)
|
|
1269
|
-
const externalBundles = await externalBundleSpecifiers(targetSource, config, targetFile, options)
|
|
1321
|
+
)
|
|
1322
|
+
const externalBundles = await externalBundleSpecifiers(targetSource, config, targetFile, options)
|
|
1270
1323
|
const result = await withBuildSlot(() =>
|
|
1271
1324
|
build({
|
|
1272
1325
|
stdin: {
|
|
@@ -1292,11 +1345,11 @@ async function writeShakenDynamicModuleUncached(
|
|
|
1292
1345
|
devModuleResolvePlugin(config, targetFile, new Map(), externalBundles, options),
|
|
1293
1346
|
],
|
|
1294
1347
|
}),
|
|
1295
|
-
)
|
|
1296
|
-
const output = result.outputFiles[0]
|
|
1297
|
-
if (!output) throw new Error(`Failed to tree-shake ${targetFile}`)
|
|
1298
|
-
await writeCompiledFile(outFile, addExtensionlessDynamicImportGlobAliases(output.text))
|
|
1299
|
-
return outFile
|
|
1348
|
+
)
|
|
1349
|
+
const output = result.outputFiles[0]
|
|
1350
|
+
if (!output) throw new Error(`Failed to tree-shake ${targetFile}`)
|
|
1351
|
+
await writeCompiledFile(outFile, addExtensionlessDynamicImportGlobAliases(output.text))
|
|
1352
|
+
return outFile
|
|
1300
1353
|
}
|
|
1301
1354
|
|
|
1302
1355
|
// Resolve the facade's re-export entry to the target's already-transformed
|
|
@@ -1308,18 +1361,18 @@ function shakeEntryPlugin(targetFile: string, targetSource: string): Plugin {
|
|
|
1308
1361
|
build.onResolve({ filter: new RegExp(`^${shakeEntrySpecifier}$`) }, () => ({
|
|
1309
1362
|
path: targetFile,
|
|
1310
1363
|
namespace: shakeEntryNamespace,
|
|
1311
|
-
}))
|
|
1364
|
+
}))
|
|
1312
1365
|
build.onLoad({ filter: /.*/, namespace: shakeEntryNamespace }, () => ({
|
|
1313
1366
|
contents: targetSource,
|
|
1314
1367
|
loader: esbuildLoader(targetFile),
|
|
1315
1368
|
resolveDir: path.dirname(targetFile),
|
|
1316
|
-
}))
|
|
1369
|
+
}))
|
|
1317
1370
|
},
|
|
1318
|
-
}
|
|
1371
|
+
}
|
|
1319
1372
|
}
|
|
1320
1373
|
|
|
1321
1374
|
function externalLoadTargetForConditionTarget(target: ServerBundleTarget): ExternalLoadTarget {
|
|
1322
|
-
return target === 'edge' ? 'edge' : 'server'
|
|
1375
|
+
return target === 'edge' ? 'edge' : 'server'
|
|
1323
1376
|
}
|
|
1324
1377
|
|
|
1325
1378
|
function clientLayerOptions(
|
|
@@ -1334,7 +1387,7 @@ function clientLayerOptions(
|
|
|
1334
1387
|
stubClientImports: false,
|
|
1335
1388
|
rewriteExternalServerImports: false,
|
|
1336
1389
|
bundleExternalPackages: reactCompatEnabled(config),
|
|
1337
|
-
}
|
|
1390
|
+
}
|
|
1338
1391
|
}
|
|
1339
1392
|
|
|
1340
1393
|
/**
|
|
@@ -1355,9 +1408,9 @@ function pagesLayerConditionTarget(
|
|
|
1355
1408
|
file: string,
|
|
1356
1409
|
incoming: ServerBundleTarget,
|
|
1357
1410
|
): ServerBundleTarget {
|
|
1358
|
-
if (incoming === 'server') return isPagesRouterSourceFile(config, file) ? 'pages' : 'server'
|
|
1359
|
-
if (incoming === 'edge') return isPagesRouterSourceFile(config, file) ? 'pages-edge' : 'edge'
|
|
1360
|
-
return incoming
|
|
1411
|
+
if (incoming === 'server') return isPagesRouterSourceFile(config, file) ? 'pages' : 'server'
|
|
1412
|
+
if (incoming === 'edge') return isPagesRouterSourceFile(config, file) ? 'pages-edge' : 'edge'
|
|
1413
|
+
return incoming
|
|
1361
1414
|
}
|
|
1362
1415
|
|
|
1363
1416
|
function clientLayerConditionTarget(
|
|
@@ -1365,24 +1418,24 @@ function clientLayerConditionTarget(
|
|
|
1365
1418
|
file: string,
|
|
1366
1419
|
incoming: ServerBundleTarget,
|
|
1367
1420
|
): ServerBundleTarget {
|
|
1368
|
-
if (!isPagesRouterSourceFile(config, file)) return 'client'
|
|
1369
|
-
return incoming === 'edge' || incoming === 'pages-edge' ? 'pages-edge' : 'pages'
|
|
1421
|
+
if (!isPagesRouterSourceFile(config, file)) return 'client'
|
|
1422
|
+
return incoming === 'edge' || incoming === 'pages-edge' ? 'pages-edge' : 'pages'
|
|
1370
1423
|
}
|
|
1371
1424
|
|
|
1372
1425
|
function isPagesRouterSourceFile(config: ResolvedConfig, file: string): boolean {
|
|
1373
|
-
const posix = file.split(path.sep).join('/')
|
|
1374
|
-
if (posix.includes('/source-pages/')) return true
|
|
1426
|
+
const posix = file.split(path.sep).join('/')
|
|
1427
|
+
if (posix.includes('/source-pages/')) return true
|
|
1375
1428
|
// Materialized hybrid wrappers live under pnext-pages-compat/<hash>/app/ for
|
|
1376
1429
|
// BOTH routers; only pages wrappers re-export from source-pages/, so sniff
|
|
1377
1430
|
// the (tiny, generated) wrapper body to tell them apart.
|
|
1378
1431
|
if (posix.includes('pnext-pages-compat/')) {
|
|
1379
1432
|
try {
|
|
1380
|
-
return readFileSync(file, 'utf8').includes('source-pages/')
|
|
1433
|
+
return readFileSync(file, 'utf8').includes('source-pages/')
|
|
1381
1434
|
} catch {
|
|
1382
|
-
return false
|
|
1435
|
+
return false
|
|
1383
1436
|
}
|
|
1384
1437
|
}
|
|
1385
|
-
return isInside(path.join(config.root, 'pages'), file)
|
|
1438
|
+
return isInside(path.join(config.root, 'pages'), file)
|
|
1386
1439
|
}
|
|
1387
1440
|
|
|
1388
1441
|
async function buildDevModuleSource(
|
|
@@ -1392,7 +1445,7 @@ async function buildDevModuleSource(
|
|
|
1392
1445
|
clientReferenceSpecifiers: Map<string, string>,
|
|
1393
1446
|
options: DevModuleOptions,
|
|
1394
1447
|
) {
|
|
1395
|
-
const externalBundles = await externalBundleSpecifiers(source, config, file, options)
|
|
1448
|
+
const externalBundles = await externalBundleSpecifiers(source, config, file, options)
|
|
1396
1449
|
const transformed = await transformDevModuleSource(
|
|
1397
1450
|
source,
|
|
1398
1451
|
config,
|
|
@@ -1400,42 +1453,44 @@ async function buildDevModuleSource(
|
|
|
1400
1453
|
clientReferenceSpecifiers,
|
|
1401
1454
|
externalBundles,
|
|
1402
1455
|
options,
|
|
1403
|
-
)
|
|
1404
|
-
if (transformed !== undefined) return transformed
|
|
1405
|
-
if (moduleStats) esbuildFallbacks.add(`${devModuleProfile(options)}:${file}`)
|
|
1406
|
-
const result = await withBuildSlot(() =>
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
const
|
|
1456
|
+
)
|
|
1457
|
+
if (transformed !== undefined) return transformed
|
|
1458
|
+
if (moduleStats) esbuildFallbacks.add(`${devModuleProfile(options)}:${file}`)
|
|
1459
|
+
const result = await withBuildSlot(() =>
|
|
1460
|
+
build({
|
|
1461
|
+
stdin: {
|
|
1462
|
+
contents: source,
|
|
1463
|
+
loader: esbuildLoader(file),
|
|
1464
|
+
resolveDir: path.dirname(file),
|
|
1465
|
+
sourcefile: file,
|
|
1466
|
+
},
|
|
1467
|
+
bundle: true,
|
|
1468
|
+
write: false,
|
|
1469
|
+
format: 'esm',
|
|
1470
|
+
platform: 'neutral',
|
|
1471
|
+
target: 'es2022',
|
|
1472
|
+
// Workspace .js/.mjs app modules may contain JSX (packages stay external,
|
|
1473
|
+
// so node_modules never reach these loaders).
|
|
1474
|
+
loader: { '.js': 'jsx', '.mjs': 'jsx' },
|
|
1475
|
+
jsx: 'automatic',
|
|
1476
|
+
jsxImportSource: 'preact',
|
|
1477
|
+
packages: 'external',
|
|
1478
|
+
logLevel: 'silent',
|
|
1479
|
+
...serverDefineOptions(),
|
|
1480
|
+
plugins: [
|
|
1481
|
+
serverAssetPlugin(config),
|
|
1482
|
+
...getBundlerExtensions().serverEsbuildPlugins(config),
|
|
1483
|
+
serverClientReferencePlugin(config, options),
|
|
1484
|
+
devModuleResolvePlugin(config, file, clientReferenceSpecifiers, externalBundles, options),
|
|
1485
|
+
],
|
|
1486
|
+
}),
|
|
1487
|
+
)
|
|
1488
|
+
const output = result.outputFiles[0]
|
|
1489
|
+
if (!output) throw new Error(`Failed to build ${file}`)
|
|
1490
|
+
const compiled = addExtensionlessDynamicImportGlobAliases(output.text)
|
|
1436
1491
|
return options.profile === 'client' && isCommonJsModuleSource(source, file)
|
|
1437
1492
|
? unwrapCommonJsDefaultExport(compiled)
|
|
1438
|
-
: compiled
|
|
1493
|
+
: compiled
|
|
1439
1494
|
}
|
|
1440
1495
|
|
|
1441
1496
|
/**
|
|
@@ -1455,13 +1510,13 @@ async function transformDevModuleSource(
|
|
|
1455
1510
|
// Kill switch: puts every module back on the esbuild build below, for
|
|
1456
1511
|
// bisecting a suspected transform-path difference.
|
|
1457
1512
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
1458
|
-
if (process.env.PNEXT_DISABLE_BATCHED_TRANSFORM) return undefined
|
|
1459
|
-
if (transformBailReason(source, file)) return undefined
|
|
1460
|
-
const transformed = transformServerModule(source, file, serverDefineOptions().define)
|
|
1461
|
-
if ('bail' in transformed) return undefined
|
|
1513
|
+
if (process.env.PNEXT_DISABLE_BATCHED_TRANSFORM) return undefined
|
|
1514
|
+
if (transformBailReason(source, file)) return undefined
|
|
1515
|
+
const transformed = transformServerModule(source, file, serverDefineOptions().define)
|
|
1516
|
+
if ('bail' in transformed) return undefined
|
|
1462
1517
|
|
|
1463
|
-
const { code } = transformed
|
|
1464
|
-
const edits: { start: number; end: number; value: string }[] = []
|
|
1518
|
+
const { code } = transformed
|
|
1519
|
+
const edits: { start: number; end: number; value: string }[] = []
|
|
1465
1520
|
for (const found of outputSpecifiers(code)) {
|
|
1466
1521
|
const target = await devModuleSpecifierTarget(
|
|
1467
1522
|
found.value,
|
|
@@ -1471,11 +1526,12 @@ async function transformDevModuleSource(
|
|
|
1471
1526
|
clientReferenceSpecifiers,
|
|
1472
1527
|
externalBundles,
|
|
1473
1528
|
options,
|
|
1474
|
-
)
|
|
1475
|
-
if (target === undefined) return undefined
|
|
1476
|
-
if (target !== found.value)
|
|
1529
|
+
)
|
|
1530
|
+
if (target === undefined) return undefined
|
|
1531
|
+
if (target !== found.value)
|
|
1532
|
+
edits.push({ start: found.start, end: found.end, value: JSON.stringify(target) })
|
|
1477
1533
|
}
|
|
1478
|
-
return spliceSource(code, edits)
|
|
1534
|
+
return spliceSource(code, edits)
|
|
1479
1535
|
}
|
|
1480
1536
|
|
|
1481
1537
|
/**
|
|
@@ -1495,11 +1551,11 @@ async function devModuleSpecifierTarget(
|
|
|
1495
1551
|
// Same claim `serverAssetPlugin` makes, and in the same order: it is the first
|
|
1496
1552
|
// onResolve in the build below.
|
|
1497
1553
|
if (isServerIgnoredAssetSpecifier(specifier)) {
|
|
1498
|
-
const resolved = resolveAssetPath(specifier, path.dirname(file))
|
|
1554
|
+
const resolved = resolveAssetPath(specifier, path.dirname(file))
|
|
1499
1555
|
// A configured loader-rule chain (turbopack.rules) preempts the generic
|
|
1500
1556
|
// asset stub — fall through to the rule-aware resolver below.
|
|
1501
1557
|
if (!getAssetExtensions().hasLoaderRuleFor(resolved)) {
|
|
1502
|
-
return pathToFileHref(await assetStubModule(config, resolved, devModuleProfile(options)))
|
|
1558
|
+
return pathToFileHref(await assetStubModule(config, resolved, devModuleProfile(options)))
|
|
1503
1559
|
}
|
|
1504
1560
|
}
|
|
1505
1561
|
const resolved = await resolveDevModuleSpecifier(
|
|
@@ -1511,20 +1567,20 @@ async function devModuleSpecifierTarget(
|
|
|
1511
1567
|
options,
|
|
1512
1568
|
kind,
|
|
1513
1569
|
file,
|
|
1514
|
-
)
|
|
1570
|
+
)
|
|
1515
1571
|
// A non-external result names a namespace the bundler loads inline
|
|
1516
1572
|
// (`pnext-module-external` facades) or a bare require-alias path.
|
|
1517
|
-
return resolved.external && typeof resolved.path === 'string' ? resolved.path : undefined
|
|
1573
|
+
return resolved.external && typeof resolved.path === 'string' ? resolved.path : undefined
|
|
1518
1574
|
}
|
|
1519
1575
|
|
|
1520
|
-
const assetStubs = new Map<string, Promise<string>>()
|
|
1576
|
+
const assetStubs = new Map<string, Promise<string>>()
|
|
1521
1577
|
|
|
1522
1578
|
/** The empty/static-asset/css-module stub the bundler used to inline, as a file. */
|
|
1523
1579
|
async function assetStubModule(config: ResolvedConfig, resolved: string, profile: string) {
|
|
1524
|
-
const base = splitHash(resolved).sourcePath.replace(/\?.*$/, '')
|
|
1525
|
-
const outFile = `${await devModulePathFor(config, base, profile)}.pnext-asset.js
|
|
1526
|
-
const existing = assetStubs.get(outFile)
|
|
1527
|
-
if (existing) return existing
|
|
1580
|
+
const base = splitHash(resolved).sourcePath.replace(/\?.*$/, '')
|
|
1581
|
+
const outFile = `${await devModulePathFor(config, base, profile)}.pnext-asset.js`
|
|
1582
|
+
const existing = assetStubs.get(outFile)
|
|
1583
|
+
if (existing) return existing
|
|
1528
1584
|
const next = (async () => {
|
|
1529
1585
|
const contents =
|
|
1530
1586
|
getCssExtensions().loadCssModuleForClient(resolved) ??
|
|
@@ -1532,27 +1588,27 @@ async function assetStubModule(config: ResolvedConfig, resolved: string, profile
|
|
|
1532
1588
|
? ''
|
|
1533
1589
|
: isStaticImageFile(base)
|
|
1534
1590
|
? await staticImageModuleSource(config, resolved)
|
|
1535
|
-
: 'export default "";')
|
|
1536
|
-
await mkdir(path.dirname(outFile), { recursive: true })
|
|
1537
|
-
await writeCompiledFile(outFile, contents)
|
|
1538
|
-
return outFile
|
|
1591
|
+
: 'export default "";')
|
|
1592
|
+
await mkdir(path.dirname(outFile), { recursive: true })
|
|
1593
|
+
await writeCompiledFile(outFile, contents)
|
|
1594
|
+
return outFile
|
|
1539
1595
|
})().catch(error => {
|
|
1540
|
-
assetStubs.delete(outFile)
|
|
1541
|
-
throw error
|
|
1542
|
-
})
|
|
1543
|
-
assetStubs.set(outFile, next)
|
|
1544
|
-
return next
|
|
1596
|
+
assetStubs.delete(outFile)
|
|
1597
|
+
throw error
|
|
1598
|
+
})
|
|
1599
|
+
assetStubs.set(outFile, next)
|
|
1600
|
+
return next
|
|
1545
1601
|
}
|
|
1546
1602
|
|
|
1547
1603
|
function unwrapCommonJsDefaultExport(code: string) {
|
|
1548
|
-
const match = /(^|\n)export default ([^;\n]+);/.exec(code)
|
|
1549
|
-
if (!match?.[2]) return code
|
|
1550
|
-
const moduleName = uniqueIdentifier(code, '__pnext_cjs_module')
|
|
1551
|
-
const defaultName = uniqueIdentifier(code, '__pnext_cjs_default', moduleName)
|
|
1604
|
+
const match = /(^|\n)export default ([^;\n]+);/.exec(code)
|
|
1605
|
+
if (!match?.[2]) return code
|
|
1606
|
+
const moduleName = uniqueIdentifier(code, '__pnext_cjs_module')
|
|
1607
|
+
const defaultName = uniqueIdentifier(code, '__pnext_cjs_default', moduleName)
|
|
1552
1608
|
return code.replace(
|
|
1553
1609
|
match[0],
|
|
1554
1610
|
`${match[1]}var ${moduleName} = ${match[2]};\nvar ${defaultName} = ${moduleName} != null && "default" in Object(${moduleName}) ? ${moduleName}.default : ${moduleName};\nexport { ${defaultName} as default };`,
|
|
1555
|
-
)
|
|
1611
|
+
)
|
|
1556
1612
|
}
|
|
1557
1613
|
|
|
1558
1614
|
function devModuleResolvePlugin(
|
|
@@ -1565,7 +1621,7 @@ function devModuleResolvePlugin(
|
|
|
1565
1621
|
return {
|
|
1566
1622
|
name: 'pnext-dev-module-resolve',
|
|
1567
1623
|
setup(build) {
|
|
1568
|
-
registerExternalLoadHandlers(build, config, options, externalLoadNamespaces.module)
|
|
1624
|
+
registerExternalLoadHandlers(build, config, options, externalLoadNamespaces.module)
|
|
1569
1625
|
build.onResolve({ filter: /.*/ }, args =>
|
|
1570
1626
|
resolveDevModuleSpecifier(
|
|
1571
1627
|
args.path,
|
|
@@ -1577,9 +1633,9 @@ function devModuleResolvePlugin(
|
|
|
1577
1633
|
args.kind,
|
|
1578
1634
|
args.importer,
|
|
1579
1635
|
),
|
|
1580
|
-
)
|
|
1636
|
+
)
|
|
1581
1637
|
},
|
|
1582
|
-
}
|
|
1638
|
+
}
|
|
1583
1639
|
}
|
|
1584
1640
|
|
|
1585
1641
|
function serverClientReferencePlugin(config: ResolvedConfig, options: DevModuleOptions): Plugin {
|
|
@@ -1587,9 +1643,9 @@ function serverClientReferencePlugin(config: ResolvedConfig, options: DevModuleO
|
|
|
1587
1643
|
name: 'pnext-server-client-references',
|
|
1588
1644
|
setup(build) {
|
|
1589
1645
|
build.onLoad({ filter: /\.[cm]?[jt]sx?$/, namespace: 'file' }, async args => {
|
|
1590
|
-
if (!options.stubClientImports) return undefined
|
|
1591
|
-
const source = await readText(args.path)
|
|
1592
|
-
if (!hasUseClientDirective(source)) return undefined
|
|
1646
|
+
if (!options.stubClientImports) return undefined
|
|
1647
|
+
const source = await readText(args.path)
|
|
1648
|
+
if (!hasUseClientDirective(source)) return undefined
|
|
1593
1649
|
return {
|
|
1594
1650
|
contents: clientReferenceModuleSource(
|
|
1595
1651
|
args.path,
|
|
@@ -1597,43 +1653,45 @@ function serverClientReferencePlugin(config: ResolvedConfig, options: DevModuleO
|
|
|
1597
1653
|
),
|
|
1598
1654
|
loader: 'js',
|
|
1599
1655
|
resolveDir: path.dirname(args.path),
|
|
1600
|
-
}
|
|
1601
|
-
})
|
|
1656
|
+
}
|
|
1657
|
+
})
|
|
1602
1658
|
},
|
|
1603
|
-
}
|
|
1659
|
+
}
|
|
1604
1660
|
}
|
|
1605
1661
|
|
|
1606
|
-
const serverAssetNamespace = 'pnext-empty-server-asset'
|
|
1662
|
+
const serverAssetNamespace = 'pnext-empty-server-asset'
|
|
1607
1663
|
|
|
1608
1664
|
function serverAssetPlugin(config: ResolvedConfig): Plugin {
|
|
1609
1665
|
return {
|
|
1610
1666
|
name: 'pnext-empty-server-assets',
|
|
1611
1667
|
setup(build) {
|
|
1612
1668
|
build.onResolve({ filter: serverIgnoredAssetFilter }, args => {
|
|
1613
|
-
const resolved = resolveAssetPath(args.path, args.resolveDir)
|
|
1669
|
+
const resolved = resolveAssetPath(args.path, args.resolveDir)
|
|
1614
1670
|
// A configured `turbopack.rules` loader chain for this extension (e.g.
|
|
1615
1671
|
// `*.svg`) preempts the generic asset pipeline: defer so the compat
|
|
1616
1672
|
// loader-rule plugin's onLoad runs the chain against the real file.
|
|
1617
1673
|
// This is the plugin that builds app-page SERVER bundles, so without
|
|
1618
1674
|
// this escape hatch a configured `*.svg` rule never runs here.
|
|
1619
|
-
if (getAssetExtensions().hasLoaderRuleFor(resolved)) return undefined
|
|
1620
|
-
return { path: resolved, namespace: serverAssetNamespace }
|
|
1621
|
-
})
|
|
1675
|
+
if (getAssetExtensions().hasLoaderRuleFor(resolved)) return undefined
|
|
1676
|
+
return { path: resolved, namespace: serverAssetNamespace }
|
|
1677
|
+
})
|
|
1622
1678
|
build.onLoad({ filter: /.*/, namespace: serverAssetNamespace }, async args => {
|
|
1623
1679
|
// Registry-provided CSS modules (e.g. *.module.scss) export their class
|
|
1624
1680
|
// map on the server instead of the empty-asset stub.
|
|
1625
|
-
const cssModule = getCssExtensions().loadCssModuleForClient(args.path)
|
|
1681
|
+
const cssModule = getCssExtensions().loadCssModuleForClient(args.path)
|
|
1626
1682
|
return {
|
|
1627
|
-
contents:
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
?
|
|
1631
|
-
:
|
|
1683
|
+
contents:
|
|
1684
|
+
cssModule ??
|
|
1685
|
+
(isCssFile(args.path)
|
|
1686
|
+
? ''
|
|
1687
|
+
: isStaticImageFile(args.path)
|
|
1688
|
+
? await staticImageModuleSource(config, args.path)
|
|
1689
|
+
: 'export default "";'),
|
|
1632
1690
|
loader: 'js',
|
|
1633
|
-
}
|
|
1634
|
-
})
|
|
1691
|
+
}
|
|
1692
|
+
})
|
|
1635
1693
|
},
|
|
1636
|
-
}
|
|
1694
|
+
}
|
|
1637
1695
|
}
|
|
1638
1696
|
|
|
1639
1697
|
async function resolveDevModuleSpecifier(
|
|
@@ -1652,8 +1710,8 @@ async function resolveDevModuleSpecifier(
|
|
|
1652
1710
|
// client-reference stubbing as a bare specifier - importing the raw file yields an untagged third
|
|
1653
1711
|
// module instance the render walk never recognizes as a client boundary.
|
|
1654
1712
|
if (specifier.startsWith('file://')) {
|
|
1655
|
-
const { sourcePath: hrefPath, hash: hrefHash } = splitHash(specifier)
|
|
1656
|
-
const target = fileURLToPath(hrefPath)
|
|
1713
|
+
const { sourcePath: hrefPath, hash: hrefHash } = splitHash(specifier)
|
|
1714
|
+
const target = fileURLToPath(hrefPath)
|
|
1657
1715
|
if (
|
|
1658
1716
|
options.stubClientImports &&
|
|
1659
1717
|
!isCssFile(target) &&
|
|
@@ -1661,11 +1719,15 @@ async function resolveDevModuleSpecifier(
|
|
|
1661
1719
|
existsSync(target) &&
|
|
1662
1720
|
(await fileHasUseClientDirective(target))
|
|
1663
1721
|
) {
|
|
1664
|
-
const stub = await clientReferencePath(config, target)
|
|
1665
|
-
await writeClientReferenceModule(
|
|
1666
|
-
|
|
1722
|
+
const stub = await clientReferencePath(config, target)
|
|
1723
|
+
await writeClientReferenceModule(
|
|
1724
|
+
stub,
|
|
1725
|
+
target,
|
|
1726
|
+
await clientReferenceExportNames(config, target),
|
|
1727
|
+
)
|
|
1728
|
+
return { path: `${pathToFileHref(stub)}${hrefHash}`, external: true }
|
|
1667
1729
|
}
|
|
1668
|
-
return { path: specifier, external: true }
|
|
1730
|
+
return { path: specifier, external: true }
|
|
1669
1731
|
}
|
|
1670
1732
|
|
|
1671
1733
|
// A `turbopack.rules` loader chain owns this specifier (`?query` and all):
|
|
@@ -1674,17 +1736,17 @@ async function resolveDevModuleSpecifier(
|
|
|
1674
1736
|
const pendingRuleModule = getAssetExtensions().loaderRuleModule(
|
|
1675
1737
|
specifier,
|
|
1676
1738
|
importer && !importer.startsWith('<') ? importer : file,
|
|
1677
|
-
)
|
|
1678
|
-
const ruleModule = pendingRuleModule === undefined ? undefined : await pendingRuleModule
|
|
1679
|
-
if (ruleModule) return { path: pathToFileHref(ruleModule), external: true }
|
|
1739
|
+
)
|
|
1740
|
+
const ruleModule = pendingRuleModule === undefined ? undefined : await pendingRuleModule
|
|
1741
|
+
if (ruleModule) return { path: pathToFileHref(ruleModule), external: true }
|
|
1680
1742
|
|
|
1681
|
-
const { sourcePath, hash } = splitHash(specifier)
|
|
1743
|
+
const { sourcePath, hash } = splitHash(specifier)
|
|
1682
1744
|
|
|
1683
1745
|
const compatAlias =
|
|
1684
|
-
pagesCompatSourceAlias(config, specifier, importer, options) ?? options.aliases[specifier]
|
|
1746
|
+
pagesCompatSourceAlias(config, specifier, importer, options) ?? options.aliases[specifier]
|
|
1685
1747
|
if (compatAlias) {
|
|
1686
1748
|
if (kind === 'require-call' && path.isAbsolute(compatAlias)) {
|
|
1687
|
-
return { path: serverRequireAlias(specifier, compatAlias, options) }
|
|
1749
|
+
return { path: serverRequireAlias(specifier, compatAlias, options) }
|
|
1688
1750
|
}
|
|
1689
1751
|
// A compat alias pointing at a 'use client' file needs the SAME stub-and-match treatment as any
|
|
1690
1752
|
// other local client component: the render tree walk recognizes a client boundary by a
|
|
@@ -1697,36 +1759,44 @@ async function resolveDevModuleSpecifier(
|
|
|
1697
1759
|
!isCssFile(compatAlias) &&
|
|
1698
1760
|
(await fileHasUseClientDirective(compatAlias))
|
|
1699
1761
|
) {
|
|
1700
|
-
const stub = await clientReferencePath(config, compatAlias)
|
|
1701
|
-
await writeClientReferenceModule(
|
|
1702
|
-
|
|
1762
|
+
const stub = await clientReferencePath(config, compatAlias)
|
|
1763
|
+
await writeClientReferenceModule(
|
|
1764
|
+
stub,
|
|
1765
|
+
compatAlias,
|
|
1766
|
+
await clientReferenceExportNames(config, compatAlias),
|
|
1767
|
+
)
|
|
1768
|
+
return { path: pathToFileHref(stub), external: true }
|
|
1703
1769
|
}
|
|
1704
1770
|
return {
|
|
1705
1771
|
path: path.isAbsolute(compatAlias) ? pathToFileHref(compatAlias) : compatAlias,
|
|
1706
1772
|
external: true,
|
|
1707
|
-
}
|
|
1773
|
+
}
|
|
1708
1774
|
}
|
|
1709
1775
|
|
|
1710
|
-
const externalBundle = externalBundles.get(sourcePath)
|
|
1776
|
+
const externalBundle = externalBundles.get(sourcePath)
|
|
1711
1777
|
if (externalBundle && kind === 'require-call') {
|
|
1712
1778
|
const required = resolvePackageSpecifier(
|
|
1713
1779
|
config.root,
|
|
1714
1780
|
file,
|
|
1715
1781
|
sourcePath,
|
|
1716
1782
|
serverBundleRequireConditions(options.conditionTarget),
|
|
1717
|
-
)
|
|
1718
|
-
if (required) return { path: `${required}${hash}`, external: true }
|
|
1783
|
+
)
|
|
1784
|
+
if (required) return { path: `${required}${hash}`, external: true }
|
|
1719
1785
|
}
|
|
1720
|
-
if (externalBundle) return { path: `${externalBundle}${hash}`, external: true }
|
|
1786
|
+
if (externalBundle) return { path: `${externalBundle}${hash}`, external: true }
|
|
1721
1787
|
|
|
1722
|
-
const clientReferenceStubPath = clientReferenceSpecifiers.get(sourcePath)
|
|
1723
|
-
if (clientReferenceStubPath)
|
|
1788
|
+
const clientReferenceStubPath = clientReferenceSpecifiers.get(sourcePath)
|
|
1789
|
+
if (clientReferenceStubPath)
|
|
1790
|
+
return { path: `${pathToFileHref(clientReferenceStubPath)}${hash}`, external: true }
|
|
1724
1791
|
|
|
1725
1792
|
if (
|
|
1726
1793
|
sourcePath === './preact' &&
|
|
1727
1794
|
path.resolve(importer) === new URL('../compat/react/client.ts', import.meta.url).pathname
|
|
1728
1795
|
) {
|
|
1729
|
-
return {
|
|
1796
|
+
return {
|
|
1797
|
+
path: pathToFileHref(new URL('../compat/react/preact.ts', import.meta.url).pathname),
|
|
1798
|
+
external: true,
|
|
1799
|
+
}
|
|
1730
1800
|
}
|
|
1731
1801
|
|
|
1732
1802
|
if (
|
|
@@ -1734,36 +1804,38 @@ async function resolveDevModuleSpecifier(
|
|
|
1734
1804
|
path.resolve(importer) === new URL('../compat/react/preact.ts', import.meta.url).pathname
|
|
1735
1805
|
) {
|
|
1736
1806
|
return {
|
|
1737
|
-
path: pathToFileHref(
|
|
1807
|
+
path: pathToFileHref(
|
|
1808
|
+
new URL('../compat/client/errors/primitive-throw.ts', import.meta.url).pathname,
|
|
1809
|
+
),
|
|
1738
1810
|
external: true,
|
|
1739
|
-
}
|
|
1811
|
+
}
|
|
1740
1812
|
}
|
|
1741
1813
|
|
|
1742
1814
|
const resolved = resolveImport(
|
|
1743
1815
|
config.root,
|
|
1744
1816
|
importer && !importer.startsWith('<') ? importer : file,
|
|
1745
1817
|
sourcePath,
|
|
1746
|
-
)
|
|
1818
|
+
)
|
|
1747
1819
|
// A relative specifier that resolved names first-party source next to its importer, so compile it
|
|
1748
1820
|
// through the normal pipeline even outside workspaceRoot (compat's own runtime files live inside
|
|
1749
1821
|
// pnext's package, not the app's workspace). A bare specifier resolving outside workspaceRoot is a
|
|
1750
1822
|
// package meant to stay external, untouched.
|
|
1751
|
-
const isLocalSourceSpecifier = sourcePath.startsWith('.') || path.isAbsolute(sourcePath)
|
|
1823
|
+
const isLocalSourceSpecifier = sourcePath.startsWith('.') || path.isAbsolute(sourcePath)
|
|
1752
1824
|
if (!resolved || (!isLocalSourceSpecifier && !isInside(config.workspaceRoot, resolved))) {
|
|
1753
1825
|
const resolvedExternalLoad = resolveExternalLoadTarget({
|
|
1754
1826
|
root: config.root,
|
|
1755
1827
|
fromFile: file,
|
|
1756
1828
|
specifier: sourcePath,
|
|
1757
1829
|
target: options.externalLoadTarget,
|
|
1758
|
-
})
|
|
1759
|
-
if (resolvedExternalLoad && await externalLoadNeedsFacade(resolvedExternalLoad)) {
|
|
1830
|
+
})
|
|
1831
|
+
if (resolvedExternalLoad && (await externalLoadNeedsFacade(resolvedExternalLoad))) {
|
|
1760
1832
|
return {
|
|
1761
1833
|
path: resolvedExternalLoad,
|
|
1762
1834
|
namespace: externalLoadNamespaces.module,
|
|
1763
|
-
}
|
|
1835
|
+
}
|
|
1764
1836
|
}
|
|
1765
1837
|
const externalTarget =
|
|
1766
|
-
resolvedExternalLoad ?? externalPackageImportTarget(config.root, file, sourcePath)
|
|
1838
|
+
resolvedExternalLoad ?? externalPackageImportTarget(config.root, file, sourcePath)
|
|
1767
1839
|
return {
|
|
1768
1840
|
path: externalTarget
|
|
1769
1841
|
? `${pathToFileHref(externalTarget)}${hash}`
|
|
@@ -1771,7 +1843,7 @@ async function resolveDevModuleSpecifier(
|
|
|
1771
1843
|
? runtimeServerImportTarget(specifier)
|
|
1772
1844
|
: specifier,
|
|
1773
1845
|
external: true,
|
|
1774
|
-
}
|
|
1846
|
+
}
|
|
1775
1847
|
}
|
|
1776
1848
|
// A configured `resolveExtensions` order can make an EXTENSIONLESS specifier
|
|
1777
1849
|
// land on an asset (`import img from './image'` -> image.png). The asset
|
|
@@ -1785,13 +1857,21 @@ async function resolveDevModuleSpecifier(
|
|
|
1785
1857
|
!isCssFile(resolved) &&
|
|
1786
1858
|
!getAssetExtensions().hasLoaderRuleFor(resolved)
|
|
1787
1859
|
) {
|
|
1788
|
-
const stub = await assetStubModule(config, resolved, devModuleProfile(options))
|
|
1789
|
-
return { path: pathToFileHref(stub), external: true }
|
|
1860
|
+
const stub = await assetStubModule(config, resolved, devModuleProfile(options))
|
|
1861
|
+
return { path: pathToFileHref(stub), external: true }
|
|
1790
1862
|
}
|
|
1791
|
-
if (
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1863
|
+
if (
|
|
1864
|
+
options.stubClientImports &&
|
|
1865
|
+
!isCssFile(resolved) &&
|
|
1866
|
+
(await fileHasUseClientDirective(resolved))
|
|
1867
|
+
) {
|
|
1868
|
+
const stub = await clientReferencePath(config, resolved)
|
|
1869
|
+
await writeClientReferenceModule(
|
|
1870
|
+
stub,
|
|
1871
|
+
resolved,
|
|
1872
|
+
await clientReferenceExportNames(config, resolved),
|
|
1873
|
+
)
|
|
1874
|
+
return { path: `${pathToFileHref(stub)}${hash}`, external: true }
|
|
1795
1875
|
}
|
|
1796
1876
|
// The compiled-path rewrite below is only safe for imports the WRITE pass walks:
|
|
1797
1877
|
// writeDevModuleUncached compiles the entry source's own local import targets, nothing deeper. A
|
|
@@ -1799,13 +1879,13 @@ async function resolveDevModuleSpecifier(
|
|
|
1799
1879
|
// relative imports through this hook, and nothing ever writes those into the profile cache - the
|
|
1800
1880
|
// import would resolve to a file that does not exist. Point such a transitive import at its own
|
|
1801
1881
|
// source instead; Bun compiles the `.ts` on import.
|
|
1802
|
-
const importerFile = importer && !importer.startsWith('<') ? path.resolve(importer) : ''
|
|
1882
|
+
const importerFile = importer && !importer.startsWith('<') ? path.resolve(importer) : ''
|
|
1803
1883
|
const bundledCompatImporter =
|
|
1804
1884
|
isLocalSourceSpecifier &&
|
|
1805
1885
|
importerFile !== '' &&
|
|
1806
1886
|
importerFile !== path.resolve(file) &&
|
|
1807
1887
|
!isInside(config.workspaceRoot, importerFile) &&
|
|
1808
|
-
!isInside(config.workspaceRoot, resolved)
|
|
1888
|
+
!isInside(config.workspaceRoot, resolved)
|
|
1809
1889
|
// The `importerFile !== file` term above keeps this to imports the write pass misses: the entry's
|
|
1810
1890
|
// OWN local imports are already walked by writeDevModuleUncached. Everything deeper - a relative
|
|
1811
1891
|
// sibling of a compat file esbuild inlined into the entry - is nobody's job but this one, whether
|
|
@@ -1818,23 +1898,23 @@ async function resolveDevModuleSpecifier(
|
|
|
1818
1898
|
// pointing at the raw source instead would load a SECOND copy of the compat runtime graph and split its
|
|
1819
1899
|
// module state. writeDevModule dedupes in-flight builds per artifact path, so a module reached from
|
|
1820
1900
|
// several bundled importers is compiled once.
|
|
1821
|
-
await writeDevModule(config, resolved, new Set(), options)
|
|
1901
|
+
await writeDevModule(config, resolved, new Set(), options)
|
|
1822
1902
|
}
|
|
1823
1903
|
return {
|
|
1824
1904
|
path: `${pathToFileHref(await devModulePath(config, resolved, options))}${hash}`,
|
|
1825
1905
|
external: true,
|
|
1826
|
-
}
|
|
1906
|
+
}
|
|
1827
1907
|
}
|
|
1828
1908
|
|
|
1829
1909
|
function serverRequireAlias(specifier: string, alias: string, options: DevModuleOptions) {
|
|
1830
|
-
if (options.conditionTarget === 'client') return alias
|
|
1910
|
+
if (options.conditionTarget === 'client') return alias
|
|
1831
1911
|
if (specifier === 'next/navigation' && alias.endsWith(`${path.sep}navigation.ts`)) {
|
|
1832
|
-
return path.join(path.dirname(alias), 'navigation.cjs')
|
|
1912
|
+
return path.join(path.dirname(alias), 'navigation.cjs')
|
|
1833
1913
|
}
|
|
1834
1914
|
if (specifier === 'next/router' && alias.endsWith(`${path.sep}router.ts`)) {
|
|
1835
|
-
return path.join(path.dirname(alias), 'router.cjs')
|
|
1915
|
+
return path.join(path.dirname(alias), 'router.cjs')
|
|
1836
1916
|
}
|
|
1837
|
-
return alias
|
|
1917
|
+
return alias
|
|
1838
1918
|
}
|
|
1839
1919
|
|
|
1840
1920
|
function registerExternalLoadHandlers(
|
|
@@ -1844,34 +1924,34 @@ function registerExternalLoadHandlers(
|
|
|
1844
1924
|
namespace: string,
|
|
1845
1925
|
) {
|
|
1846
1926
|
build.onResolve({ filter: /.*/, namespace }, args => {
|
|
1847
|
-
const { sourcePath } = splitHash(args.path)
|
|
1927
|
+
const { sourcePath } = splitHash(args.path)
|
|
1848
1928
|
const importer = args.importer.startsWith(`${namespace}:`)
|
|
1849
1929
|
? args.importer.slice(namespace.length + 1)
|
|
1850
|
-
: args.importer
|
|
1851
|
-
const compatAlias = options.aliases[sourcePath]
|
|
1930
|
+
: args.importer
|
|
1931
|
+
const compatAlias = options.aliases[sourcePath]
|
|
1852
1932
|
if (compatAlias) {
|
|
1853
1933
|
return {
|
|
1854
1934
|
path: path.isAbsolute(compatAlias) ? pathToFileHref(compatAlias) : compatAlias,
|
|
1855
1935
|
external: true,
|
|
1856
|
-
}
|
|
1936
|
+
}
|
|
1857
1937
|
}
|
|
1858
1938
|
const target = resolveExternalLoadTarget({
|
|
1859
1939
|
root: config.root,
|
|
1860
1940
|
fromFile: importer,
|
|
1861
1941
|
specifier: sourcePath,
|
|
1862
1942
|
target: options.externalLoadTarget,
|
|
1863
|
-
})
|
|
1864
|
-
if (target) return { path: target, namespace }
|
|
1943
|
+
})
|
|
1944
|
+
if (target) return { path: target, namespace }
|
|
1865
1945
|
if (sourcePath.startsWith('.') || path.isAbsolute(sourcePath)) {
|
|
1866
|
-
return { path: path.resolve(path.dirname(importer), sourcePath), namespace }
|
|
1946
|
+
return { path: path.resolve(path.dirname(importer), sourcePath), namespace }
|
|
1867
1947
|
}
|
|
1868
|
-
return { path: args.path, external: true }
|
|
1869
|
-
})
|
|
1948
|
+
return { path: args.path, external: true }
|
|
1949
|
+
})
|
|
1870
1950
|
build.onLoad({ filter: /.*/, namespace }, async args => ({
|
|
1871
1951
|
contents: rewriteExternalLoadSpecifiers(await readText(args.path), args.path, config, options),
|
|
1872
1952
|
loader: esbuildLoader(args.path),
|
|
1873
1953
|
resolveDir: path.dirname(args.path),
|
|
1874
|
-
}))
|
|
1954
|
+
}))
|
|
1875
1955
|
}
|
|
1876
1956
|
|
|
1877
1957
|
function rewriteExternalLoadSpecifiers(
|
|
@@ -1883,74 +1963,74 @@ function rewriteExternalLoadSpecifiers(
|
|
|
1883
1963
|
// Folded onto the module record (PERF-REWRITES #28); side-effect imports keep
|
|
1884
1964
|
// resolving through the host, as under the `from`-anchored regex.
|
|
1885
1965
|
return rewriteSpecifierLiterals(source, file, (specifier, kind) => {
|
|
1886
|
-
if (kind === 'side-effect' || !specifier.startsWith('#')) return undefined
|
|
1966
|
+
if (kind === 'side-effect' || !specifier.startsWith('#')) return undefined
|
|
1887
1967
|
const target = resolveExternalLoadTarget({
|
|
1888
1968
|
root: config.root,
|
|
1889
1969
|
fromFile: file,
|
|
1890
1970
|
specifier,
|
|
1891
1971
|
target: options.externalLoadTarget,
|
|
1892
|
-
})
|
|
1893
|
-
return target ? JSON.stringify(target) : undefined
|
|
1894
|
-
})
|
|
1972
|
+
})
|
|
1973
|
+
return target ? JSON.stringify(target) : undefined
|
|
1974
|
+
})
|
|
1895
1975
|
}
|
|
1896
1976
|
|
|
1897
1977
|
async function externalLoadNeedsFacade(file: string) {
|
|
1898
|
-
const source = await readText(file).catch(() => '')
|
|
1899
|
-
return /\bfrom\s*['"]#|\b(?:import|require)\s*\(\s*['"]#/.test(source)
|
|
1978
|
+
const source = await readText(file).catch(() => '')
|
|
1979
|
+
return /\bfrom\s*['"]#|\b(?:import|require)\s*\(\s*['"]#/.test(source)
|
|
1900
1980
|
}
|
|
1901
1981
|
|
|
1902
1982
|
function addExtensionlessDynamicImportGlobAliases(source: string) {
|
|
1903
|
-
if (!source.includes('__glob({')) return source
|
|
1904
|
-
const lines = source.split('\n')
|
|
1905
|
-
const next: string[] = []
|
|
1906
|
-
let inGlob = false
|
|
1907
|
-
const keys = new Set<string>()
|
|
1983
|
+
if (!source.includes('__glob({')) return source
|
|
1984
|
+
const lines = source.split('\n')
|
|
1985
|
+
const next: string[] = []
|
|
1986
|
+
let inGlob = false
|
|
1987
|
+
const keys = new Set<string>()
|
|
1908
1988
|
|
|
1909
1989
|
for (const line of lines) {
|
|
1910
1990
|
if (line.includes('__glob({')) {
|
|
1911
|
-
inGlob = true
|
|
1912
|
-
keys.clear()
|
|
1991
|
+
inGlob = true
|
|
1992
|
+
keys.clear()
|
|
1913
1993
|
}
|
|
1914
1994
|
if (inGlob) {
|
|
1915
|
-
const key = globKey(line)
|
|
1916
|
-
if (key) keys.add(key)
|
|
1995
|
+
const key = globKey(line)
|
|
1996
|
+
if (key) keys.add(key)
|
|
1917
1997
|
}
|
|
1918
|
-
next.push(line)
|
|
1998
|
+
next.push(line)
|
|
1919
1999
|
if (inGlob) {
|
|
1920
|
-
const aliases = globAliases(line, keys)
|
|
2000
|
+
const aliases = globAliases(line, keys)
|
|
1921
2001
|
if (aliases.length > 0) {
|
|
1922
|
-
if (!line.endsWith(',')) next[next.length - 1] = `${line}
|
|
1923
|
-
const value = line.replace(/^(\s*)"[^"]+":\s*/, '')
|
|
1924
|
-
const normalizedValue = value.endsWith(',') ? value.slice(0, -1) : value
|
|
2002
|
+
if (!line.endsWith(',')) next[next.length - 1] = `${line},`
|
|
2003
|
+
const value = line.replace(/^(\s*)"[^"]+":\s*/, '')
|
|
2004
|
+
const normalizedValue = value.endsWith(',') ? value.slice(0, -1) : value
|
|
1925
2005
|
for (const alias of aliases) {
|
|
1926
|
-
keys.add(alias)
|
|
1927
|
-
next.push(`${line.match(/^\s*/)?.[0] ?? ''}${JSON.stringify(alias)}: ${normalizedValue},`)
|
|
2006
|
+
keys.add(alias)
|
|
2007
|
+
next.push(`${line.match(/^\s*/)?.[0] ?? ''}${JSON.stringify(alias)}: ${normalizedValue},`)
|
|
1928
2008
|
}
|
|
1929
2009
|
}
|
|
1930
2010
|
}
|
|
1931
2011
|
if (inGlob && line.trim() === '});') {
|
|
1932
|
-
const previous = next[next.length - 2]
|
|
1933
|
-
if (previous?.endsWith(',')) next[next.length - 2] = previous.slice(0, -1)
|
|
1934
|
-
inGlob = false
|
|
2012
|
+
const previous = next[next.length - 2]
|
|
2013
|
+
if (previous?.endsWith(',')) next[next.length - 2] = previous.slice(0, -1)
|
|
2014
|
+
inGlob = false
|
|
1935
2015
|
}
|
|
1936
2016
|
}
|
|
1937
2017
|
|
|
1938
|
-
return next.join('\n')
|
|
2018
|
+
return next.join('\n')
|
|
1939
2019
|
}
|
|
1940
2020
|
|
|
1941
2021
|
function globKey(line: string) {
|
|
1942
|
-
return /^\s*"([^"]+)":\s*/.exec(line)?.[1]
|
|
2022
|
+
return /^\s*"([^"]+)":\s*/.exec(line)?.[1]
|
|
1943
2023
|
}
|
|
1944
2024
|
|
|
1945
2025
|
function globAliases(line: string, existing: Set<string>) {
|
|
1946
|
-
const key = globKey(line)
|
|
1947
|
-
if (!key) return []
|
|
1948
|
-
const extension = path.extname(key)
|
|
1949
|
-
if (!extensionlessDynamicImportExtensions.has(extension)) return []
|
|
1950
|
-
const aliases = [key.slice(0, -extension.length)]
|
|
1951
|
-
const indexSuffix = `/index${extension}
|
|
1952
|
-
if (key.endsWith(indexSuffix)) aliases.push(key.slice(0, -indexSuffix.length))
|
|
1953
|
-
return aliases.filter(alias => alias && !existing.has(alias))
|
|
2026
|
+
const key = globKey(line)
|
|
2027
|
+
if (!key) return []
|
|
2028
|
+
const extension = path.extname(key)
|
|
2029
|
+
if (!extensionlessDynamicImportExtensions.has(extension)) return []
|
|
2030
|
+
const aliases = [key.slice(0, -extension.length)]
|
|
2031
|
+
const indexSuffix = `/index${extension}`
|
|
2032
|
+
if (key.endsWith(indexSuffix)) aliases.push(key.slice(0, -indexSuffix.length))
|
|
2033
|
+
return aliases.filter(alias => alias && !existing.has(alias))
|
|
1954
2034
|
}
|
|
1955
2035
|
|
|
1956
2036
|
async function externalBundleSpecifiers(
|
|
@@ -1959,12 +2039,14 @@ async function externalBundleSpecifiers(
|
|
|
1959
2039
|
file: string,
|
|
1960
2040
|
options: DevModuleOptions,
|
|
1961
2041
|
) {
|
|
1962
|
-
const bundles = new Map<string, string>()
|
|
1963
|
-
if (!options.bundleExternalPackages) return bundles
|
|
2042
|
+
const bundles = new Map<string, string>()
|
|
2043
|
+
if (!options.bundleExternalPackages) return bundles
|
|
1964
2044
|
|
|
1965
|
-
const candidates = [
|
|
2045
|
+
const candidates = [
|
|
2046
|
+
...new Set(importSpecifiers(source, file).map(specifier => splitHash(specifier).sourcePath)),
|
|
2047
|
+
]
|
|
1966
2048
|
.filter(specifier => !isServerIgnoredAssetSpecifier(specifier))
|
|
1967
|
-
.filter(specifier => shouldBundleExternalPackage(specifier, config, file, options))
|
|
2049
|
+
.filter(specifier => shouldBundleExternalPackage(specifier, config, file, options))
|
|
1968
2050
|
await Promise.all(
|
|
1969
2051
|
candidates.map(async specifier => {
|
|
1970
2052
|
bundles.set(
|
|
@@ -1976,10 +2058,10 @@ async function externalBundleSpecifiers(
|
|
|
1976
2058
|
path.dirname(file),
|
|
1977
2059
|
options.conditionTarget,
|
|
1978
2060
|
),
|
|
1979
|
-
)
|
|
2061
|
+
)
|
|
1980
2062
|
}),
|
|
1981
|
-
)
|
|
1982
|
-
return bundles
|
|
2063
|
+
)
|
|
2064
|
+
return bundles
|
|
1983
2065
|
}
|
|
1984
2066
|
|
|
1985
2067
|
function shouldBundleExternalPackage(
|
|
@@ -1988,76 +2070,84 @@ function shouldBundleExternalPackage(
|
|
|
1988
2070
|
file: string,
|
|
1989
2071
|
options: DevModuleOptions,
|
|
1990
2072
|
) {
|
|
1991
|
-
if (options.aliases[specifier]) return false
|
|
1992
|
-
if (specifier === 'server-only') return false
|
|
1993
|
-
if (!isPackageSpecifier(specifier)) return false
|
|
1994
|
-
const resolved = resolveImport(config.root, file, specifier)
|
|
1995
|
-
const inWorkspace = resolved !== undefined && isInside(config.workspaceRoot, resolved)
|
|
2073
|
+
if (options.aliases[specifier]) return false
|
|
2074
|
+
if (specifier === 'server-only') return false
|
|
2075
|
+
if (!isPackageSpecifier(specifier)) return false
|
|
2076
|
+
const resolved = resolveImport(config.root, file, specifier)
|
|
2077
|
+
const inWorkspace = resolved !== undefined && isInside(config.workspaceRoot, resolved)
|
|
1996
2078
|
// transpilePackages force-bundle; serverExternalPackages force-external (never bundled). A
|
|
1997
2079
|
// transpiled package that resolves INSIDE the workspace is first-party source, not a registry
|
|
1998
2080
|
// copy: it belongs to the batched module pipeline, which compiles it per file instead of
|
|
1999
2081
|
// re-bundling its whole graph once per demanded subpath. Named imports survive that boundary
|
|
2000
2082
|
// because CommonJS deps are now externalized or faceted rather than inlined. "Vendor" is registry
|
|
2001
2083
|
// node_modules only.
|
|
2002
|
-
const packageName = packageNameOfSpecifier(specifier)
|
|
2084
|
+
const packageName = packageNameOfSpecifier(specifier)
|
|
2003
2085
|
if (packageName) {
|
|
2004
|
-
const policy = getExternalPackagePolicy()
|
|
2005
|
-
if (policy.transpile(packageName)) return !inWorkspace
|
|
2006
|
-
if (policy.external(packageName)) return false
|
|
2086
|
+
const policy = getExternalPackagePolicy()
|
|
2087
|
+
if (policy.transpile(packageName)) return !inWorkspace
|
|
2088
|
+
if (policy.external(packageName)) return false
|
|
2007
2089
|
}
|
|
2008
|
-
return !inWorkspace
|
|
2090
|
+
return !inWorkspace
|
|
2009
2091
|
}
|
|
2010
2092
|
|
|
2011
2093
|
function clientSsrAliases(config: ResolvedConfig) {
|
|
2012
2094
|
return {
|
|
2013
2095
|
...coreAliases(config, 'client'),
|
|
2014
2096
|
...getImportAliasExtensions().clientSsrAliases(config),
|
|
2015
|
-
}
|
|
2097
|
+
}
|
|
2016
2098
|
}
|
|
2017
2099
|
|
|
2018
2100
|
/** Absolute files of this module's dynamic(ssr:false) targets — the dev split points. */
|
|
2019
2101
|
function deferredDynamicTargetFiles(source: string, config: ResolvedConfig, file: string) {
|
|
2020
|
-
const specifiers = deferredDynamicImportSpecifiers(source, file)
|
|
2021
|
-
if (specifiers.size === 0) return undefined
|
|
2022
|
-
const files = new Set<string>()
|
|
2102
|
+
const specifiers = deferredDynamicImportSpecifiers(source, file)
|
|
2103
|
+
if (specifiers.size === 0) return undefined
|
|
2104
|
+
const files = new Set<string>()
|
|
2023
2105
|
for (const specifier of specifiers) {
|
|
2024
|
-
const resolved = resolveLocalImport(config, file, specifier)
|
|
2025
|
-
if (resolved) files.add(path.resolve(resolved))
|
|
2106
|
+
const resolved = resolveLocalImport(config, file, specifier)
|
|
2107
|
+
if (resolved) files.add(path.resolve(resolved))
|
|
2026
2108
|
}
|
|
2027
|
-
return files.size > 0 ? files : undefined
|
|
2109
|
+
return files.size > 0 ? files : undefined
|
|
2028
2110
|
}
|
|
2029
2111
|
|
|
2030
|
-
function localImportTargets(
|
|
2031
|
-
|
|
2112
|
+
function localImportTargets(
|
|
2113
|
+
source: string,
|
|
2114
|
+
config: ResolvedConfig,
|
|
2115
|
+
file: string,
|
|
2116
|
+
aliases: AliasMap,
|
|
2117
|
+
) {
|
|
2118
|
+
const targets: { target: string; isLocalSource: boolean }[] = []
|
|
2032
2119
|
for (const specifier of importSpecifiers(source, file)) {
|
|
2033
2120
|
// Compat-aliased specifiers (next/server etc.) are rewritten at build
|
|
2034
2121
|
// time; never resolve them locally — inside a workspace that ships a real
|
|
2035
2122
|
// `next` package they would drag its whole module graph into the recursion.
|
|
2036
|
-
const { sourcePath } = splitHash(specifier)
|
|
2037
|
-
if (aliases[specifier] || aliases[sourcePath]) continue
|
|
2123
|
+
const { sourcePath } = splitHash(specifier)
|
|
2124
|
+
if (aliases[specifier] || aliases[sourcePath]) continue
|
|
2038
2125
|
// Non-css ignored assets (scss, fonts, images) are stubbed at build time;
|
|
2039
2126
|
// compiling them as modules would fail the import scan. Css keeps its
|
|
2040
2127
|
// copy-through so compiled css module paths stay importable.
|
|
2041
|
-
if (isServerIgnoredAssetSpecifier(sourcePath) && !isCssFile(sourcePath)) continue
|
|
2042
|
-
const resolved = resolveImport(config.root, file, sourcePath)
|
|
2128
|
+
if (isServerIgnoredAssetSpecifier(sourcePath) && !isCssFile(sourcePath)) continue
|
|
2129
|
+
const resolved = resolveImport(config.root, file, sourcePath)
|
|
2043
2130
|
// `resolveExtensions` can land an extensionless specifier on an asset
|
|
2044
2131
|
// (`import img from './image'` -> image.png), which the specifier check
|
|
2045
2132
|
// above misses — compiling the image bytes as a module fails the build.
|
|
2046
|
-
if (resolved && isServerIgnoredAssetSpecifier(resolved) && !isCssFile(resolved)) continue
|
|
2133
|
+
if (resolved && isServerIgnoredAssetSpecifier(resolved) && !isCssFile(resolved)) continue
|
|
2047
2134
|
// A loader-rule source is not a module either: its chain output is
|
|
2048
2135
|
// materialized by the resolvers, so compiling the raw file would only fail
|
|
2049
2136
|
// the parse (`*.txt`, `*.test-file.ts`, …).
|
|
2050
|
-
if (resolved && getAssetExtensions().hasLoaderRuleFor(resolved)) continue
|
|
2137
|
+
if (resolved && getAssetExtensions().hasLoaderRuleFor(resolved)) continue
|
|
2051
2138
|
// Our own emitted artifacts (a tree-shake facade the source above was just rewritten to point at, a
|
|
2052
2139
|
// vendor bundle) are already compiled modules Bun imports directly. Feeding one back through the
|
|
2053
2140
|
// pipeline compiles it a SECOND time, splitting module identity and pulling artifact names into the
|
|
2054
2141
|
// source graph, where every boot's fresh names look like edits.
|
|
2055
2142
|
if (resolved && !isInside(config.outPath, resolved)) {
|
|
2056
|
-
targets.push({
|
|
2143
|
+
targets.push({
|
|
2144
|
+
target: resolved,
|
|
2145
|
+
isLocalSource: sourcePath.startsWith('.') || path.isAbsolute(sourcePath),
|
|
2146
|
+
})
|
|
2057
2147
|
}
|
|
2058
2148
|
}
|
|
2059
2149
|
|
|
2060
|
-
return targets
|
|
2150
|
+
return targets
|
|
2061
2151
|
}
|
|
2062
2152
|
|
|
2063
2153
|
async function writeClientReferenceModules(
|
|
@@ -2066,57 +2156,57 @@ async function writeClientReferenceModules(
|
|
|
2066
2156
|
file: string,
|
|
2067
2157
|
aliases: AliasMap,
|
|
2068
2158
|
) {
|
|
2069
|
-
const references = new Map<string, { file: string; exports: Set<string> }>()
|
|
2159
|
+
const references = new Map<string, { file: string; exports: Set<string> }>()
|
|
2070
2160
|
|
|
2071
2161
|
for (const specifier of importSpecifiers(source, file)) {
|
|
2072
|
-
const { sourcePath } = splitHash(specifier)
|
|
2073
|
-
if (aliases[specifier] || aliases[sourcePath]) continue
|
|
2074
|
-
const resolved = resolveImport(config.root, file, sourcePath)
|
|
2075
|
-
if (!resolved || !isInside(config.workspaceRoot, resolved) || isCssFile(resolved)) continue
|
|
2076
|
-
if (!(await fileHasUseClientDirective(resolved))) continue
|
|
2162
|
+
const { sourcePath } = splitHash(specifier)
|
|
2163
|
+
if (aliases[specifier] || aliases[sourcePath]) continue
|
|
2164
|
+
const resolved = resolveImport(config.root, file, sourcePath)
|
|
2165
|
+
if (!resolved || !isInside(config.workspaceRoot, resolved) || isCssFile(resolved)) continue
|
|
2166
|
+
if (!(await fileHasUseClientDirective(resolved))) continue
|
|
2077
2167
|
|
|
2078
|
-
const entry = references.get(sourcePath) ?? { file: resolved, exports: new Set<string>() }
|
|
2168
|
+
const entry = references.get(sourcePath) ?? { file: resolved, exports: new Set<string>() }
|
|
2079
2169
|
for (const exportName of await clientReferenceExportNames(config, resolved)) {
|
|
2080
|
-
entry.exports.add(exportName)
|
|
2170
|
+
entry.exports.add(exportName)
|
|
2081
2171
|
}
|
|
2082
|
-
references.set(sourcePath, entry)
|
|
2172
|
+
references.set(sourcePath, entry)
|
|
2083
2173
|
}
|
|
2084
2174
|
|
|
2085
|
-
const specifiers = new Map<string, string>()
|
|
2086
|
-
const files = new Set<string>()
|
|
2175
|
+
const specifiers = new Map<string, string>()
|
|
2176
|
+
const files = new Set<string>()
|
|
2087
2177
|
await Promise.all(
|
|
2088
2178
|
[...references.entries()].map(async ([specifier, reference]) => {
|
|
2089
|
-
const stub = await clientReferencePath(config, reference.file)
|
|
2090
|
-
await writeClientReferenceModule(stub, reference.file, [...reference.exports].sort())
|
|
2091
|
-
specifiers.set(specifier, stub)
|
|
2092
|
-
files.add(path.resolve(reference.file))
|
|
2179
|
+
const stub = await clientReferencePath(config, reference.file)
|
|
2180
|
+
await writeClientReferenceModule(stub, reference.file, [...reference.exports].sort())
|
|
2181
|
+
specifiers.set(specifier, stub)
|
|
2182
|
+
files.add(path.resolve(reference.file))
|
|
2093
2183
|
}),
|
|
2094
|
-
)
|
|
2184
|
+
)
|
|
2095
2185
|
|
|
2096
|
-
return { specifiers, files }
|
|
2186
|
+
return { specifiers, files }
|
|
2097
2187
|
}
|
|
2098
2188
|
|
|
2099
2189
|
async function writeClientReferenceModule(file: string, sourceFile: string, exportNames: string[]) {
|
|
2100
|
-
await mkdir(path.dirname(file), { recursive: true })
|
|
2101
|
-
await writeCompiledFile(file, clientReferenceModuleSource(sourceFile, exportNames))
|
|
2190
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
2191
|
+
await writeCompiledFile(file, clientReferenceModuleSource(sourceFile, exportNames))
|
|
2102
2192
|
}
|
|
2103
2193
|
|
|
2104
2194
|
async function fileHasUseClientDirective(file: string) {
|
|
2105
|
-
return hasUseClientDirective(await readText(file))
|
|
2195
|
+
return hasUseClientDirective(await readText(file))
|
|
2106
2196
|
}
|
|
2107
2197
|
|
|
2108
2198
|
function splitHash(specifier: string) {
|
|
2109
2199
|
// A leading '#' is a subpath-import/tsconfig-paths specifier (e.g. '#/lib/x'),
|
|
2110
2200
|
// not a fragment separator.
|
|
2111
|
-
const index = specifier.startsWith('#') ? specifier.indexOf('#', 1) : specifier.indexOf('#')
|
|
2201
|
+
const index = specifier.startsWith('#') ? specifier.indexOf('#', 1) : specifier.indexOf('#')
|
|
2112
2202
|
return index === -1
|
|
2113
2203
|
? { sourcePath: specifier, hash: '' }
|
|
2114
|
-
: { sourcePath: specifier.slice(0, index), hash: specifier.slice(index) }
|
|
2204
|
+
: { sourcePath: specifier.slice(0, index), hash: specifier.slice(index) }
|
|
2115
2205
|
}
|
|
2116
2206
|
|
|
2117
2207
|
function isInside(root: string, file: string) {
|
|
2118
|
-
const relative = path.relative(root, file)
|
|
2119
|
-
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
2208
|
+
const relative = path.relative(root, file)
|
|
2209
|
+
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
2120
2210
|
}
|
|
2121
2211
|
|
|
2122
2212
|
function devModuleProfile(options: DevModuleOptions) {
|
|
@@ -2126,35 +2216,35 @@ function devModuleProfile(options: DevModuleOptions) {
|
|
|
2126
2216
|
// compiled modules (and baked-in vendor hrefs) in separate cache dirs.
|
|
2127
2217
|
return options.conditionTarget === 'client'
|
|
2128
2218
|
? options.profile
|
|
2129
|
-
: `client-${options.conditionTarget}
|
|
2219
|
+
: `client-${options.conditionTarget}`
|
|
2130
2220
|
}
|
|
2131
2221
|
const externalTarget =
|
|
2132
2222
|
options.externalLoadTarget === externalLoadTargetForConditionTarget(options.conditionTarget)
|
|
2133
2223
|
? ''
|
|
2134
|
-
: `-external-${options.externalLoadTarget}
|
|
2135
|
-
return `${options.profile}-${options.conditionTarget}${externalTarget}${options.reactServerLayer ? '-react-server' : ''}
|
|
2224
|
+
: `-external-${options.externalLoadTarget}`
|
|
2225
|
+
return `${options.profile}-${options.conditionTarget}${externalTarget}${options.reactServerLayer ? '-react-server' : ''}`
|
|
2136
2226
|
}
|
|
2137
2227
|
|
|
2138
2228
|
// Compiled artifacts are named `<source name>.<graph hash>.<ext>` under a per-profile directory that
|
|
2139
2229
|
// mirrors the source tree. The hash covers the module's whole source graph, so an edit renames the edited
|
|
2140
2230
|
// file and its dependents and nothing else - a stale artifact is simply never asked for.
|
|
2141
2231
|
function devServerPath(config: ResolvedConfig, file: string, profile: string, hash: string) {
|
|
2142
|
-
const key = devServerCacheKey(config, file)
|
|
2143
|
-
const ext = path.extname(key) || '.js'
|
|
2232
|
+
const key = devServerCacheKey(config, file)
|
|
2233
|
+
const ext = path.extname(key) || '.js'
|
|
2144
2234
|
// A compiled `.json` module is JS (esbuild's json loader wraps it in exports),
|
|
2145
2235
|
// so it must not keep the `.json` name — the runtime would parse the artifact
|
|
2146
2236
|
// as JSON. Keeping the source name in front of `.js` keeps it collision-free.
|
|
2147
|
-
const outExt = ext === '.json' ? '.json.js' : ext
|
|
2237
|
+
const outExt = ext === '.json' ? '.json.js' : ext
|
|
2148
2238
|
return path.join(
|
|
2149
2239
|
cacheRoot(config.outPath),
|
|
2150
2240
|
profile === 'server' ? 'modules' : `modules-${profile}`,
|
|
2151
2241
|
`${key.slice(0, key.length - ext.length)}.${hash}${outExt}`,
|
|
2152
|
-
)
|
|
2242
|
+
)
|
|
2153
2243
|
}
|
|
2154
2244
|
|
|
2155
2245
|
/** The staged directory mirroring `dir` for `profile` (asset links live there). */
|
|
2156
2246
|
function devServerDir(config: ResolvedConfig, dir: string, profile: string) {
|
|
2157
|
-
return path.dirname(devServerPath(config, path.join(dir, '__pnext_context__.js'), profile, 'dir'))
|
|
2247
|
+
return path.dirname(devServerPath(config, path.join(dir, '__pnext_context__.js'), profile, 'dir'))
|
|
2158
2248
|
}
|
|
2159
2249
|
|
|
2160
2250
|
// A module compiled standalone (not merely as a transitive import of an
|
|
@@ -2168,23 +2258,23 @@ function devServerDir(config: ResolvedConfig, dir: string, profile: string) {
|
|
|
2168
2258
|
// location once written there). Key those out-of-workspace files by a stable
|
|
2169
2259
|
// hash of their absolute path instead of a relative path.
|
|
2170
2260
|
function devServerCacheKey(config: ResolvedConfig, file: string) {
|
|
2171
|
-
if (isInside(config.workspaceRoot, file)) return path.relative(config.workspaceRoot, file)
|
|
2261
|
+
if (isInside(config.workspaceRoot, file)) return path.relative(config.workspaceRoot, file)
|
|
2172
2262
|
// Hash the portable identity, not the absolute path: a deployed build runs
|
|
2173
2263
|
// these same files from a different root and must find the same artifact.
|
|
2174
2264
|
const hash = createHash('sha256')
|
|
2175
2265
|
.update(devSourceIdentity(file, config.workspaceRoot))
|
|
2176
2266
|
.digest('hex')
|
|
2177
|
-
.slice(0, 16)
|
|
2178
|
-
return path.join('external', `${hash}${path.extname(file) || '.js'}`)
|
|
2267
|
+
.slice(0, 16)
|
|
2268
|
+
return path.join('external', `${hash}${path.extname(file) || '.js'}`)
|
|
2179
2269
|
}
|
|
2180
2270
|
|
|
2181
2271
|
// Bundle names memoized per (route, entries) so a warm request re-derives
|
|
2182
2272
|
// nothing; any save clears the whole map (see clearDevRouteBundleKeys).
|
|
2183
|
-
const routeBundleKeys = new Map<string, Promise<string>>()
|
|
2273
|
+
const routeBundleKeys = new Map<string, Promise<string>>()
|
|
2184
2274
|
|
|
2185
2275
|
/** A save may rename any route bundle: drop every memoized name. */
|
|
2186
2276
|
export function clearDevRouteBundleKeys() {
|
|
2187
|
-
routeBundleKeys.clear()
|
|
2277
|
+
routeBundleKeys.clear()
|
|
2188
2278
|
}
|
|
2189
2279
|
|
|
2190
2280
|
// The route bundle inlines the route file and its layouts, so its name carries
|
|
@@ -2194,16 +2284,16 @@ function devRouteBundlePath(
|
|
|
2194
2284
|
route: RouteManifestEntry,
|
|
2195
2285
|
layoutFiles: string[],
|
|
2196
2286
|
) {
|
|
2197
|
-
const entries = uniqueFiles([route.file, ...layoutFiles])
|
|
2198
|
-
const memoKey = `${route.id}\0${entries.join('\0')}
|
|
2199
|
-
const memoized = routeBundleKeys.get(memoKey)
|
|
2200
|
-
if (memoized) return memoized
|
|
2287
|
+
const entries = uniqueFiles([route.file, ...layoutFiles])
|
|
2288
|
+
const memoKey = `${route.id}\0${entries.join('\0')}`
|
|
2289
|
+
const memoized = routeBundleKeys.get(memoKey)
|
|
2290
|
+
if (memoized) return memoized
|
|
2201
2291
|
const next = computeDevRouteBundlePath(config, route, entries).catch(error => {
|
|
2202
|
-
routeBundleKeys.delete(memoKey)
|
|
2203
|
-
throw error
|
|
2204
|
-
})
|
|
2205
|
-
routeBundleKeys.set(memoKey, next)
|
|
2206
|
-
return next
|
|
2292
|
+
routeBundleKeys.delete(memoKey)
|
|
2293
|
+
throw error
|
|
2294
|
+
})
|
|
2295
|
+
routeBundleKeys.set(memoKey, next)
|
|
2296
|
+
return next
|
|
2207
2297
|
}
|
|
2208
2298
|
|
|
2209
2299
|
async function computeDevRouteBundlePath(
|
|
@@ -2211,12 +2301,12 @@ async function computeDevRouteBundlePath(
|
|
|
2211
2301
|
route: RouteManifestEntry,
|
|
2212
2302
|
entries: string[],
|
|
2213
2303
|
) {
|
|
2214
|
-
const graph = devModuleGraph(config)
|
|
2215
|
-
const cached = await cachedRouteBundlePath(config, route.id, graph.graphKey, entries)
|
|
2216
|
-
if (cached) return cached
|
|
2217
|
-
const hashes = await Promise.all(entries.map(file => graph.graphHash(file)))
|
|
2218
|
-
const hash = createHash('sha256').update(hashes.join('\0')).digest('hex').slice(0, 16)
|
|
2219
|
-
const bundle = path.join(cacheRoot(config.outPath), 'routes', `${route.id}.${hash}.mjs`)
|
|
2304
|
+
const graph = devModuleGraph(config)
|
|
2305
|
+
const cached = await cachedRouteBundlePath(config, route.id, graph.graphKey, entries)
|
|
2306
|
+
if (cached) return cached
|
|
2307
|
+
const hashes = await Promise.all(entries.map(file => graph.graphHash(file)))
|
|
2308
|
+
const hash = createHash('sha256').update(hashes.join('\0')).digest('hex').slice(0, 16)
|
|
2309
|
+
const bundle = path.join(cacheRoot(config.outPath), 'routes', `${route.id}.${hash}.mjs`)
|
|
2220
2310
|
saveRouteBundlePath(
|
|
2221
2311
|
config,
|
|
2222
2312
|
route.id,
|
|
@@ -2224,82 +2314,85 @@ async function computeDevRouteBundlePath(
|
|
|
2224
2314
|
entries,
|
|
2225
2315
|
bundle,
|
|
2226
2316
|
await graph.graphSources(entries),
|
|
2227
|
-
)
|
|
2228
|
-
return bundle
|
|
2317
|
+
)
|
|
2318
|
+
return bundle
|
|
2229
2319
|
}
|
|
2230
2320
|
|
|
2231
2321
|
async function clientReferencePath(config: ResolvedConfig, file: string) {
|
|
2232
|
-
return `${await devModulePathFor(config, file, 'compat')}.client-reference.ts
|
|
2322
|
+
return `${await devModulePathFor(config, file, 'compat')}.client-reference.ts`
|
|
2233
2323
|
}
|
|
2234
2324
|
|
|
2235
2325
|
function isCssFile(file: string) {
|
|
2236
|
-
return file.endsWith('.css')
|
|
2326
|
+
return file.endsWith('.css')
|
|
2237
2327
|
}
|
|
2238
2328
|
|
|
2239
2329
|
function isStaticImageFile(file: string) {
|
|
2240
|
-
return /\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/.test(file)
|
|
2330
|
+
return /\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/.test(file)
|
|
2241
2331
|
}
|
|
2242
2332
|
|
|
2243
2333
|
const serverIgnoredAssetFilter =
|
|
2244
|
-
/\.(?:css|scss|sass|woff2?|ttf|otf|eot|png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])
|
|
2334
|
+
/\.(?:css|scss|sass|woff2?|ttf|otf|eot|png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/
|
|
2245
2335
|
|
|
2246
2336
|
function isServerIgnoredAssetSpecifier(specifier: string) {
|
|
2247
|
-
return serverIgnoredAssetFilter.test(specifier)
|
|
2337
|
+
return serverIgnoredAssetFilter.test(specifier)
|
|
2248
2338
|
}
|
|
2249
2339
|
|
|
2250
2340
|
function esbuildLoader(file: string) {
|
|
2251
|
-
if (file.endsWith('.tsx')) return 'tsx'
|
|
2252
|
-
if (file.endsWith('.ts')) return 'ts'
|
|
2341
|
+
if (file.endsWith('.tsx')) return 'tsx'
|
|
2342
|
+
if (file.endsWith('.ts')) return 'ts'
|
|
2253
2343
|
// `import data from './x.json'` is an ordinary module edge, so the walk
|
|
2254
2344
|
// reaches the data file itself; parsing it as JS fails on the first key.
|
|
2255
|
-
if (file.endsWith('.json')) return 'json'
|
|
2345
|
+
if (file.endsWith('.json')) return 'json'
|
|
2256
2346
|
// App-convention .js/.jsx/.mjs modules may contain JSX (jsxImportSource is
|
|
2257
2347
|
// preact via the build config), so parse them with the jsx loader. .tsx/.ts
|
|
2258
2348
|
// keep their dedicated loaders above for the fast path.
|
|
2259
|
-
return 'jsx'
|
|
2349
|
+
return 'jsx'
|
|
2260
2350
|
}
|
|
2261
2351
|
|
|
2262
2352
|
function isPackageSpecifier(specifier: string) {
|
|
2263
|
-
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:'))
|
|
2264
|
-
|
|
2265
|
-
if (specifier.startsWith('
|
|
2266
|
-
if (specifier.
|
|
2267
|
-
|
|
2268
|
-
|
|
2353
|
+
if (specifier.startsWith('.') || specifier.startsWith('/') || specifier.startsWith('file:'))
|
|
2354
|
+
return false
|
|
2355
|
+
if (specifier.startsWith('#')) return false
|
|
2356
|
+
if (specifier.startsWith('node:')) return false
|
|
2357
|
+
if (specifier.includes(':')) return false
|
|
2358
|
+
const packageName = packageNameFromSpecifier(specifier)
|
|
2359
|
+
return Boolean(packageName && !builtinModules.includes(packageName))
|
|
2269
2360
|
}
|
|
2270
2361
|
|
|
2271
2362
|
function isBuiltinSpecifier(specifier: string) {
|
|
2272
|
-
if (specifier.startsWith('node:')) return true
|
|
2273
|
-
const packageName = packageNameFromSpecifier(specifier)
|
|
2274
|
-
return Boolean(packageName && builtinModules.includes(packageName))
|
|
2363
|
+
if (specifier.startsWith('node:')) return true
|
|
2364
|
+
const packageName = packageNameFromSpecifier(specifier)
|
|
2365
|
+
return Boolean(packageName && builtinModules.includes(packageName))
|
|
2275
2366
|
}
|
|
2276
2367
|
|
|
2277
2368
|
function packageNameFromSpecifier(specifier: string) {
|
|
2278
|
-
const parts = specifier.split('/')
|
|
2279
|
-
return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
2369
|
+
const parts = specifier.split('/')
|
|
2370
|
+
return specifier.startsWith('@') ? parts.slice(0, 2).join('/') : parts[0]
|
|
2280
2371
|
}
|
|
2281
2372
|
|
|
2282
2373
|
function resolveLocalImport(config: ResolvedConfig, fromFile: string, specifier: string) {
|
|
2283
|
-
if (path.isAbsolute(specifier)) return existsSync(specifier) ? specifier : undefined
|
|
2284
|
-
return resolveImport(config.root, fromFile, specifier)
|
|
2374
|
+
if (path.isAbsolute(specifier)) return existsSync(specifier) ? specifier : undefined
|
|
2375
|
+
return resolveImport(config.root, fromFile, specifier)
|
|
2285
2376
|
}
|
|
2286
2377
|
|
|
2287
2378
|
async function linkAssetContext(config: ResolvedConfig, file: string, profile: string) {
|
|
2288
|
-
if (!isInside(config.root, file)) return
|
|
2289
|
-
const sourceDirs = ancestorDirs(config.root, path.dirname(file))
|
|
2290
|
-
await Promise.all(
|
|
2379
|
+
if (!isInside(config.root, file)) return
|
|
2380
|
+
const sourceDirs = ancestorDirs(config.root, path.dirname(file))
|
|
2381
|
+
await Promise.all(
|
|
2382
|
+
sourceDirs.map(sourceDir => linkDirectoryAssetContext(config, sourceDir, profile)),
|
|
2383
|
+
)
|
|
2291
2384
|
}
|
|
2292
2385
|
|
|
2293
2386
|
function ancestorDirs(root: string, dir: string) {
|
|
2294
|
-
const dirs = [root]
|
|
2295
|
-
const relative = path.relative(root, dir)
|
|
2296
|
-
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return dirs
|
|
2297
|
-
let current = root
|
|
2387
|
+
const dirs = [root]
|
|
2388
|
+
const relative = path.relative(root, dir)
|
|
2389
|
+
if (!relative || relative.startsWith('..') || path.isAbsolute(relative)) return dirs
|
|
2390
|
+
let current = root
|
|
2298
2391
|
for (const segment of relative.split(path.sep).filter(Boolean)) {
|
|
2299
|
-
current = path.join(current, segment)
|
|
2300
|
-
dirs.push(current)
|
|
2392
|
+
current = path.join(current, segment)
|
|
2393
|
+
dirs.push(current)
|
|
2301
2394
|
}
|
|
2302
|
-
return dirs
|
|
2395
|
+
return dirs
|
|
2303
2396
|
}
|
|
2304
2397
|
|
|
2305
2398
|
async function linkDirectoryAssetContext(
|
|
@@ -2307,35 +2400,35 @@ async function linkDirectoryAssetContext(
|
|
|
2307
2400
|
sourceDir: string,
|
|
2308
2401
|
profile: string,
|
|
2309
2402
|
) {
|
|
2310
|
-
let entries: Dirent<string>[]
|
|
2403
|
+
let entries: Dirent<string>[]
|
|
2311
2404
|
try {
|
|
2312
|
-
entries = await readdir(sourceDir, { withFileTypes: true, encoding: 'utf8' })
|
|
2405
|
+
entries = await readdir(sourceDir, { withFileTypes: true, encoding: 'utf8' })
|
|
2313
2406
|
} catch {
|
|
2314
|
-
return
|
|
2407
|
+
return
|
|
2315
2408
|
}
|
|
2316
|
-
const targetDir = devServerDir(config, sourceDir, profile)
|
|
2409
|
+
const targetDir = devServerDir(config, sourceDir, profile)
|
|
2317
2410
|
await Promise.all(
|
|
2318
2411
|
entries.map(async entry => {
|
|
2319
|
-
if (!assetContextEntry(config, sourceDir, entry.name, entry.isDirectory())) return
|
|
2320
|
-
const source = path.join(sourceDir, entry.name)
|
|
2321
|
-
const target = path.join(targetDir, entry.name)
|
|
2412
|
+
if (!assetContextEntry(config, sourceDir, entry.name, entry.isDirectory())) return
|
|
2413
|
+
const source = path.join(sourceDir, entry.name)
|
|
2414
|
+
const target = path.join(targetDir, entry.name)
|
|
2322
2415
|
// Root-level sibling asset directories are mirrored, not symlinked wholesale: a `dir` symlink
|
|
2323
2416
|
// would make every path beneath it - including code modules that must be compiled through the
|
|
2324
2417
|
// alias transform - resolve straight back to raw source, shadowing the staged compile.
|
|
2325
2418
|
// Mirroring links only non-code leaf files.
|
|
2326
2419
|
if (entry.isDirectory()) {
|
|
2327
|
-
await mirrorAssetDirectory(source, target)
|
|
2328
|
-
return
|
|
2420
|
+
await mirrorAssetDirectory(source, target)
|
|
2421
|
+
return
|
|
2329
2422
|
}
|
|
2330
|
-
if (existsSync(target)) return
|
|
2331
|
-
await mkdir(path.dirname(target), { recursive: true })
|
|
2423
|
+
if (existsSync(target)) return
|
|
2424
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
2332
2425
|
try {
|
|
2333
|
-
await symlink(source, target, 'file')
|
|
2426
|
+
await symlink(source, target, 'file')
|
|
2334
2427
|
} catch {
|
|
2335
|
-
if (!existsSync(target)) await copyFile(source, target)
|
|
2428
|
+
if (!existsSync(target)) await copyFile(source, target)
|
|
2336
2429
|
}
|
|
2337
2430
|
}),
|
|
2338
|
-
)
|
|
2431
|
+
)
|
|
2339
2432
|
}
|
|
2340
2433
|
|
|
2341
2434
|
// Recursively stage an asset directory: link non-code leaf files and recurse
|
|
@@ -2343,31 +2436,31 @@ async function linkDirectoryAssetContext(
|
|
|
2343
2436
|
// module graph into their own staged output and must not be shadowed by a raw
|
|
2344
2437
|
// symlink.
|
|
2345
2438
|
async function mirrorAssetDirectory(sourceDir: string, targetDir: string) {
|
|
2346
|
-
let entries: Dirent<string>[]
|
|
2439
|
+
let entries: Dirent<string>[]
|
|
2347
2440
|
try {
|
|
2348
|
-
entries = await readdir(sourceDir, { withFileTypes: true, encoding: 'utf8' })
|
|
2441
|
+
entries = await readdir(sourceDir, { withFileTypes: true, encoding: 'utf8' })
|
|
2349
2442
|
} catch {
|
|
2350
|
-
return
|
|
2443
|
+
return
|
|
2351
2444
|
}
|
|
2352
2445
|
await Promise.all(
|
|
2353
2446
|
entries.map(async entry => {
|
|
2354
|
-
if (entry.name.startsWith('.')) return
|
|
2355
|
-
const source = path.join(sourceDir, entry.name)
|
|
2356
|
-
const target = path.join(targetDir, entry.name)
|
|
2447
|
+
if (entry.name.startsWith('.')) return
|
|
2448
|
+
const source = path.join(sourceDir, entry.name)
|
|
2449
|
+
const target = path.join(targetDir, entry.name)
|
|
2357
2450
|
if (entry.isDirectory()) {
|
|
2358
|
-
await mirrorAssetDirectory(source, target)
|
|
2359
|
-
return
|
|
2451
|
+
await mirrorAssetDirectory(source, target)
|
|
2452
|
+
return
|
|
2360
2453
|
}
|
|
2361
|
-
if (isCodeFile(entry.name)) return
|
|
2362
|
-
if (existsSync(target)) return
|
|
2363
|
-
await mkdir(path.dirname(target), { recursive: true })
|
|
2454
|
+
if (isCodeFile(entry.name)) return
|
|
2455
|
+
if (existsSync(target)) return
|
|
2456
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
2364
2457
|
try {
|
|
2365
|
-
await symlink(source, target, 'file')
|
|
2458
|
+
await symlink(source, target, 'file')
|
|
2366
2459
|
} catch {
|
|
2367
|
-
if (!existsSync(target)) await copyFile(source, target)
|
|
2460
|
+
if (!existsSync(target)) await copyFile(source, target)
|
|
2368
2461
|
}
|
|
2369
2462
|
}),
|
|
2370
|
-
)
|
|
2463
|
+
)
|
|
2371
2464
|
}
|
|
2372
2465
|
|
|
2373
2466
|
function assetContextEntry(
|
|
@@ -2376,9 +2469,9 @@ function assetContextEntry(
|
|
|
2376
2469
|
name: string,
|
|
2377
2470
|
directory: boolean,
|
|
2378
2471
|
) {
|
|
2379
|
-
if (name.startsWith('.')) return false
|
|
2380
|
-
if (!directory) return !isCodeFile(name)
|
|
2381
|
-
if (sourceDir !== config.root) return false
|
|
2472
|
+
if (name.startsWith('.')) return false
|
|
2473
|
+
if (!directory) return !isCodeFile(name)
|
|
2474
|
+
if (sourceDir !== config.root) return false
|
|
2382
2475
|
return !new Set([
|
|
2383
2476
|
'app',
|
|
2384
2477
|
'src',
|
|
@@ -2386,40 +2479,40 @@ function assetContextEntry(
|
|
|
2386
2479
|
'public',
|
|
2387
2480
|
'node_modules',
|
|
2388
2481
|
path.basename(config.outRootPath),
|
|
2389
|
-
]).has(name)
|
|
2482
|
+
]).has(name)
|
|
2390
2483
|
}
|
|
2391
2484
|
|
|
2392
2485
|
function isCodeFile(file: string) {
|
|
2393
|
-
if (/\.(?:tsx?|jsx?|mjs|cjs)$/.test(file)) return true
|
|
2486
|
+
if (/\.(?:tsx?|jsx?|mjs|cjs)$/.test(file)) return true
|
|
2394
2487
|
// Extra loadable source extensions registered by compat (e.g. mdx/md) compile
|
|
2395
2488
|
// through the module graph, so they must not be symlinked raw as asset
|
|
2396
2489
|
// context — otherwise the raw markdown shadows the compiled staged module.
|
|
2397
|
-
const dot = file.lastIndexOf('.')
|
|
2398
|
-
if (dot < 0) return false
|
|
2399
|
-
return extraLoadableExtensions().includes(file.slice(dot + 1))
|
|
2490
|
+
const dot = file.lastIndexOf('.')
|
|
2491
|
+
if (dot < 0) return false
|
|
2492
|
+
return extraLoadableExtensions().includes(file.slice(dot + 1))
|
|
2400
2493
|
}
|
|
2401
2494
|
|
|
2402
2495
|
function resolveAssetPath(specifier: string, resolveDir: string) {
|
|
2403
|
-
const { sourcePath, hash } = splitHash(specifier)
|
|
2404
|
-
return `${path.isAbsolute(sourcePath) ? sourcePath : path.resolve(resolveDir, sourcePath)}${hash}
|
|
2496
|
+
const { sourcePath, hash } = splitHash(specifier)
|
|
2497
|
+
return `${path.isAbsolute(sourcePath) ? sourcePath : path.resolve(resolveDir, sourcePath)}${hash}`
|
|
2405
2498
|
}
|
|
2406
2499
|
|
|
2407
2500
|
async function staticImageModuleSource(config: ResolvedConfig, file: string) {
|
|
2408
|
-
const { sourcePath } = splitHash(file)
|
|
2409
|
-
const bytes = new Uint8Array(await Bun.file(sourcePath).arrayBuffer())
|
|
2410
|
-
const emitted: string[] = []
|
|
2501
|
+
const { sourcePath } = splitHash(file)
|
|
2502
|
+
const bytes = new Uint8Array(await Bun.file(sourcePath).arrayBuffer())
|
|
2503
|
+
const emitted: string[] = []
|
|
2411
2504
|
const emit = (relative: string) => {
|
|
2412
|
-
emitted.push(relative)
|
|
2413
|
-
return `/${relative}
|
|
2414
|
-
}
|
|
2415
|
-
const compat = await getAssetExtensions().staticAssetModule({ sourcePath, bytes, emit })
|
|
2416
|
-
const source = compat ?? coreStaticAssetModule(sourcePath, bytes, emit)
|
|
2505
|
+
emitted.push(relative)
|
|
2506
|
+
return `/${relative}`
|
|
2507
|
+
}
|
|
2508
|
+
const compat = await getAssetExtensions().staticAssetModule({ sourcePath, bytes, emit })
|
|
2509
|
+
const source = compat ?? coreStaticAssetModule(sourcePath, bytes, emit)
|
|
2417
2510
|
for (const relative of emitted) {
|
|
2418
|
-
const target = path.join(config.outPath, 'public', ...relative.split('/'))
|
|
2419
|
-
await mkdir(path.dirname(target), { recursive: true })
|
|
2420
|
-
if (!existsSync(target)) await copyFile(sourcePath, target)
|
|
2511
|
+
const target = path.join(config.outPath, 'public', ...relative.split('/'))
|
|
2512
|
+
await mkdir(path.dirname(target), { recursive: true })
|
|
2513
|
+
if (!existsSync(target)) await copyFile(sourcePath, target)
|
|
2421
2514
|
}
|
|
2422
|
-
return source
|
|
2515
|
+
return source
|
|
2423
2516
|
}
|
|
2424
2517
|
|
|
2425
2518
|
// Core's generic static-asset module (no compat override): emit under a hashed
|
|
@@ -2429,31 +2522,25 @@ function coreStaticAssetModule(
|
|
|
2429
2522
|
bytes: Uint8Array,
|
|
2430
2523
|
emit: (relative: string, bytes: Uint8Array) => string,
|
|
2431
2524
|
): string {
|
|
2432
|
-
const ext = path.extname(sourcePath).toLowerCase() || '.bin'
|
|
2433
|
-
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8)
|
|
2434
|
-
const base = path.basename(sourcePath, path.extname(sourcePath)).replace(/[^A-Za-z0-9_-]+/g, '-')
|
|
2435
|
-
const relative = getAssetExtensions().staticAssetRelativePath({ sourcePath, hash, base, ext })
|
|
2436
|
-
const src = emit(relative, bytes)
|
|
2437
|
-
return `const src = ${JSON.stringify(src)};\nexport default src;\nexport { src };\n
|
|
2525
|
+
const ext = path.extname(sourcePath).toLowerCase() || '.bin'
|
|
2526
|
+
const hash = createHash('sha256').update(bytes).digest('hex').slice(0, 8)
|
|
2527
|
+
const base = path.basename(sourcePath, path.extname(sourcePath)).replace(/[^A-Za-z0-9_-]+/g, '-')
|
|
2528
|
+
const relative = getAssetExtensions().staticAssetRelativePath({ sourcePath, hash, base, ext })
|
|
2529
|
+
const src = emit(relative, bytes)
|
|
2530
|
+
return `const src = ${JSON.stringify(src)};\nexport default src;\nexport { src };\n`
|
|
2438
2531
|
}
|
|
2439
2532
|
|
|
2440
2533
|
function uniqueFiles(files: string[]) {
|
|
2441
|
-
return [...new Set(files.map(file => path.resolve(file)).filter(file => existsSync(file)))]
|
|
2534
|
+
return [...new Set(files.map(file => path.resolve(file)).filter(file => existsSync(file)))]
|
|
2442
2535
|
}
|
|
2443
2536
|
|
|
2444
2537
|
async function profileDevImport<T>(label: string, task: () => Promise<T>) {
|
|
2445
2538
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
2446
|
-
if (!process.env.PNEXT_DEV_PROFILE) return task()
|
|
2447
|
-
const start = performance.now()
|
|
2539
|
+
if (!process.env.PNEXT_DEV_PROFILE) return task()
|
|
2540
|
+
const start = performance.now()
|
|
2448
2541
|
try {
|
|
2449
|
-
return await task()
|
|
2542
|
+
return await task()
|
|
2450
2543
|
} finally {
|
|
2451
|
-
console.log(`dev-import ${label} in ${formatDuration(performance.now() - start)}`)
|
|
2544
|
+
console.log(`dev-import ${label} in ${formatDuration(performance.now() - start)}`)
|
|
2452
2545
|
}
|
|
2453
2546
|
}
|
|
2454
|
-
|
|
2455
|
-
function formatDuration(durationMs: number) {
|
|
2456
|
-
if (durationMs < 10) return `${durationMs.toFixed(1)}ms`;
|
|
2457
|
-
if (durationMs < 1000) return `${Math.round(durationMs)}ms`;
|
|
2458
|
-
return `${(durationMs / 1000).toFixed(durationMs < 10000 ? 2 : 1)}s`;
|
|
2459
|
-
}
|