@wular/pnext 0.0.1
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 +153 -0
- package/bin/pnext +67 -0
- package/config/lint/base.js +48 -0
- package/config/ts/base.json +26 -0
- package/config/ts/react.json +13 -0
- package/package.json +70 -0
- package/reference/compat.md +63 -0
- package/reference/config.md +120 -0
- package/reference/css.md +58 -0
- package/reference/dev.md +69 -0
- package/reference/env.md +40 -0
- package/reference/metadata.md +86 -0
- package/reference/navigation.md +149 -0
- package/reference/overview.md +35 -0
- package/reference/performance.md +97 -0
- package/reference/rendering.md +127 -0
- package/reference/routing.md +167 -0
- package/reference/typegen.md +64 -0
- package/src/api/cache.ts +80 -0
- package/src/api/client-cache.ts +9 -0
- package/src/api/client-navigation.ts +279 -0
- package/src/api/dynamic.tsx +102 -0
- package/src/api/link.tsx +119 -0
- package/src/api/navigation.ts +198 -0
- package/src/api/router/events.ts +53 -0
- package/src/api/router/history.ts +70 -0
- package/src/api/router/hub.ts +194 -0
- package/src/api/router/policies.ts +107 -0
- package/src/api/router/runtime.ts +5238 -0
- package/src/api/router/types.ts +299 -0
- package/src/api/router.ts +167 -0
- package/src/api/server.ts +323 -0
- package/src/api/suspense.ts +16 -0
- package/src/cache/context.ts +61 -0
- package/src/cli/adapters/vercel-warm.ts +375 -0
- package/src/cli/adapters/vercel.ts +1310 -0
- package/src/cli/analyze-print.ts +181 -0
- package/src/cli/analyze.ts +328 -0
- package/src/cli/boot-trace.ts +29 -0
- package/src/cli/build.ts +3114 -0
- package/src/cli/dev.ts +276 -0
- package/src/cli/index.ts +196 -0
- package/src/cli/named-bin.ts +119 -0
- package/src/cli/serve-ui.ts +160 -0
- package/src/cli/start.ts +1425 -0
- package/src/client/build.ts +2136 -0
- package/src/client/chunk-fold.ts +526 -0
- package/src/client/entry.ts +1525 -0
- package/src/client/paths.ts +22 -0
- package/src/client/prebuilt.ts +621 -0
- package/src/client/profile.ts +75 -0
- package/src/client/react-compiler.ts +94 -0
- package/src/client/reference-stub.ts +145 -0
- package/src/client/reference.ts +47 -0
- package/src/compat/actions/action-client.ts +531 -0
- package/src/compat/actions/action-dispatch.ts +676 -0
- package/src/compat/actions/action-router.ts +40 -0
- package/src/compat/actions/action-shared.ts +95 -0
- package/src/compat/actions/client-plugin.ts +135 -0
- package/src/compat/actions/client-stub.ts +65 -0
- package/src/compat/actions/config.ts +164 -0
- package/src/compat/actions/detect.ts +208 -0
- package/src/compat/actions/discovery.ts +244 -0
- package/src/compat/actions/early-submit.ts +40 -0
- package/src/compat/actions/endpoint.ts +602 -0
- package/src/compat/actions/flight.ts +52 -0
- package/src/compat/actions/form-state.ts +73 -0
- package/src/compat/actions/hoist.ts +485 -0
- package/src/compat/actions/ids.ts +39 -0
- package/src/compat/actions/index.ts +41 -0
- package/src/compat/actions/instances.ts +144 -0
- package/src/compat/actions/origin.ts +109 -0
- package/src/compat/actions/protocol.ts +125 -0
- package/src/compat/actions/registry.ts +74 -0
- package/src/compat/actions/rewrite.ts +282 -0
- package/src/compat/actions/serve.ts +414 -0
- package/src/compat/actions/server-tag.ts +21 -0
- package/src/compat/actions/unrecognized-error.ts +30 -0
- package/src/compat/adapter/build-complete.ts +257 -0
- package/src/compat/bundler/bun-externals.ts +53 -0
- package/src/compat/bundler/cjs-exports.ts +542 -0
- package/src/compat/bundler/config.ts +363 -0
- package/src/compat/bundler/externals.ts +34 -0
- package/src/compat/bundler/import-meta-url.ts +60 -0
- package/src/compat/bundler/modularize-imports.ts +119 -0
- package/src/compat/bundler/new-url-asset.ts +87 -0
- package/src/compat/bundler/optimize-package-imports.ts +273 -0
- package/src/compat/bundler/polyfill.ts +88 -0
- package/src/compat/bundler/react-compiler.ts +61 -0
- package/src/compat/bundler/react-profiler.tsx +25 -0
- package/src/compat/bundler/relay-transform.ts +116 -0
- package/src/compat/bundler/require-context.ts +281 -0
- package/src/compat/bundler/resolve-extensions.ts +75 -0
- package/src/compat/bundler/source-cache.ts +61 -0
- package/src/compat/bundler/static-imports.ts +25 -0
- package/src/compat/bundler/symlink-imports.ts +119 -0
- package/src/compat/bundler/tsconfig-paths.ts +50 -0
- package/src/compat/bundler/wasm.ts +153 -0
- package/src/compat/bundler/webpack-loaders.ts +685 -0
- package/src/compat/bundler/worker.ts +278 -0
- package/src/compat/cache/build-flags.ts +81 -0
- package/src/compat/cache/build-prerender-errors.ts +163 -0
- package/src/compat/cache/custom-handler.ts +159 -0
- package/src/compat/cache/fetch-patch.ts +745 -0
- package/src/compat/cache/handler.ts +100 -0
- package/src/compat/cache/modern-handler.ts +275 -0
- package/src/compat/cache/resume-data-cache.ts +143 -0
- package/src/compat/cache/revalidate.ts +759 -0
- package/src/compat/cache/runtime-error.ts +124 -0
- package/src/compat/cache/use-cache-transform.ts +961 -0
- package/src/compat/cache/use-cache.ts +1695 -0
- package/src/compat/cache-control.ts +269 -0
- package/src/compat/client/base-path.ts +64 -0
- package/src/compat/client/css-order.ts +36 -0
- package/src/compat/client/errors/control-flow.ts +92 -0
- package/src/compat/client/errors/error-boundary.ts +222 -0
- package/src/compat/client/errors/global-error.ts +238 -0
- package/src/compat/client/errors/install.ts +217 -0
- package/src/compat/client/errors/lazy.ts +53 -0
- package/src/compat/client/errors/primitive-throw.ts +126 -0
- package/src/compat/client/errors/soft-refresh.ts +14 -0
- package/src/compat/client/link-status.ts +86 -0
- package/src/compat/client/nav-compat-runtime.ts +57 -0
- package/src/compat/client/nav-compat.ts +42 -0
- package/src/compat/client/navigation-scroll.ts +154 -0
- package/src/compat/client/optimistic-routing.ts +206 -0
- package/src/compat/client/prefetch-cache.ts +111 -0
- package/src/compat/client/route-announcer.ts +72 -0
- package/src/compat/client/segment-cache-policy.ts +159 -0
- package/src/compat/client/segment-cache.ts +1077 -0
- package/src/compat/client/segment-prefetch.ts +375 -0
- package/src/compat/client/trailing-slash.ts +24 -0
- package/src/compat/css/chunking.ts +254 -0
- package/src/compat/css/inline-css.ts +73 -0
- package/src/compat/css/lightningcss.ts +90 -0
- package/src/compat/css/modules.ts +373 -0
- package/src/compat/css/nonce.ts +30 -0
- package/src/compat/css/sass-plugin.ts +65 -0
- package/src/compat/css/sass.ts +392 -0
- package/src/compat/css/styled-jsx-runtime.ts +80 -0
- package/src/compat/css/styled-jsx.ts +49 -0
- package/src/compat/edge-runtime.ts +71 -0
- package/src/compat/export/client.ts +112 -0
- package/src/compat/export/index.ts +272 -0
- package/src/compat/export/standalone.ts +207 -0
- package/src/compat/image-optimizer/cache.ts +119 -0
- package/src/compat/image-optimizer/detect.ts +143 -0
- package/src/compat/image-optimizer/index.ts +601 -0
- package/src/compat/image-optimizer/source.ts +243 -0
- package/src/compat/index.ts +458 -0
- package/src/compat/lifecycle/after-scope.ts +86 -0
- package/src/compat/lifecycle/after.ts +173 -0
- package/src/compat/lifecycle/error-funnel.ts +306 -0
- package/src/compat/lifecycle/error-serialize.ts +87 -0
- package/src/compat/lifecycle/error-ui.ts +167 -0
- package/src/compat/lifecycle/instrumentation-client.ts +138 -0
- package/src/compat/lifecycle/instrumentation.ts +277 -0
- package/src/compat/lifecycle/node-console.ts +19 -0
- package/src/compat/lifecycle/testmode.ts +263 -0
- package/src/compat/mdx/compile.ts +219 -0
- package/src/compat/mdx/next-mdx-stub.ts +46 -0
- package/src/compat/mdx/plugin.ts +37 -0
- package/src/compat/metadata-route-artifacts.ts +458 -0
- package/src/compat/metadata.ts +295 -0
- package/src/compat/middleware/manifest.ts +210 -0
- package/src/compat/misc/action-return.ts +173 -0
- package/src/compat/next/cache.ts +211 -0
- package/src/compat/next/canonical-url.ts +35 -0
- package/src/compat/next/client-cache.ts +57 -0
- package/src/compat/next/client-navigation.ts +313 -0
- package/src/compat/next/client-only.ts +3 -0
- package/src/compat/next/client-script.tsx +215 -0
- package/src/compat/next/client-server.ts +39 -0
- package/src/compat/next/config-loader.ts +569 -0
- package/src/compat/next/config.ts +29 -0
- package/src/compat/next/constants.cjs +6 -0
- package/src/compat/next/constants.ts +6 -0
- package/src/compat/next/custom-server.ts +236 -0
- package/src/compat/next/dist/client/components/app-router-headers.ts +32 -0
- package/src/compat/next/dist/server/app-render/work-unit-async-storage.external.cjs +38 -0
- package/src/compat/next/dist/server/web/spec-extension/revalidate.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-cache.ts +1 -0
- package/src/compat/next/dist/server/web/spec-extension/unstable-no-store.ts +1 -0
- package/src/compat/next/dynamic.tsx +46 -0
- package/src/compat/next/error.tsx +148 -0
- package/src/compat/next/font/cache.ts +171 -0
- package/src/compat/next/font/google.ts +2 -0
- package/src/compat/next/font/index.ts +8 -0
- package/src/compat/next/font/local.ts +5 -0
- package/src/compat/next/font/runtime-client.ts +71 -0
- package/src/compat/next/font/runtime.ts +974 -0
- package/src/compat/next/font/shared.ts +281 -0
- package/src/compat/next/form.tsx +156 -0
- package/src/compat/next/head.tsx +10 -0
- package/src/compat/next/headers.ts +247 -0
- package/src/compat/next/image/config.ts +196 -0
- package/src/compat/next/image/optimizer.ts +96 -0
- package/src/compat/next/image/patterns.ts +103 -0
- package/src/compat/next/image/shared.ts +141 -0
- package/src/compat/next/image/static-metadata.ts +283 -0
- package/src/compat/next/image/validate.ts +269 -0
- package/src/compat/next/image-client.tsx +215 -0
- package/src/compat/next/image-props.ts +575 -0
- package/src/compat/next/image-usage.ts +102 -0
- package/src/compat/next/image.tsx +56 -0
- package/src/compat/next/index.ts +1 -0
- package/src/compat/next/legacy-image.tsx +97 -0
- package/src/compat/next/link-usage.ts +29 -0
- package/src/compat/next/link-validation-transform.ts +200 -0
- package/src/compat/next/link.tsx +466 -0
- package/src/compat/next/navigation.cjs +21 -0
- package/src/compat/next/navigation.ts +188 -0
- package/src/compat/next/offline.ts +51 -0
- package/src/compat/next/og.ts +324 -0
- package/src/compat/next/optimistic-route-state.ts +188 -0
- package/src/compat/next/preferred-region.ts +39 -0
- package/src/compat/next/redirects.ts +131 -0
- package/src/compat/next/resource-hints.ts +136 -0
- package/src/compat/next/rewrites.ts +350 -0
- package/src/compat/next/root-params.ts +142 -0
- package/src/compat/next/router.cjs +49 -0
- package/src/compat/next/router.ts +143 -0
- package/src/compat/next/script.tsx +355 -0
- package/src/compat/next/server-only.ts +3 -0
- package/src/compat/next/server.ts +28 -0
- package/src/compat/next/svgr.ts +58 -0
- package/src/compat/next/telemetry.ts +77 -0
- package/src/compat/next/user-agent.ts +100 -0
- package/src/compat/next/web-vitals.ts +56 -0
- package/src/compat/otel/api.ts +95 -0
- package/src/compat/otel/client-trace-metadata.ts +71 -0
- package/src/compat/otel/fetch-span.ts +77 -0
- package/src/compat/otel/tracer.ts +944 -0
- package/src/compat/pages/client-plugin.ts +108 -0
- package/src/compat/pages/index.ts +527 -0
- package/src/compat/pages/router-state.ts +94 -0
- package/src/compat/ppr/io.ts +38 -0
- package/src/compat/ppr/missing-root-params.ts +105 -0
- package/src/compat/ppr/root-params-scan.ts +164 -0
- package/src/compat/ppr/root-params-transform.ts +75 -0
- package/src/compat/ppr/root-params.ts +129 -0
- package/src/compat/ppr/segment-config-incompat.ts +34 -0
- package/src/compat/protocol.ts +202 -0
- package/src/compat/react/client.ts +59 -0
- package/src/compat/react/compiler-runtime.ts +60 -0
- package/src/compat/react/dom-client.ts +115 -0
- package/src/compat/react/dom-react-server.ts +20 -0
- package/src/compat/react/dom-server.ts +40 -0
- package/src/compat/react/dom.ts +154 -0
- package/src/compat/react/preact.ts +522 -0
- package/src/compat/react/react-server.ts +84 -0
- package/src/compat/react/router-shim.ts +26 -0
- package/src/compat/react/server-component-use.ts +48 -0
- package/src/compat/react/server-inserted-html.ts +87 -0
- package/src/compat/react/server.ts +156 -0
- package/src/compat/react/view-transition.ts +60 -0
- package/src/compat/register/actions.ts +875 -0
- package/src/compat/register/boot.ts +141 -0
- package/src/compat/register/build-tier.ts +11 -0
- package/src/compat/register/build.ts +182 -0
- package/src/compat/register/bundler.ts +587 -0
- package/src/compat/register/cache.ts +85 -0
- package/src/compat/register/client-errors.ts +18 -0
- package/src/compat/register/config.ts +17 -0
- package/src/compat/register/css-extras.ts +120 -0
- package/src/compat/register/edge-runtime.ts +6 -0
- package/src/compat/register/errors.ts +46 -0
- package/src/compat/register/export.ts +23 -0
- package/src/compat/register/font.ts +36 -0
- package/src/compat/register/hooks.ts +34 -0
- package/src/compat/register/image.ts +133 -0
- package/src/compat/register/index.ts +111 -0
- package/src/compat/register/instrumentation-client.ts +35 -0
- package/src/compat/register/lifecycle.ts +86 -0
- package/src/compat/register/mdx.ts +48 -0
- package/src/compat/register/middleware.ts +36 -0
- package/src/compat/register/misc.ts +44 -0
- package/src/compat/register/otel.ts +288 -0
- package/src/compat/register/pages-api.ts +473 -0
- package/src/compat/register/ppr.ts +56 -0
- package/src/compat/register/protocol.ts +57 -0
- package/src/compat/register/proxy.ts +127 -0
- package/src/compat/register/render.ts +268 -0
- package/src/compat/register/routing.ts +410 -0
- package/src/compat/register/segment.ts +1903 -0
- package/src/compat/register/static-image.ts +21 -0
- package/src/compat/register/typed-routes.ts +35 -0
- package/src/compat/register/usecache.ts +131 -0
- package/src/compat/register/validation.ts +56 -0
- package/src/compat/segment/loading-boundary.ts +113 -0
- package/src/compat/segment/page-slot.ts +200 -0
- package/src/compat/segment/tree.ts +481 -0
- package/src/compat/segment/vary-key.ts +102 -0
- package/src/compat/segment/vary-params.ts +551 -0
- package/src/compat/static-params.ts +33 -0
- package/src/compat/tsconfig-defaults.ts +301 -0
- package/src/compat/typecheck/index.ts +1481 -0
- package/src/compat/typecheck/worker.ts +26 -0
- package/src/compat/typed-routes/index.ts +92 -0
- package/src/compat/typed-routes/manifest.ts +356 -0
- package/src/compat/typed-routes/typegen.ts +566 -0
- package/src/compat/validation/errors.ts +159 -0
- package/src/compat/validation/index.ts +1770 -0
- package/src/compat/validation/prerender-diagnostics.ts +1508 -0
- package/src/compat-bootstrap.ts +67 -0
- package/src/config.ts +218 -0
- package/src/css/build.ts +697 -0
- package/src/css/index.ts +2 -0
- package/src/css/postcss.ts +236 -0
- package/src/css/worker.ts +34 -0
- package/src/dev/client-actions.ts +35 -0
- package/src/dev/client-chunk-store.ts +92 -0
- package/src/dev/client-key-cache.ts +178 -0
- package/src/dev/global-css-cache.ts +212 -0
- package/src/dev/imports.ts +2430 -0
- package/src/dev/module-cache.ts +721 -0
- package/src/dev/module-generations.ts +38 -0
- package/src/dev/module-transform.ts +188 -0
- package/src/dev/node-module-bundle-cache.ts +63 -0
- package/src/dev/restart-cache.ts +10 -0
- package/src/dev/route-bundle-key-cache.ts +154 -0
- package/src/dev/route-facts-cache.ts +223 -0
- package/src/dev/server.ts +1710 -0
- package/src/dynamic/source.ts +307 -0
- package/src/dynamic/tree-shake.ts +262 -0
- package/src/env.ts +92 -0
- package/src/extensions.ts +1898 -0
- package/src/index.ts +34 -0
- package/src/internal.ts +43 -0
- package/src/islands/boundary-error.ts +8 -0
- package/src/islands/static-children.ts +37 -0
- package/src/islands/static-slots.ts +106 -0
- package/src/ppr-postpone.ts +24 -0
- package/src/ppr.ts +784 -0
- package/src/proxy.ts +752 -0
- package/src/render/hooks.ts +384 -0
- package/src/render/index.ts +1 -0
- package/src/render/island-context.ts +47 -0
- package/src/render/metadata.ts +857 -0
- package/src/render/renderer.ts +7391 -0
- package/src/render/resource-hints.ts +44 -0
- package/src/render/slots.tsx +679 -0
- package/src/request/context.ts +396 -0
- package/src/resolve/engine.ts +219 -0
- package/src/resolve/imports.ts +1104 -0
- package/src/resolve/scan-facts.ts +474 -0
- package/src/resolve/source-text.ts +86 -0
- package/src/routing/forwarded.ts +41 -0
- package/src/routing/handler.ts +271 -0
- package/src/routing/href.ts +203 -0
- package/src/routing/metadata.ts +1018 -0
- package/src/routing/request-runtime.ts +43 -0
- package/src/routing/routes.ts +2560 -0
- package/src/routing/slots.ts +432 -0
- package/src/runtime/server.ts +3453 -0
- package/src/runtime/vendor.ts +1160 -0
- package/src/style-modules.d.ts +9 -0
- package/src/typegen.ts +151 -0
- package/src/types.ts +725 -0
- package/src/utils/ansi.ts +9 -0
- package/src/utils/content-type.ts +31 -0
- package/src/utils/decode.ts +7 -0
- package/src/utils/dev-profile.ts +31 -0
- package/src/utils/error-log.ts +29 -0
- package/src/utils/fs-cache.ts +31 -0
- package/src/utils/fs.ts +119 -0
- package/src/utils/html.ts +46 -0
- package/src/utils/serialize.ts +378 -0
- package/src/utils/source.ts +35 -0
- package/src/utils/verbose.ts +39 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chunk-count fold for the initial tier: merges chunks that are provably
|
|
3
|
+
* co-loaded (see `coLoadGroups`), so a merge can never over-fetch. Mechanically
|
|
4
|
+
* an esbuild re-bundle of a renaming barrel plus an import rewrite in every
|
|
5
|
+
* chunk that referenced a member.
|
|
6
|
+
*/
|
|
7
|
+
import { createHash } from 'node:crypto';
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { unlink } from 'node:fs/promises';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { build, type BuildOptions, type Metafile } from 'esbuild';
|
|
12
|
+
import { clientProfile } from './profile';
|
|
13
|
+
import { readText, writeText } from '../utils/fs';
|
|
14
|
+
import { escapeRegex } from '../utils/source';
|
|
15
|
+
|
|
16
|
+
/** Unlinking an artifact a concurrent build already removed is not news. */
|
|
17
|
+
function ignoreMissing() {
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Import/export forms the rewrite understands; anything else vetoes the fold. */
|
|
22
|
+
const namedClause = String.raw`\{([^}]*)\}`;
|
|
23
|
+
|
|
24
|
+
interface FoldOptions {
|
|
25
|
+
outDir: string;
|
|
26
|
+
metafile: Metafile;
|
|
27
|
+
/** esbuild flags of the build being folded, so the merge is emitted the same way. */
|
|
28
|
+
buildOptions: Pick<BuildOptions, 'minify' | 'sourcemap' | 'target' | 'format'>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Merge the always-initial chunks of a finished client build. Returns a metafile
|
|
33
|
+
* with the merge applied, or the original untouched when nothing folds.
|
|
34
|
+
*/
|
|
35
|
+
export async function foldInitialChunks(options: FoldOptions): Promise<Metafile> {
|
|
36
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
37
|
+
if (process.env.PNEXT_CHUNK_FOLD === '0') return options.metafile;
|
|
38
|
+
// The cost this removes is compressed-transfer only (a gzip header and
|
|
39
|
+
// dictionary reset per chunk), so the pass rides the minify flag.
|
|
40
|
+
if (!options.buildOptions.minify) return options.metafile;
|
|
41
|
+
return foldChunks(options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Failure policy: a group that has not touched disk is skipped and reported (the
|
|
46
|
+
* build is correct without the fold); once it has, the metafile and the files on
|
|
47
|
+
* disk have diverged, so it rethrows. `PNEXT_CHUNK_FOLD=strict` throws for both.
|
|
48
|
+
*/
|
|
49
|
+
async function foldChunks({ outDir, metafile, buildOptions }: FoldOptions): Promise<Metafile> {
|
|
50
|
+
let folded = metafile;
|
|
51
|
+
const groups = clientProfile.time('foldGroups', () => coLoadGroups(metafile));
|
|
52
|
+
clientProfile.count('foldGroup#', groups.length);
|
|
53
|
+
clientProfile.count('foldOutput#', Object.keys(metafile.outputs).length);
|
|
54
|
+
if (groups.length === 0) return folded;
|
|
55
|
+
// Output text is read once and kept in step with what each group writes, and
|
|
56
|
+
// the same state indexes specifiers — so a group visits only the files naming
|
|
57
|
+
// one of its members instead of re-reading and re-filtering every output.
|
|
58
|
+
const state = await clientProfile.timeAsync('foldRead', () => createFoldState(outDir, metafile));
|
|
59
|
+
for (const group of groups) {
|
|
60
|
+
const mutation = { started: false };
|
|
61
|
+
try {
|
|
62
|
+
folded = await foldGroup({ outDir, metafile: folded, buildOptions }, group, mutation, state);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (mutation.started) throw error;
|
|
65
|
+
reportFoldFailure(group, error);
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return folded;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function reportFoldFailure(group: string[], error: unknown) {
|
|
73
|
+
const names = group.map(member => path.basename(member)).join(', ');
|
|
74
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
75
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
76
|
+
if (process.env.PNEXT_CHUNK_FOLD === 'strict') {
|
|
77
|
+
throw new Error(`client chunk fold failed for [${names}]: ${message}`);
|
|
78
|
+
}
|
|
79
|
+
console.warn(`pnext: client chunk fold skipped [${names}] — ${message}`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function foldGroup(
|
|
83
|
+
{ outDir, metafile, buildOptions }: FoldOptions,
|
|
84
|
+
members: string[],
|
|
85
|
+
mutation: { started: boolean },
|
|
86
|
+
state: FoldState,
|
|
87
|
+
): Promise<Metafile> {
|
|
88
|
+
if (members.length < 2) return metafile;
|
|
89
|
+
|
|
90
|
+
const chunksDir = path.join(outDir, 'chunks');
|
|
91
|
+
const memberNames = members.map(output => path.basename(output));
|
|
92
|
+
if (memberNames.some(name => !existsSync(path.join(chunksDir, name)))) return metafile;
|
|
93
|
+
|
|
94
|
+
const sources = new Map<string, string>();
|
|
95
|
+
for (const name of memberNames) sources.set(name, await readText(path.join(chunksDir, name)));
|
|
96
|
+
|
|
97
|
+
const aliases = memberAliases(memberNames, sources);
|
|
98
|
+
if (!aliases) return metafile;
|
|
99
|
+
|
|
100
|
+
// Every file that referenced a member has to be rewritten; if any of them
|
|
101
|
+
// uses a form the rewriter can't express (`import * as`, `export *`, a bare
|
|
102
|
+
// dynamic `import()` of a member), the fold is abandoned whole.
|
|
103
|
+
const rewrites = new Map<string, string>();
|
|
104
|
+
const rewrite = memberRewriter(memberNames, aliases, chunksDir);
|
|
105
|
+
const rewritable = clientProfile.time('foldRewrite', () => {
|
|
106
|
+
for (const file of state.referrersOf(memberNames)) {
|
|
107
|
+
if (memberNames.includes(path.basename(file))) continue;
|
|
108
|
+
const output = state.outputs.get(file);
|
|
109
|
+
if (!output) continue;
|
|
110
|
+
const next = rewrite(output.text, file);
|
|
111
|
+
if (next === null) return false;
|
|
112
|
+
if (next !== output.text) rewrites.set(file, next);
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
});
|
|
116
|
+
if (!rewritable) return metafile;
|
|
117
|
+
|
|
118
|
+
const merged = await clientProfile.timeAsync('foldBuild', () =>
|
|
119
|
+
buildMergedChunk(chunksDir, memberNames, aliases, buildOptions),
|
|
120
|
+
);
|
|
121
|
+
if (!merged) return metafile;
|
|
122
|
+
|
|
123
|
+
// Read-only up to here, so failing is skippable. From the first rewrite on the
|
|
124
|
+
// metafile and the files on disk must be kept in step, so failures are fatal.
|
|
125
|
+
mutation.started = true;
|
|
126
|
+
|
|
127
|
+
// The rewrites were produced against a placeholder href — the merged chunk's
|
|
128
|
+
// name is its own content hash and so isn't known until it is built.
|
|
129
|
+
await Promise.all(
|
|
130
|
+
[...rewrites].map(([file, source]) => {
|
|
131
|
+
const next = source.replaceAll(MERGED_PLACEHOLDER, merged.name);
|
|
132
|
+
state.set(file, foldOutput(next));
|
|
133
|
+
return writeText(file, next);
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
await Promise.all(
|
|
137
|
+
memberNames.flatMap(name => [
|
|
138
|
+
unlink(path.join(chunksDir, name)).catch(ignoreMissing),
|
|
139
|
+
unlink(path.join(chunksDir, `${name}.map`)).catch(ignoreMissing),
|
|
140
|
+
]),
|
|
141
|
+
);
|
|
142
|
+
for (const name of memberNames) state.remove(path.join(chunksDir, name));
|
|
143
|
+
// A later group can reference this merged chunk, and it is what the previous
|
|
144
|
+
// per-group re-walk picked up off disk; index it the same way.
|
|
145
|
+
state.set(path.join(chunksDir, merged.name), foldOutput(merged.text));
|
|
146
|
+
return mergeMetafile(metafile, members, path.join(chunksDir, merged.name), merged.bytes);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Chunks that are always downloaded together, grouped. Two chunks reachable from
|
|
151
|
+
* exactly the same load roots (route entries and `import()` targets) can merge
|
|
152
|
+
* without adding a byte to any load. Entries never fold - the shared chunk is
|
|
153
|
+
* what a lazily loaded island's CSS/runtime seam resolves against.
|
|
154
|
+
*/
|
|
155
|
+
function coLoadGroups(metafile: Metafile) {
|
|
156
|
+
const dynamicTargets = new Set<string>();
|
|
157
|
+
for (const output of Object.values(metafile.outputs)) {
|
|
158
|
+
for (const imported of output.imports ?? []) {
|
|
159
|
+
if (imported.kind === 'dynamic-import') dynamicTargets.add(imported.path);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// esbuild records an `entryPoint` for the chunk behind every `import()` too,
|
|
163
|
+
// so dynamic targets have to be subtracted before what is left counts as a
|
|
164
|
+
// route entry.
|
|
165
|
+
const entries = Object.keys(metafile.outputs).filter(
|
|
166
|
+
file => metafile.outputs[file]?.entryPoint && !dynamicTargets.has(file),
|
|
167
|
+
);
|
|
168
|
+
if (entries.length === 0) return [];
|
|
169
|
+
|
|
170
|
+
const available = availability(metafile, entries, dynamicTargets);
|
|
171
|
+
const roots = [...available.keys()];
|
|
172
|
+
const groups = new Map<string, string[]>();
|
|
173
|
+
for (const file of Object.keys(metafile.outputs)) {
|
|
174
|
+
if (!file.endsWith('.js')) continue;
|
|
175
|
+
if (entries.includes(file) || dynamicTargets.has(file)) continue;
|
|
176
|
+
const mask = roots.map(root => (available.get(root)!.has(file) ? '1' : '0')).join('');
|
|
177
|
+
// A chunk no root can reach is dead output; leave it alone.
|
|
178
|
+
if (!mask.includes('1')) continue;
|
|
179
|
+
(groups.get(mask) ?? groups.set(mask, []).get(mask)!).push(file);
|
|
180
|
+
}
|
|
181
|
+
return [...groups.values()]
|
|
182
|
+
.filter(
|
|
183
|
+
group =>
|
|
184
|
+
group.length > 1 &&
|
|
185
|
+
// Only groups that touch a first-paint tier are worth a re-bundle; a
|
|
186
|
+
// purely deferred group is off-budget and pays build time for bytes
|
|
187
|
+
// nobody waits on.
|
|
188
|
+
entries.some(entry => available.get(entry)!.has(group[0]!)),
|
|
189
|
+
)
|
|
190
|
+
.map(group => [...group].sort());
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* What is guaranteed to be on the page when `file` runs: a root answers for
|
|
195
|
+
* itself, any other chunk with the intersection over every root that reaches it.
|
|
196
|
+
*/
|
|
197
|
+
function availabilityAt(
|
|
198
|
+
available: Map<string, Set<string>>,
|
|
199
|
+
file: string,
|
|
200
|
+
memo: Map<string, Set<string>>,
|
|
201
|
+
) {
|
|
202
|
+
const own = available.get(file);
|
|
203
|
+
if (own) return own;
|
|
204
|
+
// The fixpoint below asks for the same non-root importers once per round per
|
|
205
|
+
// target, and each answer scans every root's availability set — quadratic in
|
|
206
|
+
// roots. The memo is cleared whenever a round changes an answer.
|
|
207
|
+
const cached = memo.get(file);
|
|
208
|
+
if (cached) return cached;
|
|
209
|
+
let acc: Set<string> | undefined;
|
|
210
|
+
for (const set of available.values()) {
|
|
211
|
+
if (!set.has(file)) continue;
|
|
212
|
+
acc = acc === undefined ? new Set(set) : intersect(acc, set);
|
|
213
|
+
}
|
|
214
|
+
const answer = acc ?? new Set([file]);
|
|
215
|
+
memo.set(file, answer);
|
|
216
|
+
return answer;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** Per load root, the chunks guaranteed to be on the page once it runs. */
|
|
220
|
+
function availability(metafile: Metafile, entries: string[], dynamicTargets: Set<string>) {
|
|
221
|
+
const available = new Map<string, Set<string>>();
|
|
222
|
+
const closures = new Map<string, Set<string>>();
|
|
223
|
+
for (const root of [...entries, ...dynamicTargets]) {
|
|
224
|
+
closures.set(root, staticClosure(metafile, root));
|
|
225
|
+
available.set(root, new Set(closures.get(root)));
|
|
226
|
+
}
|
|
227
|
+
const importersOf = new Map<string, string[]>();
|
|
228
|
+
for (const [file, output] of Object.entries(metafile.outputs)) {
|
|
229
|
+
for (const imported of output.imports ?? []) {
|
|
230
|
+
if (imported.kind !== 'dynamic-import') continue;
|
|
231
|
+
(importersOf.get(imported.path) ?? importersOf.set(imported.path, []).get(imported.path)!)
|
|
232
|
+
.push(file);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// `available` only ever grows, so iterating to a fixed point terminates; the
|
|
236
|
+
// bound is the number of roots (each round settles at least one).
|
|
237
|
+
const memo = new Map<string, Set<string>>();
|
|
238
|
+
for (let round = 0; round < dynamicTargets.size + 1; round += 1) {
|
|
239
|
+
let changed = false;
|
|
240
|
+
for (const target of dynamicTargets) {
|
|
241
|
+
const importers = importersOf.get(target) ?? [];
|
|
242
|
+
let inherited: Set<string> | undefined;
|
|
243
|
+
for (const importer of importers) {
|
|
244
|
+
const from = availabilityAt(available, importer, memo);
|
|
245
|
+
inherited = inherited === undefined ? new Set(from) : intersect(inherited, from);
|
|
246
|
+
}
|
|
247
|
+
const next = new Set([...closures.get(target)!, ...(inherited ?? [])]);
|
|
248
|
+
if (next.size !== available.get(target)!.size) {
|
|
249
|
+
available.set(target, next);
|
|
250
|
+
memo.clear();
|
|
251
|
+
changed = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (!changed) break;
|
|
255
|
+
}
|
|
256
|
+
return available;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function staticClosure(metafile: Metafile, root: string) {
|
|
260
|
+
const seen = new Set([root]);
|
|
261
|
+
const queue = [root];
|
|
262
|
+
// `queue` grows inside the loop — a breadth-first walk of the chunk graph.
|
|
263
|
+
for (const current of queue) {
|
|
264
|
+
for (const imported of metafile.outputs[current]?.imports ?? []) {
|
|
265
|
+
if (imported.kind !== 'import-statement') continue;
|
|
266
|
+
if (seen.has(imported.path)) continue;
|
|
267
|
+
seen.add(imported.path);
|
|
268
|
+
queue.push(imported.path);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return seen;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function intersect(a: Set<string>, b: Set<string>) {
|
|
275
|
+
return new Set([...a].filter(item => b.has(item)));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Per-member `exported name -> alias`. Chunk exports are minified per chunk and
|
|
280
|
+
* collide across chunks, so the barrel renames them apart.
|
|
281
|
+
*/
|
|
282
|
+
function memberAliases(memberNames: string[], sources: Map<string, string>) {
|
|
283
|
+
const aliases = new Map<string, Map<string, string>>();
|
|
284
|
+
for (const [index, name] of memberNames.entries()) {
|
|
285
|
+
const source = sources.get(name)!;
|
|
286
|
+
// A star re-export can't be aliased name-by-name; nothing to fold safely.
|
|
287
|
+
if (/export\s*\*/.test(source)) return undefined;
|
|
288
|
+
// The export clause is the chunk's last one — a linked sourcemap comment
|
|
289
|
+
// can follow it, so anchor on "last `export{…}` that isn't a re-export"
|
|
290
|
+
// rather than on end-of-file.
|
|
291
|
+
const clauses = [...source.matchAll(new RegExp(String.raw`export\s*${namedClause}\s*(?!from)`, 'g'))];
|
|
292
|
+
const clause = clauses.at(-1);
|
|
293
|
+
const names = new Map<string, string>();
|
|
294
|
+
for (const part of clause?.[1]?.split(',') ?? []) {
|
|
295
|
+
const exported = part.split(' as ').at(-1)?.trim();
|
|
296
|
+
if (exported) names.set(exported, `$${index}${exported}`);
|
|
297
|
+
}
|
|
298
|
+
aliases.set(name, names);
|
|
299
|
+
}
|
|
300
|
+
return aliases;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* The rewrite for one group, compiled once: points a file's member imports at the
|
|
305
|
+
* merged chunk, renaming each binding to its barrel alias. Answers `null` when a
|
|
306
|
+
* member is referenced through a form the fold cannot rewrite.
|
|
307
|
+
*/
|
|
308
|
+
function memberRewriter(
|
|
309
|
+
memberNames: string[],
|
|
310
|
+
aliases: Map<string, Map<string, string>>,
|
|
311
|
+
chunksDir: string,
|
|
312
|
+
) {
|
|
313
|
+
const memberPattern = memberNames.map(escapeRegex).join('|');
|
|
314
|
+
const specifier = String.raw`"\.[^"]*(?:${memberPattern})"`;
|
|
315
|
+
// Forms that can't be expressed against a renaming barrel.
|
|
316
|
+
const vetoes = [
|
|
317
|
+
new RegExp(String.raw`import\s*\*\s*as\s*[\w$]+\s*from\s*${specifier}`),
|
|
318
|
+
new RegExp(String.raw`export\s*\*[^;]*from\s*${specifier}`),
|
|
319
|
+
new RegExp(String.raw`import\(\s*${specifier}`),
|
|
320
|
+
];
|
|
321
|
+
const namedImport = new RegExp(
|
|
322
|
+
String.raw`(import|export)\s*${namedClause}\s*from\s*(${specifier})`,
|
|
323
|
+
'g',
|
|
324
|
+
);
|
|
325
|
+
const sideEffectImport = new RegExp(String.raw`import\s*(${specifier})`, 'g');
|
|
326
|
+
|
|
327
|
+
const nameOf = (spec: string) => memberNames.find(name => spec.includes(name))!;
|
|
328
|
+
const rename = (clause: string, member: string) =>
|
|
329
|
+
clause
|
|
330
|
+
.split(',')
|
|
331
|
+
.map(part => {
|
|
332
|
+
const [imported, local] = part.split(' as ').map(item => item.trim());
|
|
333
|
+
if (!imported) return part;
|
|
334
|
+
const alias = aliases.get(member)?.get(imported);
|
|
335
|
+
if (!alias) return part;
|
|
336
|
+
return `${alias} as ${local ?? imported}`;
|
|
337
|
+
})
|
|
338
|
+
.join(',');
|
|
339
|
+
|
|
340
|
+
return (source: string, file: string) => {
|
|
341
|
+
for (const veto of vetoes) if (veto.test(source)) return null;
|
|
342
|
+
const mergedHref = path
|
|
343
|
+
.relative(path.dirname(file), path.join(chunksDir, MERGED_PLACEHOLDER))
|
|
344
|
+
.split(path.sep)
|
|
345
|
+
.join('/');
|
|
346
|
+
const href = mergedHref.startsWith('.') ? mergedHref : `./${mergedHref}`;
|
|
347
|
+
return source
|
|
348
|
+
.replace(
|
|
349
|
+
namedImport,
|
|
350
|
+
(_all, keyword: string, clause: string, spec: string) =>
|
|
351
|
+
`${keyword}{${rename(clause, nameOf(spec))}}from"${href}"`,
|
|
352
|
+
)
|
|
353
|
+
// Side-effect-only imports of a member collapse onto the merged chunk.
|
|
354
|
+
.replace(sideEffectImport, () => `import"${href}"`);
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Stands in for the merged chunk's final (content-hashed) name. */
|
|
359
|
+
const MERGED_PLACEHOLDER = 'pnext-merged';
|
|
360
|
+
|
|
361
|
+
async function buildMergedChunk(
|
|
362
|
+
chunksDir: string,
|
|
363
|
+
memberNames: string[],
|
|
364
|
+
aliases: Map<string, Map<string, string>>,
|
|
365
|
+
buildOptions: FoldOptions['buildOptions'],
|
|
366
|
+
) {
|
|
367
|
+
const barrel = memberNames
|
|
368
|
+
.map(name => {
|
|
369
|
+
const names = aliases.get(name)!;
|
|
370
|
+
const spec = JSON.stringify(`./${name}`);
|
|
371
|
+
if (names.size === 0) return `import ${spec};`;
|
|
372
|
+
const clause = [...names].map(([exported, alias]) => `${exported} as ${alias}`).join(',');
|
|
373
|
+
return `export {${clause}} from ${spec};`;
|
|
374
|
+
})
|
|
375
|
+
.join('\n');
|
|
376
|
+
|
|
377
|
+
const barrelPath = path.join(chunksDir, '__pnext-fold-barrel.js');
|
|
378
|
+
await writeText(barrelPath, barrel);
|
|
379
|
+
try {
|
|
380
|
+
const result = await build({
|
|
381
|
+
minify: buildOptions.minify,
|
|
382
|
+
sourcemap: buildOptions.sourcemap,
|
|
383
|
+
target: buildOptions.target,
|
|
384
|
+
format: buildOptions.format ?? 'esm',
|
|
385
|
+
bundle: true,
|
|
386
|
+
entryPoints: [barrelPath],
|
|
387
|
+
outfile: path.join(chunksDir, '__pnext-fold-merged.js'),
|
|
388
|
+
write: false,
|
|
389
|
+
// Only the members are pulled in; every other chunk reference (including
|
|
390
|
+
// the `import()`s that keep the idle tier deferred) passes through as-is.
|
|
391
|
+
plugins: [
|
|
392
|
+
{
|
|
393
|
+
name: 'pnext-fold-members',
|
|
394
|
+
setup(builder) {
|
|
395
|
+
builder.onResolve({ filter: /.*/ }, args => {
|
|
396
|
+
if (args.kind === 'entry-point') return undefined;
|
|
397
|
+
const base = path.basename(args.path);
|
|
398
|
+
if (memberNames.includes(base)) return { path: path.join(chunksDir, base) };
|
|
399
|
+
return { path: args.path, external: true };
|
|
400
|
+
});
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
});
|
|
405
|
+
const js = result.outputFiles?.find(file => file.path.endsWith('.js'));
|
|
406
|
+
if (!js) return undefined;
|
|
407
|
+
const hash = createHash('sha256').update(js.contents).digest('hex').slice(0, 8).toUpperCase();
|
|
408
|
+
const name = `shared-init-${hash}.js`;
|
|
409
|
+
const map = result.outputFiles?.find(file => file.path.endsWith('.js.map'));
|
|
410
|
+
let contents = new TextDecoder().decode(js.contents);
|
|
411
|
+
if (map) {
|
|
412
|
+
contents = contents.replace(/# sourceMappingURL=.*$/m, `# sourceMappingURL=${name}.map`);
|
|
413
|
+
await writeText(path.join(chunksDir, `${name}.map`), new TextDecoder().decode(map.contents));
|
|
414
|
+
}
|
|
415
|
+
await writeText(path.join(chunksDir, name), contents);
|
|
416
|
+
return { name, text: contents, bytes: Buffer.byteLength(contents) };
|
|
417
|
+
} finally {
|
|
418
|
+
await unlink(barrelPath).catch(ignoreMissing);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** An output file's text plus the relative specifiers it imports through. */
|
|
423
|
+
interface FoldOutput {
|
|
424
|
+
text: string;
|
|
425
|
+
specifiers: string[];
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
function foldOutput(text: string): FoldOutput {
|
|
429
|
+
return { text, specifiers: [...text.matchAll(/"\.[^"]*"/g)].map(match => match[0]) };
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** Every output's text plus a basename->referrers index, read once. Chunk names
|
|
433
|
+
* are content-hashed, so a specifier naming a member spells it as its basename. */
|
|
434
|
+
interface FoldState {
|
|
435
|
+
outputs: Map<string, FoldOutput>;
|
|
436
|
+
/** Files whose specifiers name any of `names`, each visited once. */
|
|
437
|
+
referrersOf(names: string[]): Iterable<string>;
|
|
438
|
+
set(file: string, output: FoldOutput): void;
|
|
439
|
+
remove(file: string): void;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
async function createFoldState(outDir: string, metafile: Metafile): Promise<FoldState> {
|
|
443
|
+
const outputs = new Map<string, FoldOutput>();
|
|
444
|
+
const referrers = new Map<string, Set<string>>();
|
|
445
|
+
|
|
446
|
+
const index = (file: string, output: FoldOutput, add: boolean) => {
|
|
447
|
+
for (const spec of output.specifiers) {
|
|
448
|
+
const base = path.basename(spec.slice(1, -1));
|
|
449
|
+
const set = referrers.get(base) ?? referrers.set(base, new Set()).get(base)!;
|
|
450
|
+
if (add) set.add(file);
|
|
451
|
+
else set.delete(file);
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
for (const name of Object.keys(metafile.outputs)) {
|
|
456
|
+
if (!name.endsWith('.js')) continue;
|
|
457
|
+
const resolved = path.resolve(name);
|
|
458
|
+
const local = existsSync(resolved) ? resolved : path.join(outDir, path.basename(name));
|
|
459
|
+
if (outputs.has(local) || !existsSync(local)) continue;
|
|
460
|
+
const output = foldOutput(await readText(local));
|
|
461
|
+
outputs.set(local, output);
|
|
462
|
+
index(local, output, true);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
return {
|
|
466
|
+
outputs,
|
|
467
|
+
referrersOf(names) {
|
|
468
|
+
const seen = new Set<string>();
|
|
469
|
+
for (const name of names) for (const file of referrers.get(name) ?? []) seen.add(file);
|
|
470
|
+
return seen;
|
|
471
|
+
},
|
|
472
|
+
set(file, output) {
|
|
473
|
+
const previous = outputs.get(file);
|
|
474
|
+
if (previous) index(file, previous, false);
|
|
475
|
+
outputs.set(file, output);
|
|
476
|
+
index(file, output, true);
|
|
477
|
+
},
|
|
478
|
+
remove(file) {
|
|
479
|
+
const previous = outputs.get(file);
|
|
480
|
+
if (previous) index(file, previous, false);
|
|
481
|
+
outputs.delete(file);
|
|
482
|
+
},
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/** The shipped graph, with the members collapsed into the merged chunk. */
|
|
487
|
+
function mergeMetafile(
|
|
488
|
+
metafile: Metafile,
|
|
489
|
+
members: string[],
|
|
490
|
+
mergedPath: string,
|
|
491
|
+
bytes: number,
|
|
492
|
+
): Metafile {
|
|
493
|
+
const memberSet = new Set(members);
|
|
494
|
+
const merged = path.relative(process.cwd(), mergedPath);
|
|
495
|
+
const outputs: Metafile['outputs'] = {};
|
|
496
|
+
const mergedImports: Metafile['outputs'][string]['imports'] = [];
|
|
497
|
+
const mergedInputs: Metafile['outputs'][string]['inputs'] = {};
|
|
498
|
+
|
|
499
|
+
for (const [file, output] of Object.entries(metafile.outputs)) {
|
|
500
|
+
const remap = (list: typeof output.imports) => {
|
|
501
|
+
const seen = new Set<string>();
|
|
502
|
+
return list.flatMap(imported => {
|
|
503
|
+
const target = memberSet.has(imported.path) ? merged : imported.path;
|
|
504
|
+
const key = `${target}:${imported.kind}`;
|
|
505
|
+
if (seen.has(key)) return [];
|
|
506
|
+
seen.add(key);
|
|
507
|
+
return [{ ...imported, path: target }];
|
|
508
|
+
});
|
|
509
|
+
};
|
|
510
|
+
if (memberSet.has(file)) {
|
|
511
|
+
Object.assign(mergedInputs, output.inputs);
|
|
512
|
+
for (const imported of remap(output.imports ?? [])) {
|
|
513
|
+
if (imported.path !== merged) mergedImports.push(imported);
|
|
514
|
+
}
|
|
515
|
+
continue;
|
|
516
|
+
}
|
|
517
|
+
outputs[file] = { ...output, imports: remap(output.imports ?? []) };
|
|
518
|
+
}
|
|
519
|
+
outputs[merged] = {
|
|
520
|
+
imports: mergedImports,
|
|
521
|
+
exports: [],
|
|
522
|
+
inputs: mergedInputs,
|
|
523
|
+
bytes,
|
|
524
|
+
};
|
|
525
|
+
return { ...metafile, outputs };
|
|
526
|
+
}
|