@wular/pnext 0.1.5 → 0.1.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/bin/pnext +9 -2
- package/dist/server/_chunks/aliases-j47a10ce.js +44 -0
- package/dist/server/_chunks/analyze-arswqe6s.js +445 -0
- package/dist/server/_chunks/boot-af0mv3dy.js +36 -0
- package/dist/server/_chunks/build-spy3dxp4.js +84 -0
- package/dist/server/_chunks/build-t9h0hec3.js +3550 -0
- package/dist/server/_chunks/build-tier-m7yvqz61.js +2472 -0
- package/dist/server/_chunks/compile-53j02j27.js +22 -0
- package/dist/server/_chunks/config-1yzwexq6.js +27 -0
- package/dist/server/_chunks/config-n2z7yxps.js +22 -0
- package/dist/server/_chunks/create-meec9hs6.js +134 -0
- package/dist/server/_chunks/dev-fjmqg6en.js +220 -0
- package/dist/server/_chunks/esbuild-yyktfrtt.js +15 -0
- package/dist/server/_chunks/generate-n9sh6hzt.js +21 -0
- package/dist/server/_chunks/index-0374j2b9.js +66 -0
- package/dist/server/_chunks/index-06nyqntx.js +213 -0
- package/dist/server/_chunks/index-1011cscr.js +5263 -0
- package/dist/server/_chunks/index-11sfk4hb.js +170 -0
- package/dist/server/_chunks/index-20ppawv7.js +297 -0
- package/dist/server/_chunks/index-2dv4y4wd.js +1065 -0
- package/dist/server/_chunks/index-2vz0wzdn.js +59 -0
- package/dist/server/_chunks/index-2wfv4kvx.js +182 -0
- package/dist/server/_chunks/index-357na4a8.js +109 -0
- package/dist/server/_chunks/index-3b3at80h.js +109 -0
- package/dist/server/_chunks/index-3tf1xjsp.js +6166 -0
- package/dist/server/_chunks/index-3xbr4eyv.js +105 -0
- package/dist/server/_chunks/index-4399bvd2.js +18 -0
- package/dist/server/_chunks/index-4hgjrqh7.js +851 -0
- package/dist/server/_chunks/index-4q2gbfwk.js +2530 -0
- package/dist/server/_chunks/index-4t92hdxd.js +172 -0
- package/dist/server/_chunks/index-4zzebszd.js +10 -0
- package/dist/server/_chunks/index-5gyr4emz.js +107 -0
- package/dist/server/_chunks/index-5hr4062s.js +70 -0
- package/dist/server/_chunks/index-68m3mwbd.js +84 -0
- package/dist/server/_chunks/index-6b009mt0.js +56 -0
- package/dist/server/_chunks/index-6e4fqv30.js +588 -0
- package/dist/server/_chunks/index-6gex3pfa.js +15 -0
- package/dist/server/_chunks/index-6kp9r0d9.js +11 -0
- package/dist/server/_chunks/index-6q7zvrap.js +134 -0
- package/dist/server/_chunks/index-73z4esab.js +38 -0
- package/dist/server/_chunks/index-79ngvam8.js +60 -0
- package/dist/server/_chunks/index-7wc9n8be.js +25 -0
- package/dist/server/_chunks/index-8k7pfejn.js +1599 -0
- package/dist/server/_chunks/index-8kcxdfya.js +206 -0
- package/dist/server/_chunks/index-91x12ykq.js +274 -0
- package/dist/server/_chunks/index-9ch50vtq.js +18 -0
- package/dist/server/_chunks/index-9hx2641q.js +28 -0
- package/dist/server/_chunks/index-9n2914ww.js +535 -0
- package/dist/server/_chunks/index-9ych9ma8.js +207 -0
- package/dist/server/_chunks/index-b6wtkdp4.js +24 -0
- package/dist/server/_chunks/index-bcws5eh2.js +35 -0
- package/dist/server/_chunks/index-c0g2wf3z.js +92 -0
- package/dist/server/_chunks/index-cf4rvxv6.js +95 -0
- package/dist/server/_chunks/index-cjgqs13g.js +65 -0
- package/dist/server/_chunks/index-ckkdpht5.js +41 -0
- package/dist/server/_chunks/index-cyfednwb.js +87 -0
- package/dist/server/_chunks/index-danncfhy.js +311 -0
- package/dist/server/_chunks/index-dg0f05zs.js +199 -0
- package/dist/server/_chunks/index-e3a6yx84.js +18 -0
- package/dist/server/_chunks/index-e88tyv35.js +45 -0
- package/dist/server/_chunks/index-eajaspjk.js +1253 -0
- package/dist/server/_chunks/index-eeaanp2e.js +483 -0
- package/dist/server/_chunks/index-egykcqg3.js +101 -0
- package/dist/server/_chunks/index-en7n9f7m.js +22 -0
- package/dist/server/_chunks/index-ezgj0tr1.js +88 -0
- package/dist/server/_chunks/index-fkp000p1.js +590 -0
- package/dist/server/_chunks/index-fn1chkkd.js +214 -0
- package/dist/server/_chunks/index-fp8r0sqs.js +64 -0
- package/dist/server/_chunks/index-ggk2j965.js +9432 -0
- package/dist/server/_chunks/index-grta8ap3.js +238 -0
- package/dist/server/_chunks/index-h2a70tg4.js +7 -0
- package/dist/server/_chunks/index-h4xy648y.js +51 -0
- package/dist/server/_chunks/index-hkdfj58q.js +92 -0
- package/dist/server/_chunks/index-j2zrcp1k.js +66 -0
- package/dist/server/_chunks/index-jcsjq3d1.js +20 -0
- package/dist/server/_chunks/index-jepjvcrf.js +198 -0
- package/dist/server/_chunks/index-jks0x0a1.js +14 -0
- package/dist/server/_chunks/index-jx7bqvw1.js +57 -0
- package/dist/server/_chunks/index-k1g3epw0.js +1299 -0
- package/dist/server/_chunks/index-knp0s912.js +7 -0
- package/dist/server/_chunks/index-kq8kpzye.js +472 -0
- package/dist/server/_chunks/index-kqyy1d4f.js +50 -0
- package/dist/server/_chunks/index-kresp2n8.js +445 -0
- package/dist/server/_chunks/index-ms9v9tnx.js +31 -0
- package/dist/server/_chunks/index-n4hxpng2.js +11 -0
- package/dist/server/_chunks/index-ncnes8c6.js +17 -0
- package/dist/server/_chunks/index-nqh3xhdc.js +57 -0
- package/dist/server/_chunks/index-nyrba2kr.js +4130 -0
- package/dist/server/_chunks/index-p77b4kcx.js +296 -0
- package/dist/server/_chunks/index-pym1cezq.js +39 -0
- package/dist/server/_chunks/index-q738kkhj.js +100 -0
- package/dist/server/_chunks/index-qjj6gddn.js +54 -0
- package/dist/server/_chunks/index-qm6kmhqs.js +110 -0
- package/dist/server/_chunks/index-qn4z5s3c.js +819 -0
- package/dist/server/_chunks/index-qpwsgt3a.js +158 -0
- package/dist/server/_chunks/index-r97ptz1q.js +97 -0
- package/dist/server/_chunks/index-ras0sdab.js +26 -0
- package/dist/server/_chunks/index-rcs322t1.js +19 -0
- package/dist/server/_chunks/index-rn8dyr1f.js +126 -0
- package/dist/server/_chunks/index-rt9qq1va.js +4 -0
- package/dist/server/_chunks/index-rthrsed5.js +356 -0
- package/dist/server/_chunks/index-sctcgyvv.js +185 -0
- package/dist/server/_chunks/index-sjpckxqn.js +213 -0
- package/dist/server/_chunks/index-sq3kvz3g.js +200 -0
- package/dist/server/_chunks/index-stbk3jhn.js +31 -0
- package/dist/server/_chunks/index-sv8m6789.js +266 -0
- package/dist/server/_chunks/index-tgcakdxc.js +125 -0
- package/dist/server/_chunks/index-tgsfjmep.js +74 -0
- package/dist/server/_chunks/index-tq0pc7eg.js +154 -0
- package/dist/server/_chunks/index-tq1f96v7.js +51 -0
- package/dist/server/_chunks/index-tqn2qnnn.js +241 -0
- package/dist/server/_chunks/index-tvqkq6e6.js +128 -0
- package/dist/server/_chunks/index-v1ags1na.js +132 -0
- package/dist/server/_chunks/index-vvmmknm1.js +1431 -0
- package/dist/server/_chunks/index-vvq7rqtn.js +329 -0
- package/dist/server/_chunks/index-vz0km5qk.js +33 -0
- package/dist/server/_chunks/index-w6h8m6vp.js +138 -0
- package/dist/server/_chunks/index-web361b8.js +138 -0
- package/dist/server/_chunks/index-wpfhfbs4.js +194 -0
- package/dist/server/_chunks/index-x0y005nh.js +283 -0
- package/dist/server/_chunks/index-x3yn4ywm.js +22 -0
- package/dist/server/_chunks/index-x93hv0sc.js +271 -0
- package/dist/server/_chunks/index-xvj80c3a.js +30 -0
- package/dist/server/_chunks/index-xywjg2vc.js +100 -0
- package/dist/server/_chunks/index-y2jr2n72.js +152 -0
- package/dist/server/_chunks/index-y36a0ybw.js +57 -0
- package/dist/server/_chunks/index-ye9hmsvf.js +47 -0
- package/dist/server/_chunks/index-yhxkxghe.js +56 -0
- package/dist/server/_chunks/index-ym67zqxp.js +130 -0
- package/dist/server/_chunks/index-ytjvpbm2.js +736 -0
- package/dist/server/_chunks/index-yz54pxrh.js +960 -0
- package/dist/server/_chunks/index-z4z9zv3b.js +733 -0
- package/dist/server/_chunks/index-z84ab1w0.js +1270 -0
- package/dist/server/_chunks/index-z88qv16t.js +11 -0
- package/dist/server/_chunks/index-zqpqyg8p.js +666 -0
- package/dist/server/_chunks/loader-7yw0j98z.js +78 -0
- package/dist/server/_chunks/modules-jn8bmbn8.js +74 -0
- package/dist/server/_chunks/pipeline-yy9jhydf.js +832 -0
- package/dist/server/_chunks/production-jar9ztm3.js +44 -0
- package/dist/server/_chunks/proxy-config-j84dgksn.js +21 -0
- package/dist/server/_chunks/proxy-w1d0wmsn.js +33 -0
- package/dist/server/_chunks/router-k1reahfn.js +498 -0
- package/dist/server/_chunks/routes-2s277fc6.js +64 -0
- package/dist/server/_chunks/run-pvd8bs1t.js +696 -0
- package/dist/server/_chunks/server-mt4a994c.js +1835 -0
- package/dist/server/_chunks/tsconfig-defaults-1nb3308g.js +20 -0
- package/dist/server/_chunks/typegen-0qr0459b.js +35 -0
- package/dist/server/api/client-cache.js +14 -0
- package/dist/server/cli/index.js +378 -0
- package/dist/server/cli/start.js +137 -0
- package/dist/server/client/reference.js +16 -0
- package/dist/server/compat/next/cache.js +56 -0
- package/dist/server/compat/next/canonical-url.js +13 -0
- package/dist/server/compat/next/client-cache.js +57 -0
- package/dist/server/compat/next/client-navigation.js +232 -0
- package/dist/server/compat/next/client-only.js +1 -0
- package/dist/server/compat/next/client-script.js +178 -0
- package/dist/server/compat/next/client-server.js +34 -0
- package/dist/server/compat/next/config-headers.js +32 -0
- package/dist/server/compat/next/config-loader.js +25 -0
- package/dist/server/compat/next/config.js +10 -0
- package/dist/server/compat/next/constants.js +18 -0
- package/dist/server/compat/next/custom-server.js +10 -0
- package/dist/server/compat/next/dist/client/components/app-router-headers.js +48 -0
- package/dist/server/compat/next/dist/server/web/spec-extension/revalidate.js +27 -0
- package/dist/server/compat/next/dist/server/web/spec-extension/unstable-cache.js +25 -0
- package/dist/server/compat/next/dist/server/web/spec-extension/unstable-no-store.js +25 -0
- package/dist/server/compat/next/dynamic.js +32 -0
- package/dist/server/compat/next/error.js +112 -0
- package/dist/server/compat/next/font/cache.js +14 -0
- package/dist/server/compat/next/font/google.js +3 -0
- package/dist/server/compat/next/font/index.js +1 -0
- package/dist/server/compat/next/font/local.js +22 -0
- package/dist/server/compat/next/font/runtime-client.js +33 -0
- package/dist/server/compat/next/font/runtime.js +41 -0
- package/dist/server/compat/next/font/shared.js +22 -0
- package/dist/server/compat/next/form.js +129 -0
- package/dist/server/compat/next/head.js +10 -0
- package/dist/server/compat/next/headers.js +30 -0
- package/dist/server/compat/next/image/client.js +11 -0
- package/dist/server/compat/next/image/config.js +18 -0
- package/dist/server/compat/next/image/optimizer.js +25 -0
- package/dist/server/compat/next/image/patterns.js +12 -0
- package/dist/server/compat/next/image/props.js +16 -0
- package/dist/server/compat/next/image/shared.js +86 -0
- package/dist/server/compat/next/image/static-metadata.js +32 -0
- package/dist/server/compat/next/image/validate.js +8 -0
- package/dist/server/compat/next/image.js +59 -0
- package/dist/server/compat/next/index.js +1 -0
- package/dist/server/compat/next/legacy-image.js +78 -0
- package/dist/server/compat/next/legacy-request-apis.js +24 -0
- package/dist/server/compat/next/link-transform.js +14 -0
- package/dist/server/compat/next/link.js +14 -0
- package/dist/server/compat/next/navigation.js +190 -0
- package/dist/server/compat/next/offline.js +45 -0
- package/dist/server/compat/next/og.js +8 -0
- package/dist/server/compat/next/preferred-region.js +8 -0
- package/dist/server/compat/next/redirects.js +30 -0
- package/dist/server/compat/next/resource-hints.js +30 -0
- package/dist/server/compat/next/rewrites.js +41 -0
- package/dist/server/compat/next/root-params.js +121 -0
- package/dist/server/compat/next/route-state.js +23 -0
- package/dist/server/compat/next/router.js +103 -0
- package/dist/server/compat/next/script.js +37 -0
- package/dist/server/compat/next/server-only.js +1 -0
- package/dist/server/compat/next/server.js +131 -0
- package/dist/server/compat/next/svgr.js +14 -0
- package/dist/server/compat/next/telemetry.js +10 -0
- package/dist/server/compat/next/usage.js +13 -0
- package/dist/server/compat/next/user-agent.js +12 -0
- package/dist/server/compat/next/web-vitals.js +35 -0
- package/dist/server/compat/react/action-state.js +11 -0
- package/dist/server/compat/react/client-lite.js +83 -0
- package/dist/server/compat/react/client.js +131 -0
- package/dist/server/compat/react/compiler-runtime.js +42 -0
- package/dist/server/compat/react/dom-client.js +106 -0
- package/dist/server/compat/react/dom-react-server.js +37 -0
- package/dist/server/compat/react/dom-server.js +40 -0
- package/dist/server/compat/react/dom.js +52 -0
- package/dist/server/compat/react/hooks-extra.js +12 -0
- package/dist/server/compat/react/internals.js +14 -0
- package/dist/server/compat/react/parity.js +11 -0
- package/dist/server/compat/react/preact.js +113 -0
- package/dist/server/compat/react/react-server.js +133 -0
- package/dist/server/compat/react/router-shim.js +8 -0
- package/dist/server/compat/react/server-component-use.js +14 -0
- package/dist/server/compat/react/server-inserted-html.js +14 -0
- package/dist/server/compat/react/server.js +194 -0
- package/dist/server/compat/react/style-values.js +10 -0
- package/dist/server/compat/react/use.js +10 -0
- package/dist/server/compat/react/view-transition.js +12 -0
- package/dist/server/generation.json +1 -0
- package/package.json +4 -1
- package/src/api/dynamic.tsx +2 -0
- package/src/api/server.ts +24 -6
- package/src/cli/adapters/vercel-warm.ts +315 -61
- package/src/cli/adapters/vercel.ts +348 -71
- package/src/cli/build.ts +58 -33
- package/src/cli/index.ts +23 -10
- package/src/cli/serve/entry.ts +21 -159
- package/src/cli/serve/immutable.ts +31 -0
- package/src/cli/serve/pipeline.ts +82 -84
- package/src/cli/start.ts +3 -1
- package/src/client/chunk-fold.ts +19 -6
- package/src/client/react-tier.ts +3 -2
- package/src/client/reference-stub.ts +21 -2
- package/src/compat/actions/client-plugin.ts +4 -2
- package/src/compat/actions/config.ts +6 -5
- package/src/compat/actions/extensions.ts +2 -3
- package/src/compat/aliases.ts +8 -1
- package/src/compat/bundler/config.ts +88 -0
- package/src/compat/bundler/optimize-package-imports.ts +86 -26
- package/src/compat/bundler/relay-transform.ts +15 -3
- package/src/compat/cache-control.ts +16 -8
- package/src/compat/css/modules.ts +18 -5
- package/src/compat/lifecycle/error-funnel.ts +1 -1
- package/src/compat/lifecycle/instrumentation.ts +117 -24
- package/src/compat/lifecycle/testmode.ts +3 -2
- package/src/compat/next/config-loader.ts +137 -13
- package/src/compat/next/font/runtime.ts +22 -2
- package/src/compat/next/og.ts +3 -3
- package/src/compat/next/usage.ts +3 -2
- package/src/compat/pages/api.ts +83 -14
- package/src/compat/pages/router.ts +30 -14
- package/src/compat/register/bundler.ts +3 -1
- package/src/compat/register/middleware.ts +1 -1
- package/src/compat/register/otel.ts +5 -9
- package/src/compat/register/render.ts +3 -1
- package/src/compat/register/use-cache.ts +3 -1
- package/src/compat/release.ts +46 -0
- package/src/compat/segment/serve.ts +1 -1
- package/src/compat/validation/validate.ts +10 -7
- package/src/config.ts +51 -7
- package/src/css/assets.ts +213 -0
- package/src/css/build.ts +174 -238
- package/src/dev/server.ts +27 -8
- package/src/extensions.ts +4 -0
- package/src/render/hooks.ts +13 -0
- package/src/render/renderer.ts +52 -32
- package/src/request/context.ts +9 -0
- package/src/resolve/dynamic.ts +3 -1
- package/src/resolve/imports.ts +12 -52
- package/src/resolve/package-json.ts +56 -0
- package/src/resolve/scan-facts.ts +65 -0
- package/src/routing/href.ts +10 -3
- package/src/routing/match.ts +287 -0
- package/src/routing/metadata-files.ts +19 -5
- package/src/routing/proxy-config.ts +137 -0
- package/src/routing/proxy.ts +35 -134
- package/src/routing/routes.ts +22 -281
- package/src/runtime/fingerprint.ts +23 -8
- package/src/runtime/load.ts +170 -0
- package/src/runtime/loader.ts +215 -43
- package/src/runtime/module-cache.ts +13 -0
- package/src/runtime/modules.ts +370 -81
- package/src/runtime/prebundle.ts +81 -0
- package/src/runtime/production.ts +391 -0
- package/src/runtime/release-graph.ts +334 -0
- package/src/runtime/vendor-build.ts +724 -13
- package/src/runtime/vendor.ts +16 -3
- package/src/types.ts +2 -0
- package/src/utils/fs.ts +89 -3
|
@@ -0,0 +1,4130 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import { join as __pnextJoin } from "node:path";
|
|
3
|
+
const __PNEXT_PACKAGE_ROOT__ = __pnextJoin(import.meta.dirname, "../../..");
|
|
4
|
+
import {
|
|
5
|
+
devDynamicSplitEnabled,
|
|
6
|
+
rewriteDeferredDynamicImports,
|
|
7
|
+
rewriteLiteralDynamicCalls
|
|
8
|
+
} from "./index-91x12ykq.js";
|
|
9
|
+
import {
|
|
10
|
+
ISLAND_BOUNDARY_ERROR_DIGEST_ATTRIBUTE,
|
|
11
|
+
ISLAND_BOUNDARY_ERROR_ELEMENT,
|
|
12
|
+
ISLAND_BOUNDARY_ERROR_MESSAGE_ATTRIBUTE,
|
|
13
|
+
ISLAND_STATIC_CHILDREN_ATTRIBUTE,
|
|
14
|
+
clientEntryName
|
|
15
|
+
} from "./index-3tf1xjsp.js";
|
|
16
|
+
import {
|
|
17
|
+
findLayouts
|
|
18
|
+
} from "./index-jepjvcrf.js";
|
|
19
|
+
import {
|
|
20
|
+
cssModuleClientPlugin
|
|
21
|
+
} from "./index-2dv4y4wd.js";
|
|
22
|
+
import {
|
|
23
|
+
createVerboseLogger,
|
|
24
|
+
formatDuration
|
|
25
|
+
} from "./index-73z4esab.js";
|
|
26
|
+
import {
|
|
27
|
+
assetContentHash,
|
|
28
|
+
withAssetPrefix
|
|
29
|
+
} from "./index-y2jr2n72.js";
|
|
30
|
+
import {
|
|
31
|
+
frameworkFingerprint
|
|
32
|
+
} from "./index-c0g2wf3z.js";
|
|
33
|
+
import {
|
|
34
|
+
readSourceSync,
|
|
35
|
+
readSourceText
|
|
36
|
+
} from "./index-fp8r0sqs.js";
|
|
37
|
+
import {
|
|
38
|
+
ensureDir,
|
|
39
|
+
listFiles,
|
|
40
|
+
readText,
|
|
41
|
+
writeText
|
|
42
|
+
} from "./index-20ppawv7.js";
|
|
43
|
+
import {
|
|
44
|
+
traceEnabled
|
|
45
|
+
} from "./index-vz0km5qk.js";
|
|
46
|
+
import {
|
|
47
|
+
frameworkRuntimeAliasEntries,
|
|
48
|
+
publicEnvDefines
|
|
49
|
+
} from "./index-x93hv0sc.js";
|
|
50
|
+
import {
|
|
51
|
+
dim
|
|
52
|
+
} from "./index-6kp9r0d9.js";
|
|
53
|
+
import {
|
|
54
|
+
clientReferenceId,
|
|
55
|
+
ssrClientReference
|
|
56
|
+
} from "./index-ras0sdab.js";
|
|
57
|
+
import {
|
|
58
|
+
scanFacts
|
|
59
|
+
} from "./index-z4z9zv3b.js";
|
|
60
|
+
import {
|
|
61
|
+
getExternalPackagePolicy,
|
|
62
|
+
resolveImport,
|
|
63
|
+
resolveLinkedPackageSpecifier
|
|
64
|
+
} from "./index-yz54pxrh.js";
|
|
65
|
+
import {
|
|
66
|
+
applyClientSourceAsyncPreTransforms,
|
|
67
|
+
applyClientSourceTransforms,
|
|
68
|
+
getAssetExtensions,
|
|
69
|
+
getBundlerExtensions,
|
|
70
|
+
getCompatModeExtensions,
|
|
71
|
+
getImportAliasExtensions,
|
|
72
|
+
hasClientSourceAsyncPreTransforms
|
|
73
|
+
} from "./index-zqpqyg8p.js";
|
|
74
|
+
import {
|
|
75
|
+
escapeRegex
|
|
76
|
+
} from "./index-qjj6gddn.js";
|
|
77
|
+
|
|
78
|
+
// src/client/build.ts
|
|
79
|
+
import { createHash as createHash3 } from "crypto";
|
|
80
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, realpathSync, statSync as statSync3 } from "fs";
|
|
81
|
+
import { copyFile, mkdir, readFile, rename, rm, stat, writeFile } from "fs/promises";
|
|
82
|
+
import path5 from "path";
|
|
83
|
+
import {
|
|
84
|
+
build as build3
|
|
85
|
+
} from "esbuild";
|
|
86
|
+
|
|
87
|
+
// src/client/chunk-fold.ts
|
|
88
|
+
import { createHash } from "crypto";
|
|
89
|
+
import { existsSync } from "fs";
|
|
90
|
+
import { unlink } from "fs/promises";
|
|
91
|
+
import path from "path";
|
|
92
|
+
import { build } from "esbuild";
|
|
93
|
+
|
|
94
|
+
// src/client/profile.ts
|
|
95
|
+
var enabled = traceEnabled("client");
|
|
96
|
+
var totals = new Map;
|
|
97
|
+
var spans = new Map;
|
|
98
|
+
function unionMs(intervals) {
|
|
99
|
+
const sorted = [...intervals].sort((a, b) => a[0] - b[0]);
|
|
100
|
+
let total = 0;
|
|
101
|
+
let [openStart, openEnd] = sorted[0] ?? [0, 0];
|
|
102
|
+
for (const [start, end] of sorted.slice(1)) {
|
|
103
|
+
if (start > openEnd) {
|
|
104
|
+
total += openEnd - openStart;
|
|
105
|
+
[openStart, openEnd] = [start, end];
|
|
106
|
+
} else if (end > openEnd) {
|
|
107
|
+
openEnd = end;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return total + (openEnd - openStart);
|
|
111
|
+
}
|
|
112
|
+
var clientProfile = {
|
|
113
|
+
enabled,
|
|
114
|
+
time(phase, task) {
|
|
115
|
+
if (!enabled)
|
|
116
|
+
return task();
|
|
117
|
+
const start = performance.now();
|
|
118
|
+
try {
|
|
119
|
+
return task();
|
|
120
|
+
} finally {
|
|
121
|
+
totals.set(phase, (totals.get(phase) ?? 0) + (performance.now() - start));
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
async timeAsync(phase, task) {
|
|
125
|
+
if (!enabled)
|
|
126
|
+
return task();
|
|
127
|
+
const start = performance.now();
|
|
128
|
+
try {
|
|
129
|
+
return await task();
|
|
130
|
+
} finally {
|
|
131
|
+
(spans.get(phase) ?? spans.set(phase, []).get(phase)).push([start, performance.now()]);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
count(key, by = 1) {
|
|
135
|
+
if (!enabled)
|
|
136
|
+
return;
|
|
137
|
+
totals.set(key, (totals.get(key) ?? 0) + by);
|
|
138
|
+
},
|
|
139
|
+
reset() {
|
|
140
|
+
totals.clear();
|
|
141
|
+
spans.clear();
|
|
142
|
+
},
|
|
143
|
+
snapshot() {
|
|
144
|
+
return {
|
|
145
|
+
...Object.fromEntries(totals),
|
|
146
|
+
...Object.fromEntries([...spans].map(([phase, list]) => [phase, unionMs(list)]))
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
report(label) {
|
|
150
|
+
if (!enabled)
|
|
151
|
+
return;
|
|
152
|
+
const snap = this.snapshot();
|
|
153
|
+
const line = Object.entries(snap).map(([phase, value]) => `${phase} ${value < 100 ? value.toFixed(1) : Math.round(value)}`).join(" \xB7 ");
|
|
154
|
+
console.log(`pnext client profile [${label}] ${line}`);
|
|
155
|
+
this.reset();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
// src/client/chunk-fold.ts
|
|
160
|
+
function ignoreMissing() {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
var namedClause = String.raw`\{([^}]*)\}`;
|
|
164
|
+
async function foldInitialChunks(options) {
|
|
165
|
+
if (process.env.PNEXT_CHUNK_FOLD === "0")
|
|
166
|
+
return options.metafile;
|
|
167
|
+
if (!options.buildOptions.minify)
|
|
168
|
+
return options.metafile;
|
|
169
|
+
return foldChunks(options);
|
|
170
|
+
}
|
|
171
|
+
async function foldChunks({ outDir, metafile, buildOptions }) {
|
|
172
|
+
let folded = metafile;
|
|
173
|
+
const groups = clientProfile.time("foldGroups", () => coLoadGroups(metafile));
|
|
174
|
+
clientProfile.count("foldGroup#", groups.length);
|
|
175
|
+
clientProfile.count("foldOutput#", Object.keys(metafile.outputs).length);
|
|
176
|
+
if (groups.length === 0)
|
|
177
|
+
return folded;
|
|
178
|
+
const state = await clientProfile.timeAsync("foldRead", () => createFoldState(outDir, metafile));
|
|
179
|
+
for (const group of groups) {
|
|
180
|
+
const mutation = { started: false };
|
|
181
|
+
try {
|
|
182
|
+
folded = await foldGroup({ outDir, metafile: folded, buildOptions }, group, mutation, state);
|
|
183
|
+
} catch (error) {
|
|
184
|
+
if (mutation.started)
|
|
185
|
+
throw error;
|
|
186
|
+
reportFoldFailure(group, error);
|
|
187
|
+
break;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return folded;
|
|
191
|
+
}
|
|
192
|
+
function reportFoldFailure(group, error) {
|
|
193
|
+
const names = group.map((member) => path.basename(member)).join(", ");
|
|
194
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
195
|
+
if (process.env.PNEXT_CHUNK_FOLD === "strict") {
|
|
196
|
+
throw new Error(`client chunk fold failed for [${names}]: ${message}`);
|
|
197
|
+
}
|
|
198
|
+
console.warn(`pnext: client chunk fold skipped [${names}] \u2014 ${message}`);
|
|
199
|
+
}
|
|
200
|
+
async function foldGroup({ outDir, metafile, buildOptions }, members, mutation, state) {
|
|
201
|
+
if (members.length < 2)
|
|
202
|
+
return metafile;
|
|
203
|
+
const chunksDir = path.join(outDir, "chunks");
|
|
204
|
+
const memberNames = members.map((output) => path.basename(output));
|
|
205
|
+
if (memberNames.some((name) => !existsSync(path.join(chunksDir, name))))
|
|
206
|
+
return metafile;
|
|
207
|
+
const sources = new Map(await Promise.all(memberNames.map(async (name) => {
|
|
208
|
+
const file = path.join(chunksDir, name);
|
|
209
|
+
return [name, state.outputs.get(file)?.text ?? await readText(file)];
|
|
210
|
+
})));
|
|
211
|
+
const aliases = memberAliases(memberNames, sources);
|
|
212
|
+
if (!aliases)
|
|
213
|
+
return metafile;
|
|
214
|
+
const rewrites = new Map;
|
|
215
|
+
const rewrite = memberRewriter(memberNames, aliases, chunksDir);
|
|
216
|
+
const rewritable = clientProfile.time("foldRewrite", () => {
|
|
217
|
+
for (const file of state.referrersOf(memberNames)) {
|
|
218
|
+
if (memberNames.includes(path.basename(file)))
|
|
219
|
+
continue;
|
|
220
|
+
const output = state.outputs.get(file);
|
|
221
|
+
if (!output)
|
|
222
|
+
continue;
|
|
223
|
+
const next = rewrite(output.text, file);
|
|
224
|
+
if (next === null)
|
|
225
|
+
return false;
|
|
226
|
+
if (next !== output.text)
|
|
227
|
+
rewrites.set(file, next);
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
});
|
|
231
|
+
if (!rewritable)
|
|
232
|
+
return metafile;
|
|
233
|
+
const merged = await clientProfile.timeAsync("foldBuild", () => buildMergedChunk(chunksDir, memberNames, aliases, buildOptions));
|
|
234
|
+
if (!merged)
|
|
235
|
+
return metafile;
|
|
236
|
+
mutation.started = true;
|
|
237
|
+
await Promise.all([...rewrites].map(([file, source]) => {
|
|
238
|
+
const next = source.replaceAll(MERGED_PLACEHOLDER, merged.name);
|
|
239
|
+
state.set(file, foldOutput(next));
|
|
240
|
+
return writeText(file, next);
|
|
241
|
+
}));
|
|
242
|
+
await Promise.all(memberNames.flatMap((name) => [
|
|
243
|
+
unlink(path.join(chunksDir, name)).catch(ignoreMissing),
|
|
244
|
+
unlink(path.join(chunksDir, `${name}.map`)).catch(ignoreMissing)
|
|
245
|
+
]));
|
|
246
|
+
for (const name of memberNames)
|
|
247
|
+
state.remove(path.join(chunksDir, name));
|
|
248
|
+
state.set(path.join(chunksDir, merged.name), foldOutput(merged.text));
|
|
249
|
+
return mergeMetafile(metafile, members, path.join(chunksDir, merged.name), merged.bytes);
|
|
250
|
+
}
|
|
251
|
+
function coLoadGroups(metafile) {
|
|
252
|
+
const dynamicTargets = new Set;
|
|
253
|
+
for (const output of Object.values(metafile.outputs)) {
|
|
254
|
+
for (const imported of output.imports ?? []) {
|
|
255
|
+
if (imported.kind === "dynamic-import")
|
|
256
|
+
dynamicTargets.add(imported.path);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const entries = Object.keys(metafile.outputs).filter((file) => metafile.outputs[file]?.entryPoint && !dynamicTargets.has(file));
|
|
260
|
+
if (entries.length === 0)
|
|
261
|
+
return [];
|
|
262
|
+
const available = availability(metafile, entries, dynamicTargets);
|
|
263
|
+
const roots = [...available.keys()];
|
|
264
|
+
const groups = new Map;
|
|
265
|
+
for (const file of Object.keys(metafile.outputs)) {
|
|
266
|
+
if (!file.endsWith(".js"))
|
|
267
|
+
continue;
|
|
268
|
+
if (entries.includes(file) || dynamicTargets.has(file))
|
|
269
|
+
continue;
|
|
270
|
+
const mask = roots.map((root) => available.get(root).has(file) ? "1" : "0").join("");
|
|
271
|
+
if (!mask.includes("1"))
|
|
272
|
+
continue;
|
|
273
|
+
(groups.get(mask) ?? groups.set(mask, []).get(mask)).push(file);
|
|
274
|
+
}
|
|
275
|
+
return [...groups.values()].filter((group) => group.length > 1 && entries.some((entry) => available.get(entry).has(group[0]))).map((group) => [...group].sort());
|
|
276
|
+
}
|
|
277
|
+
function availabilityAt(available, file, memo) {
|
|
278
|
+
const own = available.get(file);
|
|
279
|
+
if (own)
|
|
280
|
+
return own;
|
|
281
|
+
const cached = memo.get(file);
|
|
282
|
+
if (cached)
|
|
283
|
+
return cached;
|
|
284
|
+
let acc;
|
|
285
|
+
for (const set of available.values()) {
|
|
286
|
+
if (!set.has(file))
|
|
287
|
+
continue;
|
|
288
|
+
acc = acc === undefined ? new Set(set) : intersect(acc, set);
|
|
289
|
+
}
|
|
290
|
+
const answer = acc ?? new Set([file]);
|
|
291
|
+
memo.set(file, answer);
|
|
292
|
+
return answer;
|
|
293
|
+
}
|
|
294
|
+
function entryFloors(metafile, entries, closures) {
|
|
295
|
+
const dynamicEdges = new Map;
|
|
296
|
+
for (const [file, output] of Object.entries(metafile.outputs)) {
|
|
297
|
+
for (const imported of output.imports ?? []) {
|
|
298
|
+
if (imported.kind !== "dynamic-import")
|
|
299
|
+
continue;
|
|
300
|
+
(dynamicEdges.get(file) ?? dynamicEdges.set(file, []).get(file)).push(imported.path);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
const floors = new Map;
|
|
304
|
+
for (const entry of entries) {
|
|
305
|
+
const own = closures.get(entry);
|
|
306
|
+
const seen = new Set(own);
|
|
307
|
+
const queue = [...own];
|
|
308
|
+
for (const current of queue) {
|
|
309
|
+
for (const target of dynamicEdges.get(current) ?? []) {
|
|
310
|
+
if (seen.has(target))
|
|
311
|
+
continue;
|
|
312
|
+
for (const file of closures.get(target) ?? staticClosure(metafile, target)) {
|
|
313
|
+
if (seen.has(file))
|
|
314
|
+
continue;
|
|
315
|
+
seen.add(file);
|
|
316
|
+
queue.push(file);
|
|
317
|
+
}
|
|
318
|
+
const floor = floors.get(target);
|
|
319
|
+
floors.set(target, floor ? intersect(floor, own) : new Set(own));
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return floors;
|
|
324
|
+
}
|
|
325
|
+
function availability(metafile, entries, dynamicTargets) {
|
|
326
|
+
const available = new Map;
|
|
327
|
+
const closures = new Map;
|
|
328
|
+
for (const root of [...entries, ...dynamicTargets]) {
|
|
329
|
+
closures.set(root, staticClosure(metafile, root));
|
|
330
|
+
available.set(root, new Set(closures.get(root)));
|
|
331
|
+
}
|
|
332
|
+
for (const [target, floor] of entryFloors(metafile, entries, closures)) {
|
|
333
|
+
const set = available.get(target);
|
|
334
|
+
if (set)
|
|
335
|
+
for (const file of floor)
|
|
336
|
+
set.add(file);
|
|
337
|
+
}
|
|
338
|
+
const importersOf = new Map;
|
|
339
|
+
for (const [file, output] of Object.entries(metafile.outputs)) {
|
|
340
|
+
for (const imported of output.imports ?? []) {
|
|
341
|
+
if (imported.kind !== "dynamic-import")
|
|
342
|
+
continue;
|
|
343
|
+
(importersOf.get(imported.path) ?? importersOf.set(imported.path, []).get(imported.path)).push(file);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
const memo = new Map;
|
|
347
|
+
for (let round = 0;round < dynamicTargets.size + 1; round += 1) {
|
|
348
|
+
let changed = false;
|
|
349
|
+
for (const target of dynamicTargets) {
|
|
350
|
+
const importers = importersOf.get(target) ?? [];
|
|
351
|
+
let inherited;
|
|
352
|
+
for (const importer of importers) {
|
|
353
|
+
const from = availabilityAt(available, importer, memo);
|
|
354
|
+
inherited = inherited === undefined ? new Set(from) : intersect(inherited, from);
|
|
355
|
+
}
|
|
356
|
+
const next = new Set([...closures.get(target), ...inherited ?? []]);
|
|
357
|
+
if (next.size !== available.get(target).size) {
|
|
358
|
+
available.set(target, next);
|
|
359
|
+
memo.clear();
|
|
360
|
+
changed = true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (!changed)
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
return available;
|
|
367
|
+
}
|
|
368
|
+
function staticClosure(metafile, root) {
|
|
369
|
+
const seen = new Set([root]);
|
|
370
|
+
const queue = [root];
|
|
371
|
+
for (const current of queue) {
|
|
372
|
+
for (const imported of metafile.outputs[current]?.imports ?? []) {
|
|
373
|
+
if (imported.kind !== "import-statement")
|
|
374
|
+
continue;
|
|
375
|
+
if (seen.has(imported.path))
|
|
376
|
+
continue;
|
|
377
|
+
seen.add(imported.path);
|
|
378
|
+
queue.push(imported.path);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return seen;
|
|
382
|
+
}
|
|
383
|
+
function intersect(a, b) {
|
|
384
|
+
return new Set([...a].filter((item) => b.has(item)));
|
|
385
|
+
}
|
|
386
|
+
function memberAliases(memberNames, sources) {
|
|
387
|
+
const aliases = new Map;
|
|
388
|
+
for (const [index, name] of memberNames.entries()) {
|
|
389
|
+
const source = sources.get(name);
|
|
390
|
+
if (/export\s*\*/.test(source))
|
|
391
|
+
return;
|
|
392
|
+
const clauses = [
|
|
393
|
+
...source.matchAll(new RegExp(String.raw`export\s*${namedClause}\s*(?!from)`, "g"))
|
|
394
|
+
];
|
|
395
|
+
const clause = clauses.at(-1);
|
|
396
|
+
const names = new Map;
|
|
397
|
+
for (const part of clause?.[1]?.split(",") ?? []) {
|
|
398
|
+
const exported = part.split(" as ").at(-1)?.trim();
|
|
399
|
+
if (exported)
|
|
400
|
+
names.set(exported, `$${index}${exported}`);
|
|
401
|
+
}
|
|
402
|
+
aliases.set(name, names);
|
|
403
|
+
}
|
|
404
|
+
return aliases;
|
|
405
|
+
}
|
|
406
|
+
function memberRewriter(memberNames, aliases, chunksDir) {
|
|
407
|
+
const memberPattern = memberNames.map(escapeRegex).join("|");
|
|
408
|
+
const specifier = String.raw`"\.[^"]*(?:${memberPattern})"`;
|
|
409
|
+
const vetoes = [
|
|
410
|
+
new RegExp(String.raw`import\s*\*\s*as\s*[\w$]+\s*from\s*${specifier}`),
|
|
411
|
+
new RegExp(String.raw`export\s*\*[^;]*from\s*${specifier}`),
|
|
412
|
+
new RegExp(String.raw`import\(\s*${specifier}`),
|
|
413
|
+
new RegExp(String.raw`"[^".][^"]*(?:${memberPattern})"`)
|
|
414
|
+
];
|
|
415
|
+
const namedImport = new RegExp(String.raw`(import|export)\s*${namedClause}\s*from\s*(${specifier})`, "g");
|
|
416
|
+
const sideEffectImport = new RegExp(String.raw`import\s*(${specifier})`, "g");
|
|
417
|
+
const nameOf = (spec) => memberNames.find((name) => spec.includes(name));
|
|
418
|
+
const rename = (clause, member) => clause.split(",").map((part) => {
|
|
419
|
+
const [imported, local] = part.split(" as ").map((item) => item.trim());
|
|
420
|
+
if (!imported)
|
|
421
|
+
return part;
|
|
422
|
+
const alias = aliases.get(member)?.get(imported);
|
|
423
|
+
if (!alias)
|
|
424
|
+
return part;
|
|
425
|
+
return `${alias} as ${local ?? imported}`;
|
|
426
|
+
}).join(",");
|
|
427
|
+
return (source, file) => {
|
|
428
|
+
for (const veto of vetoes)
|
|
429
|
+
if (veto.test(source))
|
|
430
|
+
return null;
|
|
431
|
+
const mergedHref = path.relative(path.dirname(file), path.join(chunksDir, MERGED_PLACEHOLDER)).split(path.sep).join("/");
|
|
432
|
+
const href = mergedHref.startsWith(".") ? mergedHref : `./${mergedHref}`;
|
|
433
|
+
return source.replace(namedImport, (_all, keyword, clause, spec) => `${keyword}{${rename(clause, nameOf(spec))}}from"${href}"`).replace(sideEffectImport, () => `import"${href}"`);
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
var MERGED_PLACEHOLDER = "\x00pnext-merged\x00";
|
|
437
|
+
async function buildMergedChunk(chunksDir, memberNames, aliases, buildOptions) {
|
|
438
|
+
const barrel = memberNames.map((name) => {
|
|
439
|
+
const names = aliases.get(name);
|
|
440
|
+
const spec = JSON.stringify(`./${name}`);
|
|
441
|
+
if (names.size === 0)
|
|
442
|
+
return `import ${spec};`;
|
|
443
|
+
const clause = [...names].map(([exported, alias]) => `${exported} as ${alias}`).join(",");
|
|
444
|
+
return `export {${clause}} from ${spec};`;
|
|
445
|
+
}).join(`
|
|
446
|
+
`);
|
|
447
|
+
const barrelPath = path.join(chunksDir, "__pnext-fold-barrel.js");
|
|
448
|
+
await writeText(barrelPath, barrel);
|
|
449
|
+
try {
|
|
450
|
+
const result = await build({
|
|
451
|
+
minify: buildOptions.minify,
|
|
452
|
+
sourcemap: buildOptions.sourcemap,
|
|
453
|
+
target: buildOptions.target,
|
|
454
|
+
format: buildOptions.format ?? "esm",
|
|
455
|
+
bundle: true,
|
|
456
|
+
entryPoints: [barrelPath],
|
|
457
|
+
outfile: path.join(chunksDir, "__pnext-fold-merged.js"),
|
|
458
|
+
write: false,
|
|
459
|
+
plugins: [
|
|
460
|
+
{
|
|
461
|
+
name: "pnext-fold-members",
|
|
462
|
+
setup(builder) {
|
|
463
|
+
builder.onResolve({ filter: /.*/ }, (args) => {
|
|
464
|
+
if (args.kind === "entry-point")
|
|
465
|
+
return;
|
|
466
|
+
const base = path.basename(args.path);
|
|
467
|
+
if (memberNames.includes(base))
|
|
468
|
+
return { path: path.join(chunksDir, base) };
|
|
469
|
+
return { path: args.path, external: true };
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
]
|
|
474
|
+
});
|
|
475
|
+
const js = result.outputFiles?.find((file) => file.path.endsWith(".js"));
|
|
476
|
+
if (!js)
|
|
477
|
+
return;
|
|
478
|
+
const hash = createHash("sha256").update(js.contents).digest("hex").slice(0, 8).toUpperCase();
|
|
479
|
+
const name = `shared-init-${hash}.js`;
|
|
480
|
+
const map = result.outputFiles?.find((file) => file.path.endsWith(".js.map"));
|
|
481
|
+
let contents = new TextDecoder().decode(js.contents);
|
|
482
|
+
if (map) {
|
|
483
|
+
contents = contents.replace(/# sourceMappingURL=.*$/m, `# sourceMappingURL=${name}.map`);
|
|
484
|
+
await writeText(path.join(chunksDir, `${name}.map`), new TextDecoder().decode(map.contents));
|
|
485
|
+
}
|
|
486
|
+
await writeText(path.join(chunksDir, name), contents);
|
|
487
|
+
return { name, text: contents, bytes: Buffer.byteLength(contents) };
|
|
488
|
+
} finally {
|
|
489
|
+
await unlink(barrelPath).catch(ignoreMissing);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function foldOutput(text) {
|
|
493
|
+
return { text, specifiers: [...text.matchAll(/"[./][^"]*"/g)].map((match) => match[0]) };
|
|
494
|
+
}
|
|
495
|
+
async function createFoldState(outDir, metafile) {
|
|
496
|
+
const outputs = new Map;
|
|
497
|
+
const referrers = new Map;
|
|
498
|
+
const index = (file, output, add) => {
|
|
499
|
+
for (const spec of output.specifiers) {
|
|
500
|
+
const base = path.basename(spec.slice(1, -1));
|
|
501
|
+
const set = referrers.get(base) ?? referrers.set(base, new Set).get(base);
|
|
502
|
+
if (add)
|
|
503
|
+
set.add(file);
|
|
504
|
+
else
|
|
505
|
+
set.delete(file);
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
const files = new Set;
|
|
509
|
+
for (const name of Object.keys(metafile.outputs)) {
|
|
510
|
+
if (!name.endsWith(".js"))
|
|
511
|
+
continue;
|
|
512
|
+
const resolved = path.resolve(name);
|
|
513
|
+
const local = existsSync(resolved) ? resolved : path.join(outDir, path.basename(name));
|
|
514
|
+
if (existsSync(local))
|
|
515
|
+
files.add(local);
|
|
516
|
+
}
|
|
517
|
+
const list = [...files];
|
|
518
|
+
const texts = await Promise.all(list.map((file) => readText(file)));
|
|
519
|
+
for (const [at, file] of list.entries()) {
|
|
520
|
+
const output = foldOutput(texts[at]);
|
|
521
|
+
outputs.set(file, output);
|
|
522
|
+
index(file, output, true);
|
|
523
|
+
}
|
|
524
|
+
return {
|
|
525
|
+
outputs,
|
|
526
|
+
referrersOf(names) {
|
|
527
|
+
const seen = new Set;
|
|
528
|
+
for (const name of names)
|
|
529
|
+
for (const file of referrers.get(name) ?? [])
|
|
530
|
+
seen.add(file);
|
|
531
|
+
return seen;
|
|
532
|
+
},
|
|
533
|
+
set(file, output) {
|
|
534
|
+
const previous = outputs.get(file);
|
|
535
|
+
if (previous)
|
|
536
|
+
index(file, previous, false);
|
|
537
|
+
outputs.set(file, output);
|
|
538
|
+
index(file, output, true);
|
|
539
|
+
},
|
|
540
|
+
remove(file) {
|
|
541
|
+
const previous = outputs.get(file);
|
|
542
|
+
if (previous)
|
|
543
|
+
index(file, previous, false);
|
|
544
|
+
outputs.delete(file);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
function mergeMetafile(metafile, members, mergedPath, bytes) {
|
|
549
|
+
const memberSet = new Set(members);
|
|
550
|
+
const merged = path.relative(process.cwd(), mergedPath);
|
|
551
|
+
const outputs = {};
|
|
552
|
+
const mergedImports = [];
|
|
553
|
+
const mergedInputs = {};
|
|
554
|
+
for (const [file, output] of Object.entries(metafile.outputs)) {
|
|
555
|
+
const remap = (list) => {
|
|
556
|
+
const seen = new Set;
|
|
557
|
+
return list.flatMap((imported) => {
|
|
558
|
+
const target = memberSet.has(imported.path) ? merged : imported.path;
|
|
559
|
+
const key = `${target}:${imported.kind}`;
|
|
560
|
+
if (seen.has(key))
|
|
561
|
+
return [];
|
|
562
|
+
seen.add(key);
|
|
563
|
+
return [{ ...imported, path: target }];
|
|
564
|
+
});
|
|
565
|
+
};
|
|
566
|
+
if (memberSet.has(file)) {
|
|
567
|
+
Object.assign(mergedInputs, output.inputs);
|
|
568
|
+
for (const imported of remap(output.imports ?? [])) {
|
|
569
|
+
if (imported.path !== merged)
|
|
570
|
+
mergedImports.push(imported);
|
|
571
|
+
}
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
outputs[file] = { ...output, imports: remap(output.imports ?? []) };
|
|
575
|
+
}
|
|
576
|
+
outputs[merged] = {
|
|
577
|
+
imports: mergedImports,
|
|
578
|
+
exports: [],
|
|
579
|
+
inputs: mergedInputs,
|
|
580
|
+
bytes
|
|
581
|
+
};
|
|
582
|
+
return { ...metafile, outputs };
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/client/entry.ts
|
|
586
|
+
import path2 from "path";
|
|
587
|
+
function routerModulePath() {
|
|
588
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "./router/index.ts");
|
|
589
|
+
}
|
|
590
|
+
function actionRouterModulePath() {
|
|
591
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/actions/router.ts");
|
|
592
|
+
}
|
|
593
|
+
function actionClientModulePath() {
|
|
594
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/actions/client.ts");
|
|
595
|
+
}
|
|
596
|
+
function navCompatModulePath() {
|
|
597
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/client/nav.ts");
|
|
598
|
+
}
|
|
599
|
+
function islandContextModulePath() {
|
|
600
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../render/island-context.ts");
|
|
601
|
+
}
|
|
602
|
+
function staticSlotsModulePath() {
|
|
603
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../render/static-slots-revive.ts");
|
|
604
|
+
}
|
|
605
|
+
function serializeModulePath() {
|
|
606
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../utils/serialize.ts");
|
|
607
|
+
}
|
|
608
|
+
function layoutSegmentHelperSource(nextCompat) {
|
|
609
|
+
const seed = `
|
|
610
|
+
function pnextIslandMask(vnode, root) {
|
|
611
|
+
const mask = root.__pnextMask ??= [root.getAttribute('data-pnext-id'), 0];
|
|
612
|
+
Object.defineProperty(vnode, '__m', { enumerable: true, get() { return (this.__c || this).__pnextMask ??= mask; } });
|
|
613
|
+
}`;
|
|
614
|
+
if (!nextCompat)
|
|
615
|
+
return `${seed}
|
|
616
|
+
function pnextIslandVNode(h, Component, props, children, root) {
|
|
617
|
+
const vnode = children !== undefined ? h(Component, props, children) : h(Component, props);
|
|
618
|
+
pnextIslandMask(vnode, root);
|
|
619
|
+
return vnode;
|
|
620
|
+
}
|
|
621
|
+
`;
|
|
622
|
+
return `${seed}
|
|
623
|
+
import { LayoutSegmentContext as __PNextLayoutSegmentContext, RouteParamsContext as __PNextRouteParamsContext } from ${JSON.stringify(islandContextModulePath())};
|
|
624
|
+
function pnextIslandReadJson(root, attr) {
|
|
625
|
+
const raw = root && root.getAttribute ? root.getAttribute(attr) : null;
|
|
626
|
+
if (!raw) return null;
|
|
627
|
+
try { return JSON.parse(raw); } catch (e) { return null; }
|
|
628
|
+
}
|
|
629
|
+
function pnextIslandVNode(h, Component, props, children, root) {
|
|
630
|
+
// An explicit undefined children argument clobbers props.children in
|
|
631
|
+
// preact's createElement; plain children travel inside the parsed props.
|
|
632
|
+
let vnode = children !== undefined ? h(Component, props, children) : h(Component, props);
|
|
633
|
+
pnextIslandMask(vnode, root);
|
|
634
|
+
const segments = pnextIslandReadJson(root, 'data-pnext-layout-segments');
|
|
635
|
+
if (segments) vnode = h(__PNextLayoutSegmentContext.Provider, { value: segments }, vnode);
|
|
636
|
+
const params = pnextIslandReadJson(root, 'data-pnext-params');
|
|
637
|
+
if (params) vnode = h(__PNextRouteParamsContext.Provider, { value: params }, vnode);
|
|
638
|
+
return vnode;
|
|
639
|
+
}
|
|
640
|
+
`;
|
|
641
|
+
}
|
|
642
|
+
function islandVNodeExpr(componentExpr, propsExpr, childrenExpr, rootExpr) {
|
|
643
|
+
return `pnextIslandVNode(h, ${componentExpr}, ${propsExpr}, ${childrenExpr}, ${rootExpr})`;
|
|
644
|
+
}
|
|
645
|
+
function holeSeamSource() {
|
|
646
|
+
return `
|
|
647
|
+
class PnextHole extends Component {
|
|
648
|
+
componentDidMount() {
|
|
649
|
+
if (this.base) this.base.__pnextHole = this;
|
|
650
|
+
}
|
|
651
|
+
componentWillUnmount() {
|
|
652
|
+
if (this.base && this.base.__pnextHole === this) this.base.__pnextHole = undefined;
|
|
653
|
+
}
|
|
654
|
+
componentWillReceiveProps(next) {
|
|
655
|
+
// A soft navigation re-adopts the parent from the incoming document's DOM;
|
|
656
|
+
// the previous document's grafted content must not outlive its children.
|
|
657
|
+
if (next.children !== this.props.children) this.setState({ adopted: undefined });
|
|
658
|
+
}
|
|
659
|
+
render(props, state) {
|
|
660
|
+
return h('pnext-hole', props.attrs, state.adopted !== undefined ? state.adopted : props.children);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
`;
|
|
664
|
+
}
|
|
665
|
+
function lateIslandClaimSource() {
|
|
666
|
+
return `
|
|
667
|
+
// The graft ($RC) swapped this hole's DOM wholesale, so re-adopting ALL of it is total: nothing in
|
|
668
|
+
// the hole is still preact-owned. The re-render lands inside the parent island's tree, which is what
|
|
669
|
+
// makes the new islands inherit its context.
|
|
670
|
+
// Detaching an element costs it every piece of state the browser keeps outside the DOM - a lazy
|
|
671
|
+
// image's deferred load observation, an iframe's document, playing media, focus - and re-inserting
|
|
672
|
+
// it does not bring any of that back. moveBefore is the state-preserving move (Chrome 133+); it
|
|
673
|
+
// needs both ends connected and refuses some moves outright, so a plain insert stays the fallback.
|
|
674
|
+
function movePnextNode(parent, node) {
|
|
675
|
+
if (parent.moveBefore && parent.isConnected && node.isConnected) {
|
|
676
|
+
try {
|
|
677
|
+
parent.moveBefore(node, null);
|
|
678
|
+
return;
|
|
679
|
+
} catch (error) {}
|
|
680
|
+
}
|
|
681
|
+
parent.append(node);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// A subtree rebuilt from adopted vnodes is made of freshly created elements, and a graft can bring
|
|
685
|
+
// in ones the browser had already deferred. Either way a lazy image can end up in the document with
|
|
686
|
+
// no live intersection observation - no request ever fires, and it stays blank forever. Re-running
|
|
687
|
+
// its source selection re-arms it WITHOUT giving up laziness; one already loading (currentSrc set)
|
|
688
|
+
// or already loaded is left alone, so this never forces a fetch or duplicates one.
|
|
689
|
+
// Drop the capture holder, moving its nodes into \`into\` first when the adoption cannot use them.
|
|
690
|
+
function releaseHoleCapture(hole, into) {
|
|
691
|
+
const captured = hole.__pnextCaptured;
|
|
692
|
+
if (!captured) return;
|
|
693
|
+
hole.__pnextCaptured = undefined;
|
|
694
|
+
if (into) for (const node of [...captured.childNodes]) movePnextNode(into, node);
|
|
695
|
+
captured.remove();
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
function rekickLazyLoadables(root) {
|
|
699
|
+
for (const img of root.querySelectorAll('img[loading=lazy]')) {
|
|
700
|
+
if (img.complete || img.currentSrc) continue;
|
|
701
|
+
for (const name of ['src', 'srcset']) {
|
|
702
|
+
const value = img.getAttribute(name);
|
|
703
|
+
if (value === null) continue;
|
|
704
|
+
img.removeAttribute(name);
|
|
705
|
+
img.setAttribute(name, value);
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
async function adoptHoleSeam(hole) {
|
|
711
|
+
const pending = hole.__pnextAdopting;
|
|
712
|
+
if (pending) return pending;
|
|
713
|
+
const seam = hole.__pnextHole;
|
|
714
|
+
// No seam ever appeared: put the captured nodes back rather than losing them.
|
|
715
|
+
if (!seam) return releaseHoleCapture(hole, hole);
|
|
716
|
+
const adopting = (async () => {
|
|
717
|
+
const captured = hole.__pnextCaptured;
|
|
718
|
+
const children = await domChildren(captured ?? hole);
|
|
719
|
+
// Preact rebuilds the subtree from the adopted vnodes, so the server nodes they were read from
|
|
720
|
+
// would survive as unowned duplicates. Clearing and re-rendering within one task keeps the swap
|
|
721
|
+
// inside a single frame - preact's rerender is a microtask, so no paint sees the empty hole.
|
|
722
|
+
releaseHoleCapture(hole);
|
|
723
|
+
hole.replaceChildren();
|
|
724
|
+
seam.setState({ adopted: children ?? null }, () => rekickLazyLoadables(hole));
|
|
725
|
+
})();
|
|
726
|
+
hole.__pnextAdopting = adopting;
|
|
727
|
+
try {
|
|
728
|
+
await adopting;
|
|
729
|
+
} finally {
|
|
730
|
+
if (hole.__pnextAdopting === adopting) hole.__pnextAdopting = undefined;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// A nested host belongs to its parent island's DOM adoption pass, never to a mount of its own. Wait
|
|
735
|
+
// out an adoption already in flight (its walk may still reach this host), and claim only what the
|
|
736
|
+
// parent can no longer see - a host the graft materialized after the parent went live - through the
|
|
737
|
+
// hole seam that owns the grafted DOM. A host under a parent that has not mounted yet stays put.
|
|
738
|
+
async function claimNestedIslandRoot(root, parent) {
|
|
739
|
+
// The innermost hole whose seam is live owns this host. A nearer hole with no seam is raw grafted
|
|
740
|
+
// DOM inside an outer seam's region, so that outer one is the tree to re-adopt through.
|
|
741
|
+
let hole;
|
|
742
|
+
for (let node = root.parentElement; node && node !== parent; node = node.parentElement) {
|
|
743
|
+
if (node.localName !== 'pnext-hole') continue;
|
|
744
|
+
if (node.__pnextHole) { hole = node; break; }
|
|
745
|
+
hole ??= node;
|
|
746
|
+
}
|
|
747
|
+
// The parent is mid-adoption and has no seam here yet: its walk may already have snapshotted this
|
|
748
|
+
// hole's fallback, and its hydration then drops the DOM the graft just landed. Park the grafted
|
|
749
|
+
// nodes synchronously - ahead of that render - and adopt them from the holder once the tree is
|
|
750
|
+
// live. A walk that had NOT reached the hole yet simply adopts an empty one and gets the same
|
|
751
|
+
// content back through the seam. The holder is a hidden ELEMENT rather than a fragment: a
|
|
752
|
+
// disconnected parent is exactly what strips browser state, and moveBefore refuses one.
|
|
753
|
+
if (hole && !hole.__pnextHole && !hole.__pnextCaptured && parent.__pnextMounting) {
|
|
754
|
+
const holder = document.createElement('pnext-hole-capture');
|
|
755
|
+
holder.hidden = true;
|
|
756
|
+
document.body.append(holder);
|
|
757
|
+
hole.__pnextCaptured = holder;
|
|
758
|
+
for (const node of [...hole.childNodes]) movePnextNode(holder, node);
|
|
759
|
+
}
|
|
760
|
+
// A parent whose module failed still settles: the claim has to release its capture rather than
|
|
761
|
+
// leave the grafted nodes parked in a holder nobody will ever adopt from.
|
|
762
|
+
await parent.__pnextMounting?.catch(() => {});
|
|
763
|
+
if (!hole || (!hole.__pnextCaptured && !root.isConnected)) return;
|
|
764
|
+
return adoptHoleSeam(hole);
|
|
765
|
+
}
|
|
766
|
+
`;
|
|
767
|
+
}
|
|
768
|
+
function mountOnceSource() {
|
|
769
|
+
return `
|
|
770
|
+
async function mountIslandOnce(root, mountTree) {
|
|
771
|
+
const pending = root.__pnextMounting;
|
|
772
|
+
if (pending) {
|
|
773
|
+
await pending;
|
|
774
|
+
// The other pass mounted this root; only a freshly stashed incoming
|
|
775
|
+
// placeholder (a soft-nav graft) still needs this pass to re-render.
|
|
776
|
+
if (root.__pnextLive && !root.__pnextIncoming) return;
|
|
777
|
+
}
|
|
778
|
+
const mounting = mountTree();
|
|
779
|
+
root.__pnextMounting = mounting;
|
|
780
|
+
try {
|
|
781
|
+
await mounting;
|
|
782
|
+
} finally {
|
|
783
|
+
if (root.__pnextMounting === mounting) root.__pnextMounting = undefined;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
`;
|
|
787
|
+
}
|
|
788
|
+
function clientErrorsInstallPath() {
|
|
789
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/client/errors/lazy.ts");
|
|
790
|
+
}
|
|
791
|
+
function clientErrorBoundaryPath() {
|
|
792
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/client/errors/error-boundary.ts");
|
|
793
|
+
}
|
|
794
|
+
function softRefreshModulePath() {
|
|
795
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/client/errors/soft-refresh.ts");
|
|
796
|
+
}
|
|
797
|
+
function bareErrorBoundaryPath() {
|
|
798
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/client/errors/bare-boundary.ts");
|
|
799
|
+
}
|
|
800
|
+
function boundaryImportSource(nextCompat, boundary, builtinNotFound) {
|
|
801
|
+
if (!nextCompat)
|
|
802
|
+
return "";
|
|
803
|
+
if (!boundary) {
|
|
804
|
+
return `import { BareErrorBoundary } from ${JSON.stringify(bareErrorBoundaryPath())};`;
|
|
805
|
+
}
|
|
806
|
+
return `import { ClientErrorBoundary } from ${JSON.stringify(clientErrorBoundaryPath())};
|
|
807
|
+
import { softRefreshRoute } from ${JSON.stringify(softRefreshModulePath())};${builtinNotFound ? `
|
|
808
|
+
${builtInNotFoundSource()}` : ""}`;
|
|
809
|
+
}
|
|
810
|
+
function errorUiModulePath() {
|
|
811
|
+
return path2.join(__PNEXT_PACKAGE_ROOT__ + "/src/client", "../compat/lifecycle/error-ui.ts");
|
|
812
|
+
}
|
|
813
|
+
function builtInNotFoundSource() {
|
|
814
|
+
return `import { httpAccessFallbackUi as __pnextHttpAccessFallbackUi } from ${JSON.stringify(errorUiModulePath())};
|
|
815
|
+
function __PNextBuiltInNotFound() {
|
|
816
|
+
return __pnextHttpAccessFallbackUi(404, 'This page could not be found.');
|
|
817
|
+
}`;
|
|
818
|
+
}
|
|
819
|
+
function hasErrorBoundary(facts) {
|
|
820
|
+
return Boolean(facts.errorFile ?? facts.notFoundFile) || facts.controlFlow === true;
|
|
821
|
+
}
|
|
822
|
+
function routerImportSource({
|
|
823
|
+
nextCompat,
|
|
824
|
+
actions,
|
|
825
|
+
errorFile,
|
|
826
|
+
globalErrorFile,
|
|
827
|
+
notFoundFile,
|
|
828
|
+
router
|
|
829
|
+
}) {
|
|
830
|
+
const base = router ? `import { installRouter, routerImportActive } from ${JSON.stringify(routerModulePath())};` : "";
|
|
831
|
+
if (!nextCompat)
|
|
832
|
+
return base;
|
|
833
|
+
const imports = [
|
|
834
|
+
`${base}
|
|
835
|
+
import { installNavCompat } from ${JSON.stringify(navCompatModulePath())};
|
|
836
|
+
import { installClientErrors } from ${JSON.stringify(clientErrorsInstallPath())};`
|
|
837
|
+
];
|
|
838
|
+
if (actions) {
|
|
839
|
+
imports.push(`import { installActionRouter } from ${JSON.stringify(actionRouterModulePath())};
|
|
840
|
+
import { isActionError } from ${JSON.stringify(actionClientModulePath())};`);
|
|
841
|
+
}
|
|
842
|
+
if (errorFile) {
|
|
843
|
+
if (actions) {
|
|
844
|
+
imports.push(`import { installActionErrorOverlay } from ${JSON.stringify(actionClientModulePath())};`);
|
|
845
|
+
}
|
|
846
|
+
imports.push(`import __PNextRouteErrorComponent from ${JSON.stringify(errorFile)};`);
|
|
847
|
+
}
|
|
848
|
+
if (globalErrorFile) {
|
|
849
|
+
imports.push(`import __PNextGlobalErrorComponent from ${JSON.stringify(globalErrorFile)};`);
|
|
850
|
+
}
|
|
851
|
+
if (notFoundFile) {
|
|
852
|
+
imports.push(`import __PNextRouteNotFoundComponent from ${JSON.stringify(notFoundFile)};`);
|
|
853
|
+
}
|
|
854
|
+
return imports.join(`
|
|
855
|
+
`);
|
|
856
|
+
}
|
|
857
|
+
function syncPropsHelperSource() {
|
|
858
|
+
return `
|
|
859
|
+
import { rebuildSyncProps as __pnextRebuildSyncProps } from ${JSON.stringify(serializeModulePath())};
|
|
860
|
+
const __pnextSyncProps = () => (typeof window !== 'undefined' && window.__PNEXT_SYNC_PROPS__ ? __pnextRebuildSyncProps : undefined);
|
|
861
|
+
`;
|
|
862
|
+
}
|
|
863
|
+
function propsParserSource(nextCompat) {
|
|
864
|
+
const revive = nextCompat ? "reviveSerializedErrorRefs" : "reviveSerializedRefs";
|
|
865
|
+
return `
|
|
866
|
+
import { ${revive} as __pnextReviveSerializedRefs, hasPromiseProps as __pnextHasPromiseProps, revivePromiseMarkers as __pnextRevivePromiseMarkers } from ${JSON.stringify(serializeModulePath())};
|
|
867
|
+
import { hasIslandStaticSlots as __pnextHasIslandSlots, reviveIslandStaticSlots as __pnextReviveIslandSlots } from ${JSON.stringify(staticSlotsModulePath())};
|
|
868
|
+
// Element-valued props: the wire carries a \`$$pnext_slot\` id per element and the server rendered it
|
|
869
|
+
// inside a matching \`pnext-static-slot\` host, adopted here exactly like element children. Islands
|
|
870
|
+
// with no element props skip the walk on a substring test of the raw attribute.
|
|
871
|
+
// \`h\` is threaded in rather than imported by the slot reviver: that would put preact in the static
|
|
872
|
+
// graph of entries (visible-dynamic) that otherwise only lazy-import it.
|
|
873
|
+
function islandProps(raw, root, toChildren, h) {
|
|
874
|
+
const props = parseIslandProps(raw);
|
|
875
|
+
if (!__pnextHasIslandSlots(raw)) return props;
|
|
876
|
+
return __pnextReviveIslandSlots(props, root, toChildren, h);
|
|
877
|
+
}
|
|
878
|
+
function parseIslandProps(raw) {
|
|
879
|
+
const props = JSON.parse(raw || '{}');${nextCompat ? `
|
|
880
|
+
const actions = typeof window !== 'undefined' ? window.__PNEXT_ACTIONS__ : undefined;` : ""}
|
|
881
|
+
// Cyclic props: the encoder wrote the self-reference as a \`$$pnext_ref\` path
|
|
882
|
+
// marker. Patch the original object back in AFTER action revival (which
|
|
883
|
+
// rebuilds plain objects, so resolving first would restore identity onto
|
|
884
|
+
// objects the revival then replaces).
|
|
885
|
+
const revived = __pnextReviveSerializedRefs(${nextCompat ? "actions ? actions.reviveProps(props) : props" : "props"});
|
|
886
|
+
// Promise props travel as markers, at the top level (a client slot page's params/searchParams) and
|
|
887
|
+
// nested in plain containers (a dehydrated react-query state's pending-query promise). Revive both
|
|
888
|
+
// into pre-fulfilled promises - see revivePromiseMarkers. The deep walk is gated on a substring
|
|
889
|
+
// test of the raw attribute, so an island with no promise props pays one indexOf.
|
|
890
|
+
return __pnextHasPromiseProps(raw || '') ? __pnextRevivePromiseMarkers(revived${nextCompat ? ", __pnextSyncProps()" : ""}) : revived;
|
|
891
|
+
}
|
|
892
|
+
`;
|
|
893
|
+
}
|
|
894
|
+
var MEASURED_HOST_TAGS = ["pnext-client", "pnext-dynamic", "pnext-static-children"];
|
|
895
|
+
function hostMeasureSource() {
|
|
896
|
+
return `
|
|
897
|
+
function pnextInstallHostMeasure() {
|
|
898
|
+
if (typeof customElements === 'undefined' || window.__PNEXT_HOST_MEASURE__) return;
|
|
899
|
+
window.__PNEXT_HOST_MEASURE__ = true;
|
|
900
|
+
const box = el =>
|
|
901
|
+
el.style.display === 'contents' || getComputedStyle(el).display === 'contents'
|
|
902
|
+
? el.parentElement
|
|
903
|
+
: null;
|
|
904
|
+
class PnextHost extends HTMLElement {
|
|
905
|
+
getBoundingClientRect() {
|
|
906
|
+
const target = box(this);
|
|
907
|
+
return target ? target.getBoundingClientRect() : super.getBoundingClientRect();
|
|
908
|
+
}
|
|
909
|
+
getClientRects() {
|
|
910
|
+
const target = box(this);
|
|
911
|
+
return target ? target.getClientRects() : super.getClientRects();
|
|
912
|
+
}
|
|
913
|
+
}
|
|
914
|
+
for (const key of ['clientWidth', 'clientHeight', 'offsetWidth', 'offsetHeight', 'offsetTop', 'offsetLeft']) {
|
|
915
|
+
Object.defineProperty(PnextHost.prototype, key, {
|
|
916
|
+
configurable: true,
|
|
917
|
+
get() {
|
|
918
|
+
const target = box(this);
|
|
919
|
+
return target ? target[key] : 0;
|
|
920
|
+
},
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
for (const tag of ${JSON.stringify(MEASURED_HOST_TAGS)}) {
|
|
924
|
+
if (!customElements.get(tag)) customElements.define(tag, class extends PnextHost {});
|
|
925
|
+
}
|
|
926
|
+
// Observing the same element twice is a single native observation, so
|
|
927
|
+
// redirecting host -> parent dedupes on its own.
|
|
928
|
+
const observed = target => (target instanceof PnextHost ? (box(target) ?? target) : target);
|
|
929
|
+
const NativeResizeObserver = window.ResizeObserver;
|
|
930
|
+
if (NativeResizeObserver) {
|
|
931
|
+
window.ResizeObserver = class extends NativeResizeObserver {
|
|
932
|
+
observe(target, options) {
|
|
933
|
+
super.observe(observed(target), options);
|
|
934
|
+
}
|
|
935
|
+
unobserve(target) {
|
|
936
|
+
super.unobserve(observed(target));
|
|
937
|
+
}
|
|
938
|
+
};
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
pnextInstallHostMeasure();
|
|
942
|
+
`;
|
|
943
|
+
}
|
|
944
|
+
function mountLifecycleSource(nextCompat) {
|
|
945
|
+
return `
|
|
946
|
+
${nextCompat ? `// __NEXT_HYDRATED is the harness's interaction gate (test/lib/next-webdriver.ts
|
|
947
|
+
// waits on it and clicks immediately after), so it must mean "the visible tree is interactive", not
|
|
948
|
+
// "the island mounts were kicked off". preact's hydrate() attaches listeners during its synchronous
|
|
949
|
+
// commit, so the mount promises settling IS commit-level readiness. The gate waits for every promise
|
|
950
|
+
// that owns visible DOM and flips in a microtask so nothing extra is interposed under CPU load.
|
|
951
|
+
// pnextGateDeadline is a deadlock guard only: a wedged dynamic import must degrade to a late gate,
|
|
952
|
+
// never to a gate that never opens.
|
|
953
|
+
const pnextGateDeadline = 10000;
|
|
954
|
+
function pnextSettled(value) {
|
|
955
|
+
if (value === undefined) return undefined;
|
|
956
|
+
return Promise.resolve(value).then(pnextIgnore, pnextIgnore);
|
|
957
|
+
}
|
|
958
|
+
function pnextIgnore() {}
|
|
959
|
+
function markRouteHydrated() {
|
|
960
|
+
window.__NEXT_HYDRATED = true;
|
|
961
|
+
window.__NEXT_HYDRATED_AT ??= performance.now();
|
|
962
|
+
if (typeof window.__NEXT_HYDRATED_CB === 'function') window.__NEXT_HYDRATED_CB();
|
|
963
|
+
window.dispatchEvent(new Event('pnext:hydrated'));
|
|
964
|
+
}
|
|
965
|
+
// A settled mount promise means the DOM is attached, NOT that useEffect ran - preact defers effects
|
|
966
|
+
// to after-paint, so a layout publishing its handle from an effect is still unset when
|
|
967
|
+
// __NEXT_HYDRATED flips. Drive preact's flush through options.requestAnimationFrame so "effects ran"
|
|
968
|
+
// is an observable event the gate can await (plain rAF/macrotask ordering is not reliable in headless
|
|
969
|
+
// Chromium).
|
|
970
|
+
let pnextPendingEffectFlushes = 0;
|
|
971
|
+
let pnextEffectFlushWaiters = [];
|
|
972
|
+
const pnextEffectFlushRafTimeout = 100;
|
|
973
|
+
function pnextDrainEffectFlush(flush) {
|
|
974
|
+
pnextPendingEffectFlushes--;
|
|
975
|
+
try {
|
|
976
|
+
flush();
|
|
977
|
+
} finally {
|
|
978
|
+
if (pnextPendingEffectFlushes <= 0) {
|
|
979
|
+
pnextPendingEffectFlushes = 0;
|
|
980
|
+
const waiters = pnextEffectFlushWaiters;
|
|
981
|
+
pnextEffectFlushWaiters = [];
|
|
982
|
+
for (const waiter of waiters) waiter();
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
__pnextPreactOptions.requestAnimationFrame = flush => {
|
|
987
|
+
pnextPendingEffectFlushes++;
|
|
988
|
+
let ran = false;
|
|
989
|
+
const run = () => {
|
|
990
|
+
if (ran) return;
|
|
991
|
+
ran = true;
|
|
992
|
+
pnextDrainEffectFlush(flush);
|
|
993
|
+
};
|
|
994
|
+
// preact's own afterNextFrame semantics: the frame, or a 100ms fallback for a
|
|
995
|
+
// document whose rAF never fires (a backgrounded page).
|
|
996
|
+
const timer = setTimeout(run, pnextEffectFlushRafTimeout);
|
|
997
|
+
if (typeof requestAnimationFrame === 'function') {
|
|
998
|
+
requestAnimationFrame(() => {
|
|
999
|
+
clearTimeout(timer);
|
|
1000
|
+
run();
|
|
1001
|
+
});
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
function pnextAfterEffectFlush(run) {
|
|
1005
|
+
// Effects are queued during the mount's synchronous commit, so by the time
|
|
1006
|
+
// the mount promise settles every pending flush is already registered above.
|
|
1007
|
+
// Nothing pending means there is nothing left to wait for.
|
|
1008
|
+
if (pnextPendingEffectFlushes === 0) {
|
|
1009
|
+
run();
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
pnextEffectFlushWaiters.push(run);
|
|
1013
|
+
}
|
|
1014
|
+
function pnextGate(mark, islandMounts, visibleMount) {
|
|
1015
|
+
const waits = [pnextSettled(islandMounts), pnextSettled(visibleMount)].filter(Boolean);
|
|
1016
|
+
if (waits.length === 0) {
|
|
1017
|
+
mark();
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
let done = false;
|
|
1021
|
+
const once = () => {
|
|
1022
|
+
if (done) return;
|
|
1023
|
+
done = true;
|
|
1024
|
+
mark();
|
|
1025
|
+
};
|
|
1026
|
+
const timer = setTimeout(once, pnextGateDeadline);
|
|
1027
|
+
void Promise.all(waits).then(() => {
|
|
1028
|
+
pnextAfterEffectFlush(() => {
|
|
1029
|
+
clearTimeout(timer);
|
|
1030
|
+
once();
|
|
1031
|
+
});
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
` : ""}
|
|
1035
|
+
function keepsMountedRoot(root, keep) {
|
|
1036
|
+
if (!keep) return false;
|
|
1037
|
+
for (const kept of keep) {
|
|
1038
|
+
if (root === kept) return true;
|
|
1039
|
+
// The preserved client page's container is a marker-range proxy, not a
|
|
1040
|
+
// Node \u2014 contains() would throw on it, and only identity can match it.
|
|
1041
|
+
if (kept instanceof Node && root.contains && root.contains(kept)) return true;
|
|
1042
|
+
}
|
|
1043
|
+
return false;
|
|
1044
|
+
}
|
|
1045
|
+
`;
|
|
1046
|
+
}
|
|
1047
|
+
function routeLifecycleSource({ nextCompat, islands, page, shell, dev }) {
|
|
1048
|
+
const gate = nextCompat ? ` pnextGate(markRouteHydrated, pnextIslandMounts, pnextVisibleMount);
|
|
1049
|
+
` : "";
|
|
1050
|
+
const islandMounts = dev ? `pnextIslandScan.done.then(() => {
|
|
1051
|
+
diagnoseSkippedIslands();
|
|
1052
|
+
return pnextIslandScan.mounted;
|
|
1053
|
+
})` : "pnextIslandScan.done.then(() => pnextIslandScan.mounted)";
|
|
1054
|
+
const mountBody = [
|
|
1055
|
+
islands ? ` if (islands.length > 0) {
|
|
1056
|
+
pnextIslandScan = scanIslands();
|
|
1057
|
+
pnextIslandMounts = ${islandMounts};
|
|
1058
|
+
}` : "",
|
|
1059
|
+
shell ? " if (shell) pnextVisibleMount = hydrateClientShell();" : "",
|
|
1060
|
+
page ? ` if (Page) {
|
|
1061
|
+
if (islands.length > 0) pnextVisibleMount = hydratePageAfterIslands();
|
|
1062
|
+
else hydratePage();
|
|
1063
|
+
}` : ""
|
|
1064
|
+
].filter(Boolean).join(`
|
|
1065
|
+
`);
|
|
1066
|
+
return `
|
|
1067
|
+
function mountRoute() {
|
|
1068
|
+
${mountBody}
|
|
1069
|
+
window.__PNEXT_ACTIVE_ENTRY__ = { unmount: unmountRoute };
|
|
1070
|
+
${gate} return pnextIslandMounts;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// A postponed boundary can flush AFTER this entry module has evaluated: the shell's async module
|
|
1074
|
+
// scripts race the server's tail, and a server under load loses that race - the initial mountRoute()
|
|
1075
|
+
// then sees a document holding only the placeholder, mounts nothing, and the tree stays inert
|
|
1076
|
+
// FOREVER, because promotion has no way back into the entry. The promotion script calls this hook
|
|
1077
|
+
// once the real DOM is parsed in. Every step is guarded, so re-running them hydrates only what the
|
|
1078
|
+
// first pass could not see.
|
|
1079
|
+
function remountRoute() {
|
|
1080
|
+
${mountBody}
|
|
1081
|
+
${gate} return pnextIslandMounts;
|
|
1082
|
+
}
|
|
1083
|
+
window.__PNEXT_MOUNT_ISLANDS__ = remountRoute;
|
|
1084
|
+
|
|
1085
|
+
function unmountRoute(keep) {
|
|
1086
|
+
// Roots in \`keep\` are grafted into the next document by the router (shared
|
|
1087
|
+
// layout preservation) \u2014 leave them and any mounted ancestor intact; an
|
|
1088
|
+
// ancestor unmount would recursively tear down the preserved island.
|
|
1089
|
+
for (const root of pnextMountedRoots) {
|
|
1090
|
+
if (keepsMountedRoot(root, keep)) continue;
|
|
1091
|
+
render(null, root);
|
|
1092
|
+
root.__pnextLive = undefined;
|
|
1093
|
+
}
|
|
1094
|
+
// A preserved client page keeps its registration: its tree is still mounted
|
|
1095
|
+
// and grafted into the new body, so hydratePage must see it as live instead
|
|
1096
|
+
// of re-hydrating over it. Every other root is dropped \u2014 island roots kept
|
|
1097
|
+
// for the bfcache are detached and owned by the cache from here on.
|
|
1098
|
+
const page = window.__PNEXT_CLIENT_PAGE_ROOT__;
|
|
1099
|
+
let keepsPage = false;
|
|
1100
|
+
if (page && keep) for (const kept of keep) if (kept === page) keepsPage = true;
|
|
1101
|
+
pnextMountedRoots.clear();
|
|
1102
|
+
if (keepsPage) pnextMountedRoots.add(page);
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
return { mountRoute, remountRoute, unmountRoute };
|
|
1106
|
+
`;
|
|
1107
|
+
}
|
|
1108
|
+
function routerOnlyEntrySource(facts) {
|
|
1109
|
+
const mark = facts.nextCompat ? `
|
|
1110
|
+
function markRouteHydrated() {
|
|
1111
|
+
window.__NEXT_HYDRATED = true;
|
|
1112
|
+
window.__NEXT_HYDRATED_AT ??= performance.now();
|
|
1113
|
+
if (typeof window.__NEXT_HYDRATED_CB === 'function') window.__NEXT_HYDRATED_CB();
|
|
1114
|
+
window.dispatchEvent(new Event('pnext:hydrated'));
|
|
1115
|
+
}
|
|
1116
|
+
` : "";
|
|
1117
|
+
return `
|
|
1118
|
+
${routerImportSource(facts)}
|
|
1119
|
+
${mark}
|
|
1120
|
+
export function mountRoute() {
|
|
1121
|
+
window.__PNEXT_ACTIVE_ENTRY__ = { unmount: unmountRoute };
|
|
1122
|
+
${facts.nextCompat ? ` markRouteHydrated();
|
|
1123
|
+
` : ""}}
|
|
1124
|
+
|
|
1125
|
+
export function unmountRoute() {}
|
|
1126
|
+
|
|
1127
|
+
${installRouterCall(facts.router)}${installCallsSource(facts)}
|
|
1128
|
+
if (${autoMountGuard(facts.router)}) mountRoute();
|
|
1129
|
+
if (window.__PNEXT_REGISTER_ENTRY__) window.__PNEXT_REGISTER_ENTRY__(import.meta.url, { mountRoute, unmountRoute });
|
|
1130
|
+
`;
|
|
1131
|
+
}
|
|
1132
|
+
function installRouterCall(router) {
|
|
1133
|
+
return router ? `installRouter();
|
|
1134
|
+
` : "";
|
|
1135
|
+
}
|
|
1136
|
+
function autoMountGuard(router) {
|
|
1137
|
+
return router ? "!routerImportActive()" : "!((window.__PNEXT_ROUTER_IMPORTS__ ?? 0) > 0)";
|
|
1138
|
+
}
|
|
1139
|
+
function installCallsSource({
|
|
1140
|
+
nextCompat,
|
|
1141
|
+
actions,
|
|
1142
|
+
errorFile,
|
|
1143
|
+
globalErrorFile,
|
|
1144
|
+
notFoundFile
|
|
1145
|
+
}) {
|
|
1146
|
+
if (!nextCompat)
|
|
1147
|
+
return "";
|
|
1148
|
+
const overlay = actions && errorFile ? `installActionErrorOverlay(__PNextRouteErrorComponent);
|
|
1149
|
+
` : "";
|
|
1150
|
+
return `${actions ? `installActionRouter();
|
|
1151
|
+
` : ""}installNavCompat();
|
|
1152
|
+
${overlay}installClientErrors({ errorComponent: ${errorFile ? "__PNextRouteErrorComponent" : "undefined"}, globalErrorComponent: ${globalErrorFile ? "__PNextGlobalErrorComponent" : "undefined"}, notFoundComponent: ${notFoundFile ? "__PNextRouteNotFoundComponent" : "undefined"}, isActionError: ${actions ? "isActionError" : "undefined"} });`;
|
|
1153
|
+
}
|
|
1154
|
+
var CLIENT_RUNTIME_MODULE = "pnext-client-runtime";
|
|
1155
|
+
function clientRuntimeFacts(routes, nextCompat, suspense) {
|
|
1156
|
+
const references = routes.flatMap(({ route }) => route.clientReferences);
|
|
1157
|
+
return {
|
|
1158
|
+
nextCompat,
|
|
1159
|
+
suspense: nextCompat ? suspense ?? true : undefined,
|
|
1160
|
+
islands: references.length > 0,
|
|
1161
|
+
page: routes.some(({ route }) => route.client),
|
|
1162
|
+
shell: routes.some((entry) => entry.shell),
|
|
1163
|
+
deferredIslands: references.some((reference) => reference.dynamic?.ssr === false),
|
|
1164
|
+
visibleIslands: references.some((reference) => reference.dynamic?.load === "visible")
|
|
1165
|
+
};
|
|
1166
|
+
}
|
|
1167
|
+
function clientRuntimeSource(facts) {
|
|
1168
|
+
const { nextCompat, deferredIslands, page, shell } = facts;
|
|
1169
|
+
const suspense = nextCompat && facts.suspense !== false;
|
|
1170
|
+
const revival = Boolean(facts.islands || shell);
|
|
1171
|
+
return `
|
|
1172
|
+
import { ${revival ? "Component, " : ""}h, hydrate, render } from 'preact';
|
|
1173
|
+
${nextCompat ? "import { options as __pnextPreactOptions } from 'preact';" : ""}
|
|
1174
|
+
${deferredIslands ? "import { useEffect, useState } from 'preact/hooks';" : ""}
|
|
1175
|
+
${suspense ? "import { Suspense } from 'preact/compat';" : ""}
|
|
1176
|
+
${revival ? layoutSegmentHelperSource(nextCompat) : ""}
|
|
1177
|
+
${nextCompat && (revival || page) ? syncPropsHelperSource() : ""}
|
|
1178
|
+
${revival ? propsParserSource(nextCompat) : ""}
|
|
1179
|
+
${revival ? islandHelpersSource(facts) : ""}
|
|
1180
|
+
${islandCssHelperSource()}
|
|
1181
|
+
${revival ? hostMeasureSource() : ""}
|
|
1182
|
+
${mountLifecycleSource(nextCompat)}
|
|
1183
|
+
|
|
1184
|
+
export function bootstrapRoute(config) {
|
|
1185
|
+
const islands = config.islands ?? [];
|
|
1186
|
+
const Page = config.Page;
|
|
1187
|
+
const shell = config.shell;
|
|
1188
|
+
const pnextMountedRoots = new Set();
|
|
1189
|
+
let pnextIslandMounts;
|
|
1190
|
+
let pnextIslandScan;
|
|
1191
|
+
let pnextVisibleMount;
|
|
1192
|
+
${clientBoundaryHelperSource(nextCompat, suspense)}
|
|
1193
|
+
${mountSource()}
|
|
1194
|
+
${revival ? hydrateIslandsSource(facts) : ""}
|
|
1195
|
+
${shell ? hydrateClientShellSource(nextCompat) : ""}
|
|
1196
|
+
${page ? hydratePageSource(nextCompat) : ""}
|
|
1197
|
+
${routeLifecycleSource(facts)}
|
|
1198
|
+
}
|
|
1199
|
+
`;
|
|
1200
|
+
}
|
|
1201
|
+
function clientEntrySource(options) {
|
|
1202
|
+
const {
|
|
1203
|
+
pageFile,
|
|
1204
|
+
clientReferences,
|
|
1205
|
+
nextCompat,
|
|
1206
|
+
errorFile,
|
|
1207
|
+
notFoundFile,
|
|
1208
|
+
clientRootLayout,
|
|
1209
|
+
shellLayoutOrder
|
|
1210
|
+
} = options;
|
|
1211
|
+
const facts = {
|
|
1212
|
+
nextCompat,
|
|
1213
|
+
suspense: options.suspense,
|
|
1214
|
+
actions: options.actions,
|
|
1215
|
+
errorFile,
|
|
1216
|
+
globalErrorFile: options.globalErrorFile,
|
|
1217
|
+
notFoundFile,
|
|
1218
|
+
controlFlow: options.controlFlow,
|
|
1219
|
+
router: options.router || !pageFile && clientReferences.length === 0
|
|
1220
|
+
};
|
|
1221
|
+
if (!pageFile && clientReferences.length === 0) {
|
|
1222
|
+
return routerOnlyEntrySource(facts);
|
|
1223
|
+
}
|
|
1224
|
+
if (!pageFile && clientReferences.length > 0 && clientReferences.every(isVisibleDynamicReference)) {
|
|
1225
|
+
return visibleDynamicIslandEntrySource(clientReferences, facts, options.deferredDynamicHref);
|
|
1226
|
+
}
|
|
1227
|
+
const imports = clientReferences.map((reference, index) => {
|
|
1228
|
+
if (reference.dynamic)
|
|
1229
|
+
return null;
|
|
1230
|
+
if (reference.sideEffect)
|
|
1231
|
+
return `import ${JSON.stringify(reference.file)};`;
|
|
1232
|
+
return reference.exportName === "default" ? `import Island${index} from ${JSON.stringify(reference.file)};` : `import { ${reference.exportName} as Island${index} } from ${JSON.stringify(reference.file)};`;
|
|
1233
|
+
}).filter((item) => Boolean(item)).join(`
|
|
1234
|
+
`);
|
|
1235
|
+
const islandManifest = clientReferences.map((reference, index) => reference.sideEffect ? null : reference.dynamic ? `{ id: ${JSON.stringify(reference.id)}, options: ${JSON.stringify(reference.dynamic)}, ${dynamicLoader(reference, options.deferredDynamicHref?.(reference))} }` : `{ id: ${JSON.stringify(reference.id)}, Component: Island${index} }`).filter((item) => item !== null).join(`,
|
|
1236
|
+
`);
|
|
1237
|
+
const islandNames = new Map(clientReferences.map((reference, index) => [reference, `Island${index}`]));
|
|
1238
|
+
const shellReferences = shellClientReferences(clientReferences, errorFile, notFoundFile, shellLayoutOrder);
|
|
1239
|
+
const shellNames = !pageFile && clientRootLayout && shellReferences.some((reference) => !reference.dynamic) ? shellReferences.map((reference) => reference.dynamic ? undefined : islandNames.get(reference)).filter((name) => Boolean(name)) : undefined;
|
|
1240
|
+
const needsCss = clientReferences.some((reference) => referenceCssHref(reference));
|
|
1241
|
+
return `
|
|
1242
|
+
import { bootstrapRoute${needsCss ? ", loadIslandCss" : ""} } from ${JSON.stringify(CLIENT_RUNTIME_MODULE)};
|
|
1243
|
+
${pageFile ? `import Page from ${JSON.stringify(pageFile)};` : ""}
|
|
1244
|
+
${imports}
|
|
1245
|
+
${routerImportSource(facts)}
|
|
1246
|
+
${boundaryImportSource(nextCompat, hasErrorBoundary(facts), !notFoundFile)}
|
|
1247
|
+
|
|
1248
|
+
const { mountRoute, remountRoute, unmountRoute } = bootstrapRoute({
|
|
1249
|
+
islands: [
|
|
1250
|
+
${islandManifest}
|
|
1251
|
+
],${pageFile ? `
|
|
1252
|
+
Page,` : ""}${shellNames ? `
|
|
1253
|
+
shell: [${shellNames.join(", ")}],` : ""}${boundaryConfigSource(facts)}
|
|
1254
|
+
});
|
|
1255
|
+
export { mountRoute, remountRoute, unmountRoute };
|
|
1256
|
+
|
|
1257
|
+
${installRouterCall(facts.router)}${installCallsSource(facts)}
|
|
1258
|
+
if (${autoMountGuard(facts.router)}) void mountRoute();
|
|
1259
|
+
if (window.__PNEXT_REGISTER_ENTRY__) window.__PNEXT_REGISTER_ENTRY__(import.meta.url, { mountRoute, unmountRoute });
|
|
1260
|
+
`;
|
|
1261
|
+
}
|
|
1262
|
+
function boundaryConfigSource(facts) {
|
|
1263
|
+
if (!facts.nextCompat)
|
|
1264
|
+
return "";
|
|
1265
|
+
const base = `
|
|
1266
|
+
errorComponent: ${facts.errorFile ? "__PNextRouteErrorComponent" : "undefined"},
|
|
1267
|
+
notFoundComponent: ${facts.notFoundFile ? "__PNextRouteNotFoundComponent" : "undefined"},`;
|
|
1268
|
+
if (!hasErrorBoundary(facts))
|
|
1269
|
+
return `${base}
|
|
1270
|
+
errorBoundary: BareErrorBoundary,`;
|
|
1271
|
+
return `${base}
|
|
1272
|
+
errorBoundary: ClientErrorBoundary,
|
|
1273
|
+
onBoundaryReset: softRefreshRoute,${facts.notFoundFile ? "" : `
|
|
1274
|
+
builtInNotFound: __PNextBuiltInNotFound,`}`;
|
|
1275
|
+
}
|
|
1276
|
+
function shellClientReferences(clientReferences, errorFile, notFoundFile, shellLayoutOrder) {
|
|
1277
|
+
const shell = clientReferences.filter((reference) => !reference.sideEffect && reference.file !== errorFile && reference.file !== notFoundFile);
|
|
1278
|
+
if (!shellLayoutOrder?.length)
|
|
1279
|
+
return shell;
|
|
1280
|
+
const rank = new Map(shellLayoutOrder.map((file, index) => [file, index]));
|
|
1281
|
+
return shell.map((reference, index) => ({ reference, index })).sort((a, b) => {
|
|
1282
|
+
const aRank = rank.get(a.reference.file);
|
|
1283
|
+
const bRank = rank.get(b.reference.file);
|
|
1284
|
+
if (aRank !== undefined && bRank !== undefined)
|
|
1285
|
+
return aRank - bRank;
|
|
1286
|
+
if (aRank !== undefined)
|
|
1287
|
+
return -1;
|
|
1288
|
+
if (bRank !== undefined)
|
|
1289
|
+
return 1;
|
|
1290
|
+
return a.index - b.index;
|
|
1291
|
+
}).map((entry) => entry.reference);
|
|
1292
|
+
}
|
|
1293
|
+
function isVisibleDynamicReference(reference) {
|
|
1294
|
+
return reference.dynamic?.load === "visible";
|
|
1295
|
+
}
|
|
1296
|
+
function visibleDynamicIslandEntrySource(clientReferences, facts, deferredDynamicHref) {
|
|
1297
|
+
const { nextCompat, errorFile, notFoundFile } = facts;
|
|
1298
|
+
const boundary = hasErrorBoundary(facts);
|
|
1299
|
+
const islandManifest = clientReferences.map((reference) => `{ id: ${JSON.stringify(reference.id)}, ${visibleDynamicLoader(reference, deferredDynamicHref?.(reference))} }`).join(`,
|
|
1300
|
+
`);
|
|
1301
|
+
const compatBoundary = !nextCompat ? "" : boundary ? `function pnextClientBoundary(h, vnode) {
|
|
1302
|
+
return h(ClientErrorBoundary, { errorComponent: ${errorFile ? "__PNextRouteErrorComponent" : "undefined"}, notFoundComponent: ${notFoundFile ? "__PNextRouteNotFoundComponent" : "__PNextBuiltInNotFound"}, onReset: softRefreshRoute }, vnode);
|
|
1303
|
+
}
|
|
1304
|
+
` : `function pnextClientBoundary(h, vnode) {
|
|
1305
|
+
return h(BareErrorBoundary, null, vnode);
|
|
1306
|
+
}
|
|
1307
|
+
`;
|
|
1308
|
+
return `
|
|
1309
|
+
${nextCompat ? "import { options as __pnextPreactOptions } from 'preact';" : ""}
|
|
1310
|
+
${islandCssHelperSource()}
|
|
1311
|
+
${routerImportSource(facts)}
|
|
1312
|
+
${boundaryImportSource(nextCompat, boundary, boundary && !notFoundFile)}
|
|
1313
|
+
${layoutSegmentHelperSource(nextCompat)}
|
|
1314
|
+
${hostMeasureSource()}
|
|
1315
|
+
${mountLifecycleSource(nextCompat)}
|
|
1316
|
+
const islands = [
|
|
1317
|
+
${islandManifest}
|
|
1318
|
+
];
|
|
1319
|
+
|
|
1320
|
+
const pnextMountedRoots = new Set();
|
|
1321
|
+
let pnextIslandMounts;
|
|
1322
|
+
let pnextRender;
|
|
1323
|
+
|
|
1324
|
+
${nextCompat ? syncPropsHelperSource() : ""}
|
|
1325
|
+
${propsParserSource(nextCompat)}
|
|
1326
|
+
${compatBoundary}
|
|
1327
|
+
export function mountRoute() {
|
|
1328
|
+
for (const island of islands) {
|
|
1329
|
+
for (const root of document.querySelectorAll(\`[data-pnext-client="\${island.id}"]\`)) {
|
|
1330
|
+
if (isNestedIslandRoot(root)) continue;
|
|
1331
|
+
// A preserved root (router grafted its live DOM across the soft nav)
|
|
1332
|
+
// still shows the previous route's children \u2014 remount it immediately
|
|
1333
|
+
// instead of waiting for an intersection.
|
|
1334
|
+
if (root.__pnextIncoming) {
|
|
1335
|
+
void mountIsland(root, island);
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
observeVisibleIsland(root, island);
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
window.__PNEXT_ACTIVE_ENTRY__ = { unmount: unmountRoute };
|
|
1342
|
+
${nextCompat ? ` pnextGate(markRouteHydrated, pnextIslandMounts, undefined);
|
|
1343
|
+
` : ""} return pnextIslandMounts;
|
|
1344
|
+
}
|
|
1345
|
+
|
|
1346
|
+
export const remountRoute = mountRoute;
|
|
1347
|
+
window.__PNEXT_MOUNT_ISLANDS__ = remountRoute;
|
|
1348
|
+
|
|
1349
|
+
export function unmountRoute(keep) {
|
|
1350
|
+
// Roots in \`keep\` are grafted into the next document by the router (shared
|
|
1351
|
+
// layout preservation) \u2014 leave them and any mounted ancestor intact; an
|
|
1352
|
+
// ancestor unmount would recursively tear down the preserved island.
|
|
1353
|
+
if (pnextRender) for (const root of pnextMountedRoots) {
|
|
1354
|
+
if (keepsMountedRoot(root, keep)) continue;
|
|
1355
|
+
pnextRender(null, root);
|
|
1356
|
+
root.__pnextLive = undefined;
|
|
1357
|
+
}
|
|
1358
|
+
pnextMountedRoots.clear();
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
${installRouterCall(facts.router)}${installCallsSource(facts)}
|
|
1362
|
+
if (${autoMountGuard(facts.router)}) void mountRoute();
|
|
1363
|
+
if (window.__PNEXT_REGISTER_ENTRY__) window.__PNEXT_REGISTER_ENTRY__(import.meta.url, { mountRoute, unmountRoute });
|
|
1364
|
+
|
|
1365
|
+
function observeVisibleIsland(root, island) {
|
|
1366
|
+
if (typeof IntersectionObserver === 'undefined') {
|
|
1367
|
+
void mountIsland(root, island);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
const target = visibleTarget(root);
|
|
1372
|
+
const observer = new IntersectionObserver(entries => {
|
|
1373
|
+
if (!entries.some(entry => entry.isIntersecting)) return;
|
|
1374
|
+
observer.disconnect();
|
|
1375
|
+
void mountIsland(root, island);
|
|
1376
|
+
}, island.options);
|
|
1377
|
+
|
|
1378
|
+
observer.observe(target);
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
function visibleTarget(root) {
|
|
1382
|
+
// IntersectionObserver never fires for a display:contents target: observe the nearest box.
|
|
1383
|
+
let target = root;
|
|
1384
|
+
while (target.parentElement && getComputedStyle(target).display === 'contents') target = target.parentElement;
|
|
1385
|
+
return target;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
${mountOnceSource()}
|
|
1389
|
+
${nextCompat ? `let islandBoundary;
|
|
1390
|
+
|
|
1391
|
+
function mountIsland(root, island) {
|
|
1392
|
+
return mountIslandOnce(root, () => mountIslandTree(root, island));
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
async function mountIslandTree(root, island) {
|
|
1396
|
+
const [{ h, hydrate, render }${facts.suspense !== false ? ", { Suspense }" : ""}, Component] = await Promise.all([
|
|
1397
|
+
import('preact'),${facts.suspense !== false ? `
|
|
1398
|
+
import('preact/compat'),` : ""}
|
|
1399
|
+
islandComponent(island),
|
|
1400
|
+
]);
|
|
1401
|
+
${facts.suspense !== false ? ` islandBoundary = Suspense;
|
|
1402
|
+
` : ""} const rawProps = root.getAttribute('data-pnext-props') ?? '{}';
|
|
1403
|
+
const source = preservedSource(root, render);
|
|
1404
|
+
if (!source) return adoptPreserved(render, root);
|
|
1405
|
+
const vnode = islandVNode(h, Component, await islandProps(rawProps, source, node => domChildren(h, node), h), await staticChildren(h, source, island.id), root);
|
|
1406
|
+
const wrapped = ${facts.suspense !== false ? "h(Suspense, { fallback: null }, pnextClientBoundary(h, vnode))" : "pnextClientBoundary(h, vnode)"};
|
|
1407
|
+
if (source !== root) adoptPreserved(render, root, wrapped);
|
|
1408
|
+
else mount(hydrate, render, root, wrapped);
|
|
1409
|
+
}` : `function mountIsland(root, island) {
|
|
1410
|
+
return mountIslandOnce(root, () => mountIslandTree(root, island));
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
async function mountIslandTree(root, island) {
|
|
1414
|
+
const [{ h, hydrate, render }, Component] = await Promise.all([import('preact'), islandComponent(island)]);
|
|
1415
|
+
const rawProps = root.getAttribute('data-pnext-props') ?? '{}';
|
|
1416
|
+
const source = preservedSource(root, render);
|
|
1417
|
+
if (!source) return adoptPreserved(render, root);
|
|
1418
|
+
const vnode = islandVNode(h, Component, await islandProps(rawProps, source, node => domChildren(h, node), h), await staticChildren(h, source, island.id), root);
|
|
1419
|
+
if (source !== root) adoptPreserved(render, root, vnode);
|
|
1420
|
+
else mount(hydrate, render, root, vnode);
|
|
1421
|
+
}`}
|
|
1422
|
+
|
|
1423
|
+
// Dev route entries each bundle their own copy of a shared island component, and Preact
|
|
1424
|
+
// diffs by component IDENTITY: mounting the destination entry's copy would remount every
|
|
1425
|
+
// preserved island on navigation (a splash gate re-splashes, a reel restarts). The first
|
|
1426
|
+
// loaded instance of an island id wins for the document's lifetime; a dev recompile
|
|
1427
|
+
// full-reloads the page, which resets the registry. Prod ships one entry, so this is a
|
|
1428
|
+
// no-op there.
|
|
1429
|
+
// NOTE: pinning a single component instance across dev route entries is NOT safe here:
|
|
1430
|
+
// each dev entry carries its own module graph, and a pinned provider's context object
|
|
1431
|
+
// would not match the consuming entry's copy (breaks e.g. QueryClientProvider). Module
|
|
1432
|
+
// graphs must be deduped at the bundler level instead.
|
|
1433
|
+
async function islandComponent(island) {
|
|
1434
|
+
return island.Component ?? await island.load();
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
// A root preserved across a soft navigation carries the incoming placeholder
|
|
1438
|
+
// (fresh SSR children) stashed by the router; render children from it when this
|
|
1439
|
+
// preact instance owns the live tree. A foreign entry copy (dev bundles) can't
|
|
1440
|
+
// be diffed \u2014 restore the SSR children and mount fresh instead.
|
|
1441
|
+
function preservedSource(root, render) {
|
|
1442
|
+
const incoming = root.__pnextIncoming;
|
|
1443
|
+
root.__pnextIncoming = undefined;
|
|
1444
|
+
if (!incoming) return root;
|
|
1445
|
+
// A kept layout's island stashed as its own incoming has no new render: keep its tree.
|
|
1446
|
+
if (root.__pnextLive === render) return incoming === root ? null : incoming;
|
|
1447
|
+
root.replaceChildren(...incoming.childNodes);
|
|
1448
|
+
root.__pnextLive = undefined;
|
|
1449
|
+
root.__pnextMask = [incoming.getAttribute('data-pnext-id'), 0];
|
|
1450
|
+
return root;
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
// Re-render the preserved root in place so component state survives while the
|
|
1454
|
+
// routed content under the island updates.
|
|
1455
|
+
function adoptPreserved(render, root, vnode) {
|
|
1456
|
+
if (vnode) render(vnode, root);
|
|
1457
|
+
pnextRender = render;
|
|
1458
|
+
pnextMountedRoots.add(root);
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
function mount(hydrate, render, root, vnode) {
|
|
1462
|
+
// See mountSource(): an SSR-failed placeholder has no server DOM to adopt.
|
|
1463
|
+
const ssrFailed = root.getAttribute && root.getAttribute('data-pnext-ssr-failed') != null;
|
|
1464
|
+
const apply = root.hasChildNodes() && !ssrFailed ? hydrate : render;
|
|
1465
|
+
apply(vnode, root);
|
|
1466
|
+
// Stamp the root with this entry's render so the router can preserve it
|
|
1467
|
+
// across a soft navigation and the next entry can tell whether its own
|
|
1468
|
+
// preact instance owns the live tree.
|
|
1469
|
+
root.__pnextLive = render;
|
|
1470
|
+
pnextRender = render;
|
|
1471
|
+
pnextMountedRoots.add(root);
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
// Adopted DOM children win (element children); plain children travel inside
|
|
1475
|
+
// the serialized props instead, and an explicit undefined third argument would
|
|
1476
|
+
// clobber props.children in preact's createElement \u2014 so only pass it when set.
|
|
1477
|
+
function islandVNode(h, Component, props, adopted, root) {
|
|
1478
|
+
const vnode = adopted !== undefined ? h(Component, props, adopted) : h(Component, props);
|
|
1479
|
+
pnextIslandMask(vnode, root);
|
|
1480
|
+
return vnode;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
async function staticChildren(h, root, id) {
|
|
1484
|
+
const node = root.querySelector(\`[${ISLAND_STATIC_CHILDREN_ATTRIBUTE}="\${id}"]\`);
|
|
1485
|
+
if (!node) return undefined;
|
|
1486
|
+
// A fallback <template> sidecar (children the component did not render on
|
|
1487
|
+
// the server): materialize any island markers inside its inert content,
|
|
1488
|
+
// adopt, then drop the sidecar so the mount starts from a clean host.
|
|
1489
|
+
const template = node.localName === 'template';
|
|
1490
|
+
const source = template ? node.content : node;
|
|
1491
|
+
if (template && window.__PNEXT_MATERIALIZE_CLIENT_ISLANDS__) {
|
|
1492
|
+
window.__PNEXT_MATERIALIZE_CLIENT_ISLANDS__(source);
|
|
1493
|
+
}
|
|
1494
|
+
const children = await domChildren(h, source);
|
|
1495
|
+
if (template) node.remove();
|
|
1496
|
+
return h('pnext-static-children', {
|
|
1497
|
+
${JSON.stringify(ISLAND_STATIC_CHILDREN_ATTRIBUTE)}: id,
|
|
1498
|
+
style: { display: 'contents' },
|
|
1499
|
+
}, children);
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
async function domChildren(h, node) {
|
|
1503
|
+
const children = (await Promise.all(Array.from(node.childNodes, child => domNode(h, child))))
|
|
1504
|
+
.filter(child => child !== null && child !== undefined && child !== false);
|
|
1505
|
+
if (children.length === 0) return undefined;
|
|
1506
|
+
return children.length === 1 ? children[0] : children;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
async function domNode(h, node) {
|
|
1510
|
+
if (node.nodeType === 3) return node.textContent;
|
|
1511
|
+
if (node.nodeType !== 1) return null;
|
|
1512
|
+
|
|
1513
|
+
const element = node;
|
|
1514
|
+
if (element.localName === ${JSON.stringify(ISLAND_BOUNDARY_ERROR_ELEMENT)}) return pnextBoundaryErrorVNode(h, element);
|
|
1515
|
+
if (element.localName === 'pnext-client') {
|
|
1516
|
+
const id = element.getAttribute('data-pnext-client');
|
|
1517
|
+
const island = islands.find(candidate => candidate.id === id);
|
|
1518
|
+
if (!island) return h(element.localName, domProps(element), await domChildren(h, element));
|
|
1519
|
+
const Component = await islandComponent(island);
|
|
1520
|
+
const rawProps = element.getAttribute('data-pnext-props') ?? '{}';
|
|
1521
|
+
const props = await islandProps(rawProps, element, node => domChildren(h, node), h);
|
|
1522
|
+
// Adopted DOM is diffed positionally, and SSR-state-only siblings (a splash overlay the
|
|
1523
|
+
// server rendered that the live tree no longer shows) shift positions between renders.
|
|
1524
|
+
// A nested island must match its previous incarnation by IDENTITY, or the shift remounts
|
|
1525
|
+
// it and resets its state - Next never remounts layout components on navigation.
|
|
1526
|
+
if (props.key === undefined) props.key = 'pnext-island:' + id;
|
|
1527
|
+
const vnode = islandVNode(h, Component, props, await staticChildren(h, element, island.id), element);
|
|
1528
|
+
return ${nextCompat ? "islandBoundary ? h(islandBoundary, { fallback: null }, pnextClientBoundary(h, vnode)) : pnextClientBoundary(h, vnode)" : "vnode"};
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
return h(element.localName, domProps(element), await domChildren(h, element));
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
function pnextBoundaryErrorVNode(h, element) {
|
|
1535
|
+
const message = element.getAttribute(${JSON.stringify(ISLAND_BOUNDARY_ERROR_MESSAGE_ATTRIBUTE)}) ?? '';
|
|
1536
|
+
const digest = element.getAttribute(${JSON.stringify(ISLAND_BOUNDARY_ERROR_DIGEST_ATTRIBUTE)});
|
|
1537
|
+
return h(function PnextBoundaryError() {
|
|
1538
|
+
const error = new Error(message);
|
|
1539
|
+
if (digest) error.digest = digest;
|
|
1540
|
+
throw error;
|
|
1541
|
+
}, null);
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
function domProps(element) {
|
|
1545
|
+
const props = {};
|
|
1546
|
+
for (const attribute of element.attributes) props[attribute.name] = attribute.value;
|
|
1547
|
+
return props;
|
|
1548
|
+
}
|
|
1549
|
+
|
|
1550
|
+
// A nested root belongs to its parent island's DOM adoption pass, never to a
|
|
1551
|
+
// mount of its own. This entry carries no streamed-hole seam (see the shared
|
|
1552
|
+
// runtime): its whole point is a preact-free first paint, and a host grafted in
|
|
1553
|
+
// after a visible island mounted would be dead weight in every bundle.
|
|
1554
|
+
function isNestedIslandRoot(root) {
|
|
1555
|
+
return root.parentElement?.closest('pnext-client') != null;
|
|
1556
|
+
}
|
|
1557
|
+
`;
|
|
1558
|
+
}
|
|
1559
|
+
function visibleDynamicLoader(reference, href) {
|
|
1560
|
+
return `options: ${JSON.stringify(reference.dynamic ?? {})}, ${dynamicLoader(reference, href)}`;
|
|
1561
|
+
}
|
|
1562
|
+
function dynamicLoader(reference, href) {
|
|
1563
|
+
const imported = reference.exportName === "default" ? "module.default" : `module.${reference.exportName}`;
|
|
1564
|
+
const importExpression = `import(${JSON.stringify(href ?? reference.file)}).then(module => ${imported})`;
|
|
1565
|
+
const cssHref = referenceCssHref(reference);
|
|
1566
|
+
return cssHref ? `load: () => Promise.all([${importExpression}, loadIslandCss(${JSON.stringify(cssHref)})]).then(([module]) => module)` : `load: () => ${importExpression}`;
|
|
1567
|
+
}
|
|
1568
|
+
function referenceCssHref(reference) {
|
|
1569
|
+
return reference.cssImports?.length ? `/assets/${reference.id}.css` : undefined;
|
|
1570
|
+
}
|
|
1571
|
+
function islandCssHelperSource() {
|
|
1572
|
+
return `
|
|
1573
|
+
export function loadIslandCss(href) {
|
|
1574
|
+
if (document.querySelector('link[href="' + href + '"]')) return Promise.resolve();
|
|
1575
|
+
return new Promise(resolve => {
|
|
1576
|
+
const link = document.createElement('link');
|
|
1577
|
+
link.rel = 'stylesheet';
|
|
1578
|
+
link.href = href;
|
|
1579
|
+
link.onload = () => resolve();
|
|
1580
|
+
link.onerror = () => resolve();
|
|
1581
|
+
document.head.append(link);
|
|
1582
|
+
});
|
|
1583
|
+
}
|
|
1584
|
+
`;
|
|
1585
|
+
}
|
|
1586
|
+
function mountSource() {
|
|
1587
|
+
return `
|
|
1588
|
+
function mount(root, vnode) {
|
|
1589
|
+
// An island whose SSR stringification threw was served as an empty
|
|
1590
|
+
// pnext-client placeholder (data-pnext-ssr-failed) with no server DOM to adopt;
|
|
1591
|
+
// render it fresh rather than hydrating against nothing.
|
|
1592
|
+
const ssrFailed = root.getAttribute && root.getAttribute('data-pnext-ssr-failed') != null;
|
|
1593
|
+
const apply = root.hasChildNodes() && !ssrFailed ? hydrate : render;
|
|
1594
|
+
apply(vnode, root);
|
|
1595
|
+
// Stamp the root with this entry's render so the router can preserve it
|
|
1596
|
+
// across a soft navigation and the next entry can tell whether its own
|
|
1597
|
+
// preact instance owns the live tree (dev bundles carry separate copies).
|
|
1598
|
+
root.__pnextLive = render;
|
|
1599
|
+
pnextMountedRoots.add(root);
|
|
1600
|
+
}
|
|
1601
|
+
`;
|
|
1602
|
+
}
|
|
1603
|
+
function hydratePageSource(nextCompat) {
|
|
1604
|
+
return `
|
|
1605
|
+
async function hydratePageAfterIslands() {
|
|
1606
|
+
await pnextIslandMounts;
|
|
1607
|
+
const root = document.getElementById('pnext-page');
|
|
1608
|
+
if (root?.closest('pnext-client')) return;
|
|
1609
|
+
hydratePage();
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
function pageProps() {
|
|
1613
|
+
const rawProps = window.__PNEXT_PROPS__ ?? {};
|
|
1614
|
+
// Pre-fulfilled promises (status/value readable synchronously): use() must
|
|
1615
|
+
// not suspend during page hydration or preact appends fragment siblings
|
|
1616
|
+
// instead of reusing the server DOM.
|
|
1617
|
+
// Under compat.next.legacyRequestAPIs the server also gave these promises the resolved object's
|
|
1618
|
+
// own keys (withSyncProps), so Next-14 code reads \`params.creator\` without await; rebuild that
|
|
1619
|
+
// same surface here or the hydrated page loses every param the SSR render had.
|
|
1620
|
+
${nextCompat ? "const legacy = __pnextSyncProps();" : ""}
|
|
1621
|
+
const fulfilled = (kind, value) => {
|
|
1622
|
+
const promise = Promise.resolve(value);
|
|
1623
|
+
promise.status = 'fulfilled';
|
|
1624
|
+
promise.value = value;
|
|
1625
|
+
return ${nextCompat ? "legacy ? legacy(promise, kind, value) : promise" : "promise"};
|
|
1626
|
+
};
|
|
1627
|
+
return {
|
|
1628
|
+
...rawProps,
|
|
1629
|
+
params: fulfilled('params', rawProps.params ?? {}),
|
|
1630
|
+
searchParams: fulfilled(
|
|
1631
|
+
'searchParams',
|
|
1632
|
+
rawProps.searchParams ?? Object.fromEntries(new URLSearchParams(location.search)),
|
|
1633
|
+
),
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
function hydratePage() {
|
|
1638
|
+
// SSR-error recovery: when the server errored and served the error-boundary
|
|
1639
|
+
// document, hydrating the real Page against that error HTML corrupts the tree.
|
|
1640
|
+
// The server stamps data-pnext-ssr-error on <html>; skip page hydration and let
|
|
1641
|
+
// the client error runtime (installClientErrors) own the tree instead.
|
|
1642
|
+
if (document.documentElement.hasAttribute('data-pnext-ssr-error')) return;
|
|
1643
|
+
// A refresh of this same route grafts the live page DOM back in (the router's
|
|
1644
|
+
// client-page preservation), so its tree is still mounted \u2014 re-hydrating over
|
|
1645
|
+
// it would blow away the state the graft exists to keep.
|
|
1646
|
+
const live = window.__PNEXT_CLIENT_PAGE_ROOT__;
|
|
1647
|
+
if (live && pnextMountedRoots.has(live)) return;
|
|
1648
|
+
const root = pageMountRoot();
|
|
1649
|
+
if (!root) return;
|
|
1650
|
+
// Published for the router: it needs the mount container (and, for a dissolved
|
|
1651
|
+
// page slot, its anchors) to preserve this page across a refresh.
|
|
1652
|
+
window.__PNEXT_CLIENT_PAGE_ROOT__ = root;
|
|
1653
|
+
mount(root, ${wrapInBoundary("h(Page, pageProps())", nextCompat)});
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1656
|
+
// Next renders a page's children directly into the layout with no wrapper element, so tests assert
|
|
1657
|
+
// structural selectors against the live DOM. The island bootstrap rebuilt div#pnext-page from the SSR
|
|
1658
|
+
// comment markers; a client page mounted here owns that wrapper exclusively, so dissolve it back into
|
|
1659
|
+
// comment anchors and hydrate against a virtual container whose children are the marker range.
|
|
1660
|
+
// Nested/segment contexts keep the real element: island adoption walks it and segment grafting
|
|
1661
|
+
// queries #pnext-page.
|
|
1662
|
+
function pageMountRoot() {
|
|
1663
|
+
const root = document.getElementById('pnext-page');
|
|
1664
|
+
if (!root || !root.parentNode) return root;
|
|
1665
|
+
if (root.closest('pnext-client') || root.closest('pnext-layout[data-pnext-segment]')) return root;
|
|
1666
|
+
const parent = root.parentNode;
|
|
1667
|
+
let attrs = '';
|
|
1668
|
+
for (const attribute of root.attributes) attrs += ' ' + attribute.name + '="' + attribute.value + '"';
|
|
1669
|
+
// Same encoding the island materializers parse (entity-escaped attribute
|
|
1670
|
+
// text, see renderer encodeIslandMarker) \u2014 a bfcache/entry snapshot of this
|
|
1671
|
+
// dissolved marker must re-materialize into a real #pnext-page wrapper.
|
|
1672
|
+
const encoded = attrs.replace(/</g, '<').replace(/>/g, '>');
|
|
1673
|
+
const open = document.createComment('pnext-page:' + encoded);
|
|
1674
|
+
const close = document.createComment('/pnext-page');
|
|
1675
|
+
parent.insertBefore(open, root);
|
|
1676
|
+
while (root.firstChild) parent.insertBefore(root.firstChild, root);
|
|
1677
|
+
parent.replaceChild(close, root);
|
|
1678
|
+
const container = pageMarkerContainer(open, close, parent);
|
|
1679
|
+
// The router preserves this range across a refresh; it needs the anchors.
|
|
1680
|
+
container.__pnextAnchors = [open, close];
|
|
1681
|
+
return container;
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// The minimal parent-DOM surface preact's diff touches on a render container
|
|
1685
|
+
// (namespaceURI/firstChild/childNodes/insertBefore), scoped to the nodes
|
|
1686
|
+
// between the two page anchors; child removal goes through each node's own
|
|
1687
|
+
// parentNode. contains/closest serve the entry's own keep/nesting checks.
|
|
1688
|
+
function pageMarkerContainer(open, close, parent) {
|
|
1689
|
+
return {
|
|
1690
|
+
nodeType: 1,
|
|
1691
|
+
namespaceURI: parent.namespaceURI,
|
|
1692
|
+
get firstChild() {
|
|
1693
|
+
const node = open.nextSibling;
|
|
1694
|
+
return node === close ? null : node;
|
|
1695
|
+
},
|
|
1696
|
+
get childNodes() {
|
|
1697
|
+
const nodes = [];
|
|
1698
|
+
for (let node = open.nextSibling; node && node !== close; node = node.nextSibling) nodes.push(node);
|
|
1699
|
+
return nodes;
|
|
1700
|
+
},
|
|
1701
|
+
hasChildNodes() { return open.nextSibling !== close; },
|
|
1702
|
+
insertBefore(node, reference) { return parent.insertBefore(node, reference ?? close); },
|
|
1703
|
+
appendChild(node) { return parent.insertBefore(node, close); },
|
|
1704
|
+
removeChild(node) { return parent.removeChild(node); },
|
|
1705
|
+
contains(node) {
|
|
1706
|
+
for (let current = open.nextSibling; current && current !== close; current = current.nextSibling) {
|
|
1707
|
+
if (current === node || (current.contains && current.contains(node))) return true;
|
|
1708
|
+
}
|
|
1709
|
+
return false;
|
|
1710
|
+
},
|
|
1711
|
+
closest(selector) { return parent.closest ? parent.closest(selector) : null; },
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
`;
|
|
1715
|
+
}
|
|
1716
|
+
function wrapInBoundary(vnodeExpression, nextCompat) {
|
|
1717
|
+
return nextCompat ? `pnextBoundary(${vnodeExpression})` : vnodeExpression;
|
|
1718
|
+
}
|
|
1719
|
+
function clientBoundaryHelperSource(nextCompat, suspense) {
|
|
1720
|
+
if (!nextCompat)
|
|
1721
|
+
return "";
|
|
1722
|
+
const boundary = `h(config.errorBoundary, {
|
|
1723
|
+
errorComponent: config.errorComponent,
|
|
1724
|
+
notFoundComponent: config.notFoundComponent,
|
|
1725
|
+
builtInNotFound: config.builtInNotFound,
|
|
1726
|
+
onReset: config.onBoundaryReset,
|
|
1727
|
+
}, vnode)`;
|
|
1728
|
+
return `
|
|
1729
|
+
function pnextBoundary(vnode) {
|
|
1730
|
+
return ${suspense ? `h(Suspense, { fallback: null }, ${boundary})` : boundary};
|
|
1731
|
+
}
|
|
1732
|
+
`;
|
|
1733
|
+
}
|
|
1734
|
+
function hydrateIslandsSource({
|
|
1735
|
+
nextCompat,
|
|
1736
|
+
visibleIslands,
|
|
1737
|
+
deferredIslands,
|
|
1738
|
+
dev
|
|
1739
|
+
}) {
|
|
1740
|
+
const diagnostic = dev ? `
|
|
1741
|
+
function diagnoseSkippedIslands() {
|
|
1742
|
+
for (const root of document.querySelectorAll('pnext-client[data-pnext-client]')) {
|
|
1743
|
+
if (root.__pnextLive) continue;
|
|
1744
|
+
const id = root.getAttribute('data-pnext-client');
|
|
1745
|
+
const island = id ? islandById.get(id) : undefined;
|
|
1746
|
+
const parent = nestedIslandParent(root);
|
|
1747
|
+
// A visible island is deliberately inert until intersection. A nested
|
|
1748
|
+
// island whose parent is still pending is likewise owned by that parent's
|
|
1749
|
+
// adoption pass, one parked in a capture holder is mid-adoption, and one the
|
|
1750
|
+
// claim pass adopted is no longer in the document. Everything else is a host
|
|
1751
|
+
// this route can never hydrate.
|
|
1752
|
+
if (island?.options?.load === 'visible') continue;
|
|
1753
|
+
if (root.closest('pnext-hole-capture, [data-pnext-stream]')) continue;
|
|
1754
|
+
if (parent && !parent.__pnextLive) continue;
|
|
1755
|
+
const reason = island
|
|
1756
|
+
? 'was materialized after its parent island mounted, outside any streamed hole this route can re-adopt'
|
|
1757
|
+
: 'has no entry in the client island manifest';
|
|
1758
|
+
console.warn('pnext: client island ' + (id ?? '<missing id>') + ' ' + reason);
|
|
1759
|
+
}
|
|
1760
|
+
}
|
|
1761
|
+
` : "";
|
|
1762
|
+
return `
|
|
1763
|
+
const islandById = new Map(islands.map(island => [island.id, island]));
|
|
1764
|
+
${lateIslandClaimSource()}${diagnostic}
|
|
1765
|
+
|
|
1766
|
+
// The mounted count is known as soon as the scan loop finishes, before any
|
|
1767
|
+
// island module resolves \u2014 so the client shell can decide whether it owns the
|
|
1768
|
+
// document without waiting behind island dynamic imports.
|
|
1769
|
+
function scanIslands() {
|
|
1770
|
+
const pending = [];
|
|
1771
|
+
let mounted = 0;${nextCompat ? `
|
|
1772
|
+
for (const island of islands) {
|
|
1773
|
+
if (island.Component) window.__PNEXT_ACTIONS__?.registerClientReference(island.id, island.Component);
|
|
1774
|
+
}` : ""}
|
|
1775
|
+
for (const island of islands) {
|
|
1776
|
+
for (const root of document.querySelectorAll(\`[data-pnext-client="\${island.id}"]\`)) {
|
|
1777
|
+
// React hydrates a streamed boundary only once revealed; its promotion re-runs this scan.
|
|
1778
|
+
if (root.closest('[data-pnext-stream]')) continue;
|
|
1779
|
+
const parent = nestedIslandParent(root);
|
|
1780
|
+
if (parent) {
|
|
1781
|
+
pending.push(claimNestedIslandRoot(root, parent));
|
|
1782
|
+
continue;
|
|
1783
|
+
}
|
|
1784
|
+
// Already mounted (e.g. a re-scan after a streamed Suspense hole materialized new islands):
|
|
1785
|
+
// mount() stamps __pnextLive, so skipping these leaves the shell's live islands untouched. A root
|
|
1786
|
+
// preserved across a soft navigation is ALSO live, but carries the incoming placeholder (fresh SSR
|
|
1787
|
+
// props/children) the router stashed - it must re-render, not be skipped, or it keeps showing the
|
|
1788
|
+
// previous document's props.
|
|
1789
|
+
if (root.__pnextLive && !root.__pnextIncoming) continue;
|
|
1790
|
+
mounted += 1;${visibleIslands ? `
|
|
1791
|
+
// A preserved root (router grafted its live DOM across the soft nav) must
|
|
1792
|
+
// re-render immediately \u2014 its DOM still shows the previous route's children.
|
|
1793
|
+
if (island.load && island.options?.load === 'visible' && !root.__pnextIncoming) {
|
|
1794
|
+
observeVisibleIsland(root, island);
|
|
1795
|
+
continue;
|
|
1796
|
+
}` : ""}
|
|
1797
|
+
pending.push(mountIsland(root, island));
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
return { mounted, done: Promise.all(pending) };
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1803
|
+
function mountIsland(root, island) {
|
|
1804
|
+
return mountIslandOnce(root, () => mountIslandTree(root, island));
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
// Dev route entries each bundle their own copy of a shared island component; Preact diffs
|
|
1808
|
+
// by identity, so mounting another entry's copy would remount every preserved island on
|
|
1809
|
+
// navigation. First loaded instance per island id wins; a dev recompile full-reloads.
|
|
1810
|
+
// NOTE: pinning a single component instance across dev route entries is NOT safe here:
|
|
1811
|
+
// each dev entry carries its own module graph, and a pinned provider's context object
|
|
1812
|
+
// would not match the consuming entry's copy (breaks e.g. QueryClientProvider). Module
|
|
1813
|
+
// graphs must be deduped at the bundler level instead.
|
|
1814
|
+
async function islandComponent(island) {
|
|
1815
|
+
return island.Component ?? await island.load();
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
async function mountIslandTree(root, island) {
|
|
1819
|
+
const Component = await islandComponent(island);${nextCompat ? `
|
|
1820
|
+
window.__PNEXT_ACTIONS__?.registerClientReference(island.id, Component);` : ""}
|
|
1821
|
+
const rawProps = root.getAttribute('data-pnext-props') ?? '{}';
|
|
1822
|
+
const incoming = root.__pnextIncoming;
|
|
1823
|
+
root.__pnextIncoming = undefined;
|
|
1824
|
+
// A streamed chunk promoted INSIDE this root's adopted children (the hard-loaded document
|
|
1825
|
+
// finishes streaming long after hydration) replaced DOM this preact tree owns without
|
|
1826
|
+
// telling it. Its vdom still names the fallback, so a diff would build the incoming
|
|
1827
|
+
// children BESIDE the promoted ones. Rebuild from the incoming DOM instead.
|
|
1828
|
+
const streamGrafted = root.__pnextStreamGrafted;
|
|
1829
|
+
root.__pnextStreamGrafted = undefined;
|
|
1830
|
+
if (incoming && root.__pnextLive === render && !streamGrafted) {
|
|
1831
|
+
// Preserved across a soft navigation: re-render in place with the incoming
|
|
1832
|
+
// document's props/children so component state survives while the routed
|
|
1833
|
+
// content under the island updates.
|
|
1834
|
+
// A kept layout's island (its own incoming) keeps its tree.
|
|
1835
|
+
if (incoming !== root) {
|
|
1836
|
+
const vnode = ${islandVNodeExpr("Component", "await islandProps(rawProps, incoming, domChildren, h)", "await staticChildren(incoming, island.id)", "root")};
|
|
1837
|
+
render(${wrapInBoundary("vnode", nextCompat)}, root);
|
|
1838
|
+
}
|
|
1839
|
+
pnextMountedRoots.add(root);
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
if (incoming) {
|
|
1843
|
+
// Preserved root owned by a foreign entry copy (dev bundles): this preact
|
|
1844
|
+
// instance can't diff its tree \u2014 restore the SSR children and mount fresh.
|
|
1845
|
+
root.replaceChildren(...incoming.childNodes);
|
|
1846
|
+
root.__pnextLive = undefined;
|
|
1847
|
+
root.__pnextMask = [incoming.getAttribute('data-pnext-id'), 0];
|
|
1848
|
+
}
|
|
1849
|
+
const vnode = ${islandVNodeExpr("Component", "await islandProps(rawProps, root, domChildren, h)", "await staticChildren(root, island.id)", "root")};
|
|
1850
|
+
mount(root, ${wrapInBoundary("vnode", nextCompat)});
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
${visibleIslands ? `function observeVisibleIsland(root, island) {
|
|
1854
|
+
if (typeof IntersectionObserver === 'undefined') {
|
|
1855
|
+
void mountIsland(root, island);
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
|
|
1859
|
+
const observer = new IntersectionObserver(entries => {
|
|
1860
|
+
if (!entries.some(entry => entry.isIntersecting)) return;
|
|
1861
|
+
observer.disconnect();
|
|
1862
|
+
void mountIsland(root, island);
|
|
1863
|
+
}, island.options);
|
|
1864
|
+
|
|
1865
|
+
observer.observe(visibleTarget(root));
|
|
1866
|
+
}
|
|
1867
|
+
` : ""}
|
|
1868
|
+
async function staticChildren(root, id) {
|
|
1869
|
+
const node = root.querySelector(\`[${ISLAND_STATIC_CHILDREN_ATTRIBUTE}="\${id}"]\`);
|
|
1870
|
+
if (!node) return undefined;
|
|
1871
|
+
// A fallback <template> sidecar (children the component did not render on
|
|
1872
|
+
// the server): materialize any island markers inside its inert content,
|
|
1873
|
+
// adopt, then drop the sidecar so the mount starts from a clean host.
|
|
1874
|
+
const template = node.localName === 'template';
|
|
1875
|
+
const source = template ? node.content : node;
|
|
1876
|
+
if (template && window.__PNEXT_MATERIALIZE_CLIENT_ISLANDS__) {
|
|
1877
|
+
window.__PNEXT_MATERIALIZE_CLIENT_ISLANDS__(source);
|
|
1878
|
+
}
|
|
1879
|
+
const children = await domChildren(source);
|
|
1880
|
+
if (Page) {
|
|
1881
|
+
const pageRoot = source.querySelector?.('#pnext-page');
|
|
1882
|
+
if (pageRoot?.parentNode) pageRoot.replaceWith(...pageRoot.childNodes);
|
|
1883
|
+
}
|
|
1884
|
+
if (template) node.remove();
|
|
1885
|
+
return h('pnext-static-children', {
|
|
1886
|
+
${JSON.stringify(ISLAND_STATIC_CHILDREN_ATTRIBUTE)}: id,
|
|
1887
|
+
style: { display: 'contents' },
|
|
1888
|
+
}, children);
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
async function domChildren(node) {
|
|
1892
|
+
const children = (await Promise.all(Array.from(node.childNodes, domNode)))
|
|
1893
|
+
.filter(child => child !== null && child !== undefined && child !== false);
|
|
1894
|
+
if (children.length === 0) return undefined;
|
|
1895
|
+
return children.length === 1 ? children[0] : children;
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
async function domNode(node) {
|
|
1899
|
+
if (node.nodeType === 3) return node.textContent;
|
|
1900
|
+
if (node.nodeType !== 1) return null;
|
|
1901
|
+
|
|
1902
|
+
const element = node;
|
|
1903
|
+
if (element.localName === ${JSON.stringify(ISLAND_BOUNDARY_ERROR_ELEMENT)}) return pnextBoundaryErrorVNode(element);
|
|
1904
|
+
// The seam a late streamed graft re-adopts through (see holeSeamSource).
|
|
1905
|
+
if (element.localName === 'pnext-hole') {
|
|
1906
|
+
return h(PnextHole, { attrs: domProps(element) }, await domChildren(element));
|
|
1907
|
+
}
|
|
1908
|
+
if (element.localName === 'pnext-client') {
|
|
1909
|
+
const id = element.getAttribute('data-pnext-client');
|
|
1910
|
+
const island = id ? islandById.get(id) : undefined;
|
|
1911
|
+
if (!island) return h(element.localName, domProps(element), await domChildren(element));
|
|
1912
|
+
const rawProps = element.getAttribute('data-pnext-props') ?? '{}';
|
|
1913
|
+
const children = await staticChildren(element, island.id);${deferredIslands ? `
|
|
1914
|
+
if (!island.Component && island.options?.ssr === false) {
|
|
1915
|
+
return h(DeferredIsland, { load: island.load, props: parseIslandProps(rawProps), children });
|
|
1916
|
+
}` : ""}
|
|
1917
|
+
const Component = await islandComponent(island);
|
|
1918
|
+
return ${wrapInBoundary(islandVNodeExpr("Component", "await islandProps(rawProps, element, domChildren, h)", "children", "element"), nextCompat)};
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
if (Page && element.id === 'pnext-page') {
|
|
1922
|
+
return ${wrapInBoundary("h(Page, pageProps())", nextCompat)};
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
return h(element.localName, domProps(element), await domChildren(element));
|
|
1926
|
+
}
|
|
1927
|
+
`;
|
|
1928
|
+
}
|
|
1929
|
+
function islandHelpersSource({ deferredIslands, visibleIslands }) {
|
|
1930
|
+
return `
|
|
1931
|
+
function pnextBoundaryErrorVNode(element) {
|
|
1932
|
+
const message = element.getAttribute(${JSON.stringify(ISLAND_BOUNDARY_ERROR_MESSAGE_ATTRIBUTE)}) ?? '';
|
|
1933
|
+
const digest = element.getAttribute(${JSON.stringify(ISLAND_BOUNDARY_ERROR_DIGEST_ATTRIBUTE)});
|
|
1934
|
+
return h(function PnextBoundaryError() {
|
|
1935
|
+
const error = new Error(message);
|
|
1936
|
+
if (digest) error.digest = digest;
|
|
1937
|
+
throw error;
|
|
1938
|
+
}, null);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
function domProps(element) {
|
|
1942
|
+
const props = {};
|
|
1943
|
+
for (const attribute of element.attributes) props[attribute.name] = attribute.value;
|
|
1944
|
+
return props;
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// The island host that owns this root's DOM, if any. A nested root is never
|
|
1948
|
+
// mounted here: the parent adopts it, or claimNestedIslandRoot routes it through
|
|
1949
|
+
// the parent's hole seam.
|
|
1950
|
+
function nestedIslandParent(root) {
|
|
1951
|
+
return root.parentElement?.closest('pnext-client');
|
|
1952
|
+
}
|
|
1953
|
+
${holeSeamSource()}${visibleIslands ? `
|
|
1954
|
+
function visibleTarget(root) {
|
|
1955
|
+
// IntersectionObserver never fires for a display:contents target: observe the nearest box.
|
|
1956
|
+
let target = root;
|
|
1957
|
+
while (target.parentElement && getComputedStyle(target).display === 'contents') target = target.parentElement;
|
|
1958
|
+
return target;
|
|
1959
|
+
}
|
|
1960
|
+
` : ""}${mountOnceSource()}${deferredIslands ? `
|
|
1961
|
+
function DeferredIsland({ load, props, children }) {
|
|
1962
|
+
const [Component, setComponent] = useState(null);
|
|
1963
|
+
useEffect(() => {
|
|
1964
|
+
let cancelled = false;
|
|
1965
|
+
void load().then(loaded => {
|
|
1966
|
+
if (!cancelled) setComponent(() => loaded);
|
|
1967
|
+
});
|
|
1968
|
+
return () => {
|
|
1969
|
+
cancelled = true;
|
|
1970
|
+
};
|
|
1971
|
+
}, [load]);
|
|
1972
|
+
return Component ? h(Component, props, children) : null;
|
|
1973
|
+
}
|
|
1974
|
+
` : ""}`;
|
|
1975
|
+
}
|
|
1976
|
+
function hydrateClientShellSource(nextCompat) {
|
|
1977
|
+
const shell = `shell.reduceRight(
|
|
1978
|
+
(children, Component, index) => h(Component, index === 0 ? slotProps : null, children),
|
|
1979
|
+
page,
|
|
1980
|
+
)`;
|
|
1981
|
+
return `
|
|
1982
|
+
// A client root layout is re-rendered whole here, but its parallel-route slot
|
|
1983
|
+
// props (nav/auth/\u2026) are server-rendered subtrees that never serialized. Adopt
|
|
1984
|
+
// each slot's preserved SSR DOM (marked with data-pnext-slot by the renderer)
|
|
1985
|
+
// as the named prop on the outermost shell component so the slots survive.
|
|
1986
|
+
async function collectRootSlotProps() {
|
|
1987
|
+
const props = {};
|
|
1988
|
+
for (const el of document.querySelectorAll('[data-pnext-slot]')) {
|
|
1989
|
+
if (el.parentElement?.closest('[data-pnext-slot]')) continue;
|
|
1990
|
+
const name = el.getAttribute('data-pnext-slot');
|
|
1991
|
+
if (!name) continue;
|
|
1992
|
+
props[name] = await domNode(el);
|
|
1993
|
+
}
|
|
1994
|
+
return props;
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
async function hydrateClientShell() {
|
|
1998
|
+
if (pnextIslandScan && pnextIslandScan.mounted > 0) return;
|
|
1999
|
+
// The streamed-chunk remount (remountRoute) re-runs every mount step; the
|
|
2000
|
+
// shell owns document.body, so re-rendering a shell that already hydrated
|
|
2001
|
+
// would blow away the live tree. mount() registers its root, and a soft
|
|
2002
|
+
// navigation clears the set, so this only skips a shell that is still live.
|
|
2003
|
+
if (pnextMountedRoots.has(document.body)) return;
|
|
2004
|
+
const pageRoot = document.getElementById('pnext-page');
|
|
2005
|
+
if (!pageRoot) return;
|
|
2006
|
+
const page = await domNode(pageRoot);
|
|
2007
|
+
const slotProps = await collectRootSlotProps();
|
|
2008
|
+
mount(document.body, ${wrapInBoundary(shell, nextCompat)});
|
|
2009
|
+
}
|
|
2010
|
+
`;
|
|
2011
|
+
}
|
|
2012
|
+
|
|
2013
|
+
// src/client/react-tier.ts
|
|
2014
|
+
import { existsSync as existsSync2, statSync } from "fs";
|
|
2015
|
+
import path3 from "path";
|
|
2016
|
+
var LITE_REACT_NAMES = new Set([
|
|
2017
|
+
"Component",
|
|
2018
|
+
"Fragment",
|
|
2019
|
+
"StrictMode",
|
|
2020
|
+
"cache",
|
|
2021
|
+
"cacheSignal",
|
|
2022
|
+
"cloneElement",
|
|
2023
|
+
"createContext",
|
|
2024
|
+
"createElement",
|
|
2025
|
+
"createRef",
|
|
2026
|
+
"isValidElement",
|
|
2027
|
+
"startTransition",
|
|
2028
|
+
"useActionState",
|
|
2029
|
+
"useCallback",
|
|
2030
|
+
"useContext",
|
|
2031
|
+
"useDebugValue",
|
|
2032
|
+
"useDeferredValue",
|
|
2033
|
+
"useEffect",
|
|
2034
|
+
"useId",
|
|
2035
|
+
"useImperativeHandle",
|
|
2036
|
+
"useInsertionEffect",
|
|
2037
|
+
"useLayoutEffect",
|
|
2038
|
+
"useMemo",
|
|
2039
|
+
"useOptimistic",
|
|
2040
|
+
"useReducer",
|
|
2041
|
+
"useRef",
|
|
2042
|
+
"useState",
|
|
2043
|
+
"useTransition",
|
|
2044
|
+
"version",
|
|
2045
|
+
"ViewTransition",
|
|
2046
|
+
"addTransitionType"
|
|
2047
|
+
]);
|
|
2048
|
+
var SAFE_BARE_SPECIFIERS = new Set([
|
|
2049
|
+
"next/link",
|
|
2050
|
+
"next/navigation",
|
|
2051
|
+
"preact",
|
|
2052
|
+
"preact/hooks",
|
|
2053
|
+
"preact/jsx-runtime",
|
|
2054
|
+
"react/jsx-runtime",
|
|
2055
|
+
"react/jsx-dev-runtime"
|
|
2056
|
+
]);
|
|
2057
|
+
var UNSAFE_TOKENS = /\bonChange\b|\bonDoubleClick\b|\bdefaultProps\b|\bgetSnapshotBeforeUpdate\b|\bcomponentWillReceiveProps\b|\bUNSAFE_\w+|\bref\s*=\s*["']|async\s+function\s+[A-Z]|(?:const|let|var)\s+[A-Z][\w$]*\s*=\s*async\b|export\s+default\s+async\b/;
|
|
2058
|
+
function unsafeDefaultValue(source) {
|
|
2059
|
+
return source.includes("defaultValue") && source.includes("<select");
|
|
2060
|
+
}
|
|
2061
|
+
var STATIC_IMPORT = /(?:^|\n)\s*(?:import|export)\s+([^;'"]*?)\s*from\s*(['"])([^'"]+)\2/g;
|
|
2062
|
+
var BARE_IMPORT = /(?:^|\n)\s*import\s*(['"])([^'"]+)\1/g;
|
|
2063
|
+
var DYNAMIC_IMPORT = /\bimport\(\s*(['"])([^'"]+)\1/g;
|
|
2064
|
+
var SOURCE_EXTENSIONS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
|
|
2065
|
+
var SOURCE_EXTENSION = /\.(?:ts|tsx|js|jsx|mjs|cjs)$/;
|
|
2066
|
+
function clientSuspenseFree(seeds) {
|
|
2067
|
+
const queue = [];
|
|
2068
|
+
const visited = new Set;
|
|
2069
|
+
for (const seed of seeds) {
|
|
2070
|
+
if (!seed || visited.has(seed))
|
|
2071
|
+
continue;
|
|
2072
|
+
visited.add(seed);
|
|
2073
|
+
queue.push(seed);
|
|
2074
|
+
}
|
|
2075
|
+
while (queue.length > 0) {
|
|
2076
|
+
const file = queue.pop();
|
|
2077
|
+
if (!SOURCE_EXTENSION.test(file) || !existsSync2(file))
|
|
2078
|
+
return false;
|
|
2079
|
+
let source;
|
|
2080
|
+
try {
|
|
2081
|
+
source = readSourceSync(file);
|
|
2082
|
+
} catch {
|
|
2083
|
+
return false;
|
|
2084
|
+
}
|
|
2085
|
+
if (UNSAFE_TOKENS.test(source) || unsafeDefaultValue(source))
|
|
2086
|
+
return false;
|
|
2087
|
+
for (const [clause, specifier] of importsOf(source)) {
|
|
2088
|
+
if (specifier === "react") {
|
|
2089
|
+
if (!liteReactClause(clause))
|
|
2090
|
+
return false;
|
|
2091
|
+
continue;
|
|
2092
|
+
}
|
|
2093
|
+
if (SAFE_BARE_SPECIFIERS.has(specifier))
|
|
2094
|
+
continue;
|
|
2095
|
+
if (specifier.startsWith(".")) {
|
|
2096
|
+
if (clause !== undefined && typeOnlyClause(clause))
|
|
2097
|
+
continue;
|
|
2098
|
+
const resolved = resolveRelative(path3.dirname(file), specifier);
|
|
2099
|
+
if (resolved === ASSET)
|
|
2100
|
+
continue;
|
|
2101
|
+
if (!resolved)
|
|
2102
|
+
return false;
|
|
2103
|
+
if (!visited.has(resolved)) {
|
|
2104
|
+
visited.add(resolved);
|
|
2105
|
+
queue.push(resolved);
|
|
2106
|
+
}
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
if (clause === undefined || !typeOnlyClause(clause))
|
|
2110
|
+
return false;
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
return true;
|
|
2114
|
+
}
|
|
2115
|
+
function* importsOf(source) {
|
|
2116
|
+
for (const match of source.matchAll(STATIC_IMPORT))
|
|
2117
|
+
yield [match[1], match[3] ?? ""];
|
|
2118
|
+
for (const match of source.matchAll(BARE_IMPORT))
|
|
2119
|
+
yield [undefined, match[2] ?? ""];
|
|
2120
|
+
for (const match of source.matchAll(DYNAMIC_IMPORT))
|
|
2121
|
+
yield [undefined, match[2] ?? ""];
|
|
2122
|
+
}
|
|
2123
|
+
function typeOnlyClause(clause) {
|
|
2124
|
+
return /^type\s/.test(clause.trim());
|
|
2125
|
+
}
|
|
2126
|
+
function liteReactClause(clause) {
|
|
2127
|
+
if (clause === undefined)
|
|
2128
|
+
return true;
|
|
2129
|
+
const trimmed = clause.trim();
|
|
2130
|
+
if (typeOnlyClause(trimmed))
|
|
2131
|
+
return true;
|
|
2132
|
+
const braceStart = trimmed.indexOf("{");
|
|
2133
|
+
if (braceStart !== 0)
|
|
2134
|
+
return false;
|
|
2135
|
+
const braceEnd = trimmed.indexOf("}");
|
|
2136
|
+
if (braceEnd === -1)
|
|
2137
|
+
return false;
|
|
2138
|
+
for (const entry of trimmed.slice(braceStart + 1, braceEnd).split(",")) {
|
|
2139
|
+
const name = entry.trim();
|
|
2140
|
+
if (!name || name.startsWith("type "))
|
|
2141
|
+
continue;
|
|
2142
|
+
const imported = (name.split(/\s+as\s+/)[0] ?? "").trim();
|
|
2143
|
+
if (!LITE_REACT_NAMES.has(imported))
|
|
2144
|
+
return false;
|
|
2145
|
+
}
|
|
2146
|
+
return true;
|
|
2147
|
+
}
|
|
2148
|
+
var ASSET = Symbol("pnext.compat-surface.asset");
|
|
2149
|
+
function resolveRelative(dir, specifier) {
|
|
2150
|
+
let base = path3.resolve(dir, specifier);
|
|
2151
|
+
const direct = fileAt(base);
|
|
2152
|
+
if (direct)
|
|
2153
|
+
return direct;
|
|
2154
|
+
if (/\.(?:js|jsx|mjs|cjs)$/.test(base))
|
|
2155
|
+
base = base.replace(/\.[^.]+$/, "");
|
|
2156
|
+
for (const extension of SOURCE_EXTENSIONS) {
|
|
2157
|
+
const candidate = fileAt(base + extension);
|
|
2158
|
+
if (candidate)
|
|
2159
|
+
return candidate;
|
|
2160
|
+
}
|
|
2161
|
+
for (const extension of SOURCE_EXTENSIONS) {
|
|
2162
|
+
const candidate = fileAt(path3.join(base, `index${extension}`));
|
|
2163
|
+
if (candidate)
|
|
2164
|
+
return candidate;
|
|
2165
|
+
}
|
|
2166
|
+
return;
|
|
2167
|
+
}
|
|
2168
|
+
function fileAt(candidate) {
|
|
2169
|
+
if (!existsSync2(candidate))
|
|
2170
|
+
return;
|
|
2171
|
+
try {
|
|
2172
|
+
if (!statSync(candidate).isFile())
|
|
2173
|
+
return;
|
|
2174
|
+
} catch {
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2177
|
+
return SOURCE_EXTENSION.test(candidate) ? candidate : ASSET;
|
|
2178
|
+
}
|
|
2179
|
+
|
|
2180
|
+
// src/client/prebuilt.ts
|
|
2181
|
+
import { createHash as createHash2 } from "crypto";
|
|
2182
|
+
import { readFileSync, readdirSync, statSync as statSync2 } from "fs";
|
|
2183
|
+
import path4 from "path";
|
|
2184
|
+
import { build as build2 } from "esbuild";
|
|
2185
|
+
var PREBUILT_MODULE = "pnext-runtime.js";
|
|
2186
|
+
var frameworkSrc = path4.resolve(__PNEXT_PACKAGE_ROOT__ + "/src/client", "..");
|
|
2187
|
+
var vendorSpecifiers = [
|
|
2188
|
+
"preact",
|
|
2189
|
+
"preact/hooks",
|
|
2190
|
+
"preact/compat",
|
|
2191
|
+
"preact/compat/client",
|
|
2192
|
+
"preact/jsx-runtime",
|
|
2193
|
+
"preact/jsx-dev-runtime"
|
|
2194
|
+
];
|
|
2195
|
+
var reactAliasSpecifier = /^react(?:-dom)?(?:\/|$)/;
|
|
2196
|
+
var isCompilerRuntime = (specifier) => specifier.endsWith("compiler-runtime");
|
|
2197
|
+
var identifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2198
|
+
function mangledName(specifier, name) {
|
|
2199
|
+
return `__p_${shortHash(specifier)}_${name}`;
|
|
2200
|
+
}
|
|
2201
|
+
function shortHash(value) {
|
|
2202
|
+
return createHash2("sha1").update(value).digest("hex").slice(0, 8);
|
|
2203
|
+
}
|
|
2204
|
+
function seedSpecifiers(aliases) {
|
|
2205
|
+
const react = aliases.filter((alias) => reactAliasSpecifier.test(alias) && !isCompilerRuntime(alias));
|
|
2206
|
+
return [...new Set([...vendorSpecifiers, ...react])];
|
|
2207
|
+
}
|
|
2208
|
+
function prebuiltSpecifierFilter(config) {
|
|
2209
|
+
const aliases = Object.keys(getImportAliasExtensions().aliases(config, "client"));
|
|
2210
|
+
const bare = [...new Set([...aliases, ...vendorSpecifiers])];
|
|
2211
|
+
return new RegExp(`^(?:${bare.map(escapeRegex).join("|")}|${escapeRegex(frameworkSrc)}/)`);
|
|
2212
|
+
}
|
|
2213
|
+
function isFrameworkSpecifier(specifier, filter) {
|
|
2214
|
+
if (specifier === CLIENT_RUNTIME_MODULE)
|
|
2215
|
+
return false;
|
|
2216
|
+
if (isCompilerRuntime(specifier))
|
|
2217
|
+
return false;
|
|
2218
|
+
if (specifier.startsWith(frameworkSrc))
|
|
2219
|
+
return true;
|
|
2220
|
+
return filter.test(specifier) && !specifier.startsWith(".");
|
|
2221
|
+
}
|
|
2222
|
+
function prebuiltRuntimeKey(config, buildOptions, signature = "") {
|
|
2223
|
+
const hash = createHash2("sha1");
|
|
2224
|
+
hash.update(frameworkSourceHash());
|
|
2225
|
+
hash.update("\x00");
|
|
2226
|
+
hash.update(JSON.stringify(Object.keys(getImportAliasExtensions().aliases(config, "client"))));
|
|
2227
|
+
hash.update("\x00");
|
|
2228
|
+
hash.update(JSON.stringify([
|
|
2229
|
+
buildOptions.define,
|
|
2230
|
+
buildOptions.minify,
|
|
2231
|
+
buildOptions.target,
|
|
2232
|
+
buildOptions.pure
|
|
2233
|
+
]));
|
|
2234
|
+
hash.update("\x00");
|
|
2235
|
+
hash.update(signature);
|
|
2236
|
+
return hash.digest("hex").slice(0, 16);
|
|
2237
|
+
}
|
|
2238
|
+
var cachedSourceHash;
|
|
2239
|
+
function frameworkSourceHash() {
|
|
2240
|
+
if (cachedSourceHash)
|
|
2241
|
+
return cachedSourceHash;
|
|
2242
|
+
const hash = createHash2("sha1");
|
|
2243
|
+
const walk = (dir) => {
|
|
2244
|
+
let entries;
|
|
2245
|
+
try {
|
|
2246
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
2247
|
+
} catch {
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : 1)) {
|
|
2251
|
+
const file = path4.join(dir, entry.name);
|
|
2252
|
+
if (entry.isDirectory())
|
|
2253
|
+
walk(file);
|
|
2254
|
+
else if (/\.(?:tsx?|jsx?|mjs|cjs)$/.test(entry.name)) {
|
|
2255
|
+
const stats = statSync2(file, { throwIfNoEntry: false });
|
|
2256
|
+
if (stats)
|
|
2257
|
+
hash.update(`${file}:${stats.size}:${stats.mtimeMs}
|
|
2258
|
+
`);
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
};
|
|
2262
|
+
walk(frameworkSrc);
|
|
2263
|
+
cachedSourceHash = hash.digest("hex").slice(0, 16);
|
|
2264
|
+
return cachedSourceHash;
|
|
2265
|
+
}
|
|
2266
|
+
async function probeExports(specifiers, options) {
|
|
2267
|
+
const found = new Map;
|
|
2268
|
+
if (specifiers.length === 0)
|
|
2269
|
+
return found;
|
|
2270
|
+
const probeDir = path4.join(options.dir, ".probe");
|
|
2271
|
+
await ensureDir(probeDir);
|
|
2272
|
+
const entryPoints = [];
|
|
2273
|
+
for (const specifier of specifiers) {
|
|
2274
|
+
const file = path4.join(probeDir, `${shortHash(specifier)}.ts`);
|
|
2275
|
+
await writeText(file, `export * from ${JSON.stringify(specifier)};
|
|
2276
|
+
`);
|
|
2277
|
+
entryPoints.push({ in: file, out: shortHash(specifier) });
|
|
2278
|
+
}
|
|
2279
|
+
const result = await build2({
|
|
2280
|
+
...options.buildOptions,
|
|
2281
|
+
entryPoints,
|
|
2282
|
+
outdir: probeDir,
|
|
2283
|
+
entryNames: "[name]",
|
|
2284
|
+
splitting: true,
|
|
2285
|
+
write: false,
|
|
2286
|
+
metafile: true,
|
|
2287
|
+
plugins: options.plugins()
|
|
2288
|
+
});
|
|
2289
|
+
for (const specifier of specifiers) {
|
|
2290
|
+
const output = Object.entries(result.metafile.outputs).find(([name]) => path4.basename(name) === `${shortHash(specifier)}.js`);
|
|
2291
|
+
found.set(specifier, (output?.[1].exports ?? []).filter((name) => identifier.test(name)));
|
|
2292
|
+
}
|
|
2293
|
+
return found;
|
|
2294
|
+
}
|
|
2295
|
+
async function probeSurface(specifiers, options) {
|
|
2296
|
+
const probed = await probeExports(specifiers, options);
|
|
2297
|
+
if (probed.size === 0)
|
|
2298
|
+
return probed;
|
|
2299
|
+
const candidate = new Map([...probed].map(([specifier, names]) => [specifier, new Set([...names, "default"])]));
|
|
2300
|
+
const { text } = await buildAggregate(options, candidate, false);
|
|
2301
|
+
return new Map([...candidate].map(([specifier, names]) => [
|
|
2302
|
+
specifier,
|
|
2303
|
+
[...names].filter((name) => text.includes(mangledName(specifier, name)))
|
|
2304
|
+
]));
|
|
2305
|
+
}
|
|
2306
|
+
async function buildAggregate(options, surface, write) {
|
|
2307
|
+
const lines = [];
|
|
2308
|
+
for (const specifier of [...surface.keys()].sort()) {
|
|
2309
|
+
const names = [...surface.get(specifier)].filter((name) => identifier.test(name)).sort();
|
|
2310
|
+
const spec = JSON.stringify(specifier);
|
|
2311
|
+
if (names.length === 0)
|
|
2312
|
+
lines.push(`import ${spec};`);
|
|
2313
|
+
else {
|
|
2314
|
+
const clause = names.map((name) => `${name} as ${mangledName(specifier, name)}`).join(", ");
|
|
2315
|
+
lines.push(`export { ${clause} } from ${spec};`);
|
|
2316
|
+
}
|
|
2317
|
+
}
|
|
2318
|
+
const aggregateDir = path4.join(options.dir, ".aggregate");
|
|
2319
|
+
await ensureDir(aggregateDir);
|
|
2320
|
+
const entry = path4.join(aggregateDir, "runtime.ts");
|
|
2321
|
+
await writeText(entry, `${lines.join(`
|
|
2322
|
+
`)}
|
|
2323
|
+
`);
|
|
2324
|
+
const result = await build2({
|
|
2325
|
+
...options.buildOptions,
|
|
2326
|
+
entryPoints: [{ in: entry, out: PREBUILT_MODULE.replace(/\.js$/, "") }],
|
|
2327
|
+
outdir: options.dir,
|
|
2328
|
+
entryNames: "[name]",
|
|
2329
|
+
splitting: true,
|
|
2330
|
+
write,
|
|
2331
|
+
metafile: true,
|
|
2332
|
+
plugins: options.plugins()
|
|
2333
|
+
});
|
|
2334
|
+
const emitted = result.outputFiles?.find((file) => path4.basename(file.path) === PREBUILT_MODULE);
|
|
2335
|
+
const text = emitted?.text ?? readFileSafe(path4.join(options.dir, PREBUILT_MODULE));
|
|
2336
|
+
return { text, metafile: result.metafile };
|
|
2337
|
+
}
|
|
2338
|
+
function readFileSafe(file) {
|
|
2339
|
+
try {
|
|
2340
|
+
return readFileSync(file, "utf8");
|
|
2341
|
+
} catch {
|
|
2342
|
+
return "";
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
async function buildPrebuiltRuntime(options, exportsBySpecifier, surface) {
|
|
2346
|
+
const { metafile } = await buildAggregate(options, surface, true);
|
|
2347
|
+
return {
|
|
2348
|
+
key: options.key,
|
|
2349
|
+
dir: options.dir,
|
|
2350
|
+
publicPath: options.publicPath,
|
|
2351
|
+
assetPath: options.assetPath,
|
|
2352
|
+
exports: exportsBySpecifier,
|
|
2353
|
+
surface,
|
|
2354
|
+
closure: aggregateClosure(metafile)
|
|
2355
|
+
};
|
|
2356
|
+
}
|
|
2357
|
+
function aggregateClosure(metafile) {
|
|
2358
|
+
const entry = Object.keys(metafile.outputs).find((name) => path4.basename(name) === PREBUILT_MODULE);
|
|
2359
|
+
const statics = new Set;
|
|
2360
|
+
const dynamics = new Set;
|
|
2361
|
+
if (!entry)
|
|
2362
|
+
return { static: [], dynamic: [] };
|
|
2363
|
+
const seen = new Set([`${entry}:static`]);
|
|
2364
|
+
const queue = [{ output: entry, dynamic: false }];
|
|
2365
|
+
for (let head = 0;head < queue.length; head += 1) {
|
|
2366
|
+
const current = queue[head];
|
|
2367
|
+
for (const imported of metafile.outputs[current.output]?.imports ?? []) {
|
|
2368
|
+
if (imported.kind !== "import-statement" && imported.kind !== "dynamic-import")
|
|
2369
|
+
continue;
|
|
2370
|
+
const dynamic = current.dynamic || imported.kind === "dynamic-import";
|
|
2371
|
+
const visitKey = `${imported.path}:${dynamic ? "dynamic" : "static"}`;
|
|
2372
|
+
if (seen.has(visitKey))
|
|
2373
|
+
continue;
|
|
2374
|
+
seen.add(visitKey);
|
|
2375
|
+
queue.push({ output: imported.path, dynamic });
|
|
2376
|
+
const relative = path4.relative(path4.dirname(entry), imported.path).split(path4.sep).join("/");
|
|
2377
|
+
(dynamic ? dynamics : statics).add(relative);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
for (const name of statics)
|
|
2381
|
+
dynamics.delete(name);
|
|
2382
|
+
return { static: [...statics], dynamic: [...dynamics] };
|
|
2383
|
+
}
|
|
2384
|
+
function prebuiltExternalPlugin(runtime, filter, unprobed, sourceOf) {
|
|
2385
|
+
const namespace = "pnext-prebuilt";
|
|
2386
|
+
const moduleUrl = prebuiltModuleUrl(runtime);
|
|
2387
|
+
return {
|
|
2388
|
+
name: "pnext-prebuilt-runtime",
|
|
2389
|
+
setup(build3) {
|
|
2390
|
+
build3.onResolve({ filter: new RegExp(`^${escapeRegex(moduleUrl)}$`) }, () => ({
|
|
2391
|
+
path: moduleUrl,
|
|
2392
|
+
external: true
|
|
2393
|
+
}));
|
|
2394
|
+
build3.onResolve({ filter }, (args) => {
|
|
2395
|
+
if (args.namespace === namespace)
|
|
2396
|
+
return;
|
|
2397
|
+
if (!isFrameworkSpecifier(args.path, filter))
|
|
2398
|
+
return;
|
|
2399
|
+
if (!runtime.exports.has(args.path)) {
|
|
2400
|
+
unprobed.add(args.path);
|
|
2401
|
+
return;
|
|
2402
|
+
}
|
|
2403
|
+
if (args.kind === "dynamic-import" && !runtime.surface.has(args.path))
|
|
2404
|
+
return;
|
|
2405
|
+
return { path: `${args.path}\x00${args.importer}`, namespace };
|
|
2406
|
+
});
|
|
2407
|
+
build3.onLoad({ filter: /.*/, namespace }, (args) => {
|
|
2408
|
+
const separator = args.path.lastIndexOf("\x00");
|
|
2409
|
+
const specifier = args.path.slice(0, separator);
|
|
2410
|
+
const importer = args.path.slice(separator + 1);
|
|
2411
|
+
const available = runtime.exports.get(specifier) ?? [];
|
|
2412
|
+
const demanded = demandedNames(importer, specifier, sourceOf(importer), available);
|
|
2413
|
+
const clause = demanded.map((name) => `${mangledName(specifier, name)} as ${name}`).join(", ");
|
|
2414
|
+
const contents = clause.length > 0 ? `export { ${clause} } from ${JSON.stringify(moduleUrl)};
|
|
2415
|
+
` : `import ${JSON.stringify(moduleUrl)};
|
|
2416
|
+
`;
|
|
2417
|
+
return { contents, loader: "js", resolveDir: path4.dirname(specifier) };
|
|
2418
|
+
});
|
|
2419
|
+
}
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
function demandedNames(importer, specifier, source, available) {
|
|
2423
|
+
if (source === undefined)
|
|
2424
|
+
return available;
|
|
2425
|
+
const sourceFile = /\.[cm]?[jt]sx?$/i.test(importer) ? importer : `${importer}.tsx`;
|
|
2426
|
+
let edges;
|
|
2427
|
+
try {
|
|
2428
|
+
edges = scanFacts(sourceFile, source).imports;
|
|
2429
|
+
} catch {
|
|
2430
|
+
return available;
|
|
2431
|
+
}
|
|
2432
|
+
const names = new Set;
|
|
2433
|
+
let named = false;
|
|
2434
|
+
for (const edge of edges) {
|
|
2435
|
+
if (edge.specifier !== specifier)
|
|
2436
|
+
continue;
|
|
2437
|
+
named = true;
|
|
2438
|
+
if (edge.star || edge.exports.includes("*"))
|
|
2439
|
+
return available;
|
|
2440
|
+
for (const name of edge.exports)
|
|
2441
|
+
names.add(name);
|
|
2442
|
+
}
|
|
2443
|
+
if (!named)
|
|
2444
|
+
return available;
|
|
2445
|
+
const offered = available.filter((name) => names.has(name));
|
|
2446
|
+
for (const name of names)
|
|
2447
|
+
if (!available.includes(name))
|
|
2448
|
+
offered.push(name);
|
|
2449
|
+
return offered;
|
|
2450
|
+
}
|
|
2451
|
+
function prebuiltModuleUrl(runtime) {
|
|
2452
|
+
return `${runtime.publicPath}/${PREBUILT_MODULE}`;
|
|
2453
|
+
}
|
|
2454
|
+
function demandedFromOutputs(runtime, outDir) {
|
|
2455
|
+
const moduleUrl = prebuiltModuleUrl(runtime);
|
|
2456
|
+
const byMangled = new Map;
|
|
2457
|
+
for (const [specifier, names] of runtime.exports) {
|
|
2458
|
+
for (const name of names)
|
|
2459
|
+
byMangled.set(mangledName(specifier, name), { specifier, name });
|
|
2460
|
+
}
|
|
2461
|
+
const demanded = new Map;
|
|
2462
|
+
const walk = (dir) => {
|
|
2463
|
+
let entries;
|
|
2464
|
+
try {
|
|
2465
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
2466
|
+
} catch {
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
for (const entry of entries) {
|
|
2470
|
+
const file = path4.join(dir, entry.name);
|
|
2471
|
+
if (entry.isDirectory()) {
|
|
2472
|
+
if (file !== runtime.dir)
|
|
2473
|
+
walk(file);
|
|
2474
|
+
continue;
|
|
2475
|
+
}
|
|
2476
|
+
if (!entry.name.endsWith(".js"))
|
|
2477
|
+
continue;
|
|
2478
|
+
let source;
|
|
2479
|
+
try {
|
|
2480
|
+
source = readFileSync(file, "utf8");
|
|
2481
|
+
} catch {
|
|
2482
|
+
continue;
|
|
2483
|
+
}
|
|
2484
|
+
if (!source.includes(moduleUrl))
|
|
2485
|
+
continue;
|
|
2486
|
+
for (const edge of scanFacts(file, source).imports) {
|
|
2487
|
+
if (edge.specifier !== moduleUrl)
|
|
2488
|
+
continue;
|
|
2489
|
+
for (const bound of edge.exports) {
|
|
2490
|
+
const origin = byMangled.get(bound);
|
|
2491
|
+
if (!origin)
|
|
2492
|
+
continue;
|
|
2493
|
+
const names = demanded.get(origin.specifier) ?? new Set;
|
|
2494
|
+
demanded.set(origin.specifier, names);
|
|
2495
|
+
names.add(origin.name);
|
|
2496
|
+
}
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
walk(outDir);
|
|
2501
|
+
return demanded;
|
|
2502
|
+
}
|
|
2503
|
+
function surfaceCovered(runtime, demanded) {
|
|
2504
|
+
for (const [specifier, names] of demanded) {
|
|
2505
|
+
const have = runtime.surface.get(specifier);
|
|
2506
|
+
if (!have)
|
|
2507
|
+
return false;
|
|
2508
|
+
for (const name of names)
|
|
2509
|
+
if (!have.has(name))
|
|
2510
|
+
return false;
|
|
2511
|
+
}
|
|
2512
|
+
return true;
|
|
2513
|
+
}
|
|
2514
|
+
var manifestName = "pnext-runtime.json";
|
|
2515
|
+
var artifactLocks = new Map;
|
|
2516
|
+
function underLock(dir, run) {
|
|
2517
|
+
const previous = artifactLocks.get(dir) ?? Promise.resolve();
|
|
2518
|
+
const next = previous.then(run, run);
|
|
2519
|
+
artifactLocks.set(dir, next.catch(() => {
|
|
2520
|
+
return;
|
|
2521
|
+
}));
|
|
2522
|
+
return next;
|
|
2523
|
+
}
|
|
2524
|
+
function ensurePrebuiltRuntime(options) {
|
|
2525
|
+
return underLock(options.dir, () => openPrebuiltRuntime(options));
|
|
2526
|
+
}
|
|
2527
|
+
async function openPrebuiltRuntime(options) {
|
|
2528
|
+
const manifest = readManifest(options.dir);
|
|
2529
|
+
if (manifest) {
|
|
2530
|
+
return {
|
|
2531
|
+
key: options.key,
|
|
2532
|
+
dir: options.dir,
|
|
2533
|
+
publicPath: options.publicPath,
|
|
2534
|
+
assetPath: options.assetPath,
|
|
2535
|
+
...manifest
|
|
2536
|
+
};
|
|
2537
|
+
}
|
|
2538
|
+
const exportsBySpecifier = await probeSurface(seedSpecifiers(options.aliases), options);
|
|
2539
|
+
const runtime = await buildPrebuiltRuntime(options, exportsBySpecifier, new Map);
|
|
2540
|
+
await writeManifest(runtime);
|
|
2541
|
+
return runtime;
|
|
2542
|
+
}
|
|
2543
|
+
function settlePrebuiltRuntime(runtime, options, outDir, unprobed) {
|
|
2544
|
+
return underLock(options.dir, () => growPrebuiltRuntime(runtime, options, outDir, unprobed));
|
|
2545
|
+
}
|
|
2546
|
+
async function growPrebuiltRuntime(runtime, options, outDir, unprobed) {
|
|
2547
|
+
const current = readManifest(options.dir);
|
|
2548
|
+
if (current)
|
|
2549
|
+
runtime = { ...runtime, ...current };
|
|
2550
|
+
const demanded = demandedFromOutputs(runtime, outDir);
|
|
2551
|
+
if (unprobed.size === 0 && surfaceCovered(runtime, demanded))
|
|
2552
|
+
return runtime;
|
|
2553
|
+
const exportsBySpecifier = new Map(runtime.exports);
|
|
2554
|
+
for (const [specifier, names] of await probeSurface([...unprobed], options)) {
|
|
2555
|
+
exportsBySpecifier.set(specifier, names);
|
|
2556
|
+
}
|
|
2557
|
+
const surface = new Map(runtime.surface);
|
|
2558
|
+
for (const [specifier, names] of demanded) {
|
|
2559
|
+
const target = surface.get(specifier) ?? new Set;
|
|
2560
|
+
surface.set(specifier, target);
|
|
2561
|
+
for (const name of names)
|
|
2562
|
+
target.add(name);
|
|
2563
|
+
}
|
|
2564
|
+
const grown = await buildPrebuiltRuntime(options, exportsBySpecifier, surface);
|
|
2565
|
+
await writeManifest(grown);
|
|
2566
|
+
return grown;
|
|
2567
|
+
}
|
|
2568
|
+
function readManifest(dir) {
|
|
2569
|
+
try {
|
|
2570
|
+
const parsed = JSON.parse(readFileSync(path4.join(dir, manifestName), "utf8"));
|
|
2571
|
+
if (!parsed.exports)
|
|
2572
|
+
return;
|
|
2573
|
+
return {
|
|
2574
|
+
exports: new Map(Object.entries(parsed.exports)),
|
|
2575
|
+
surface: new Map(Object.entries(parsed.surface).map(([spec, n]) => [spec, new Set(n)])),
|
|
2576
|
+
closure: parsed.closure
|
|
2577
|
+
};
|
|
2578
|
+
} catch {
|
|
2579
|
+
return;
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
async function writeManifest(runtime) {
|
|
2583
|
+
const manifest = {
|
|
2584
|
+
key: runtime.key,
|
|
2585
|
+
exports: Object.fromEntries(runtime.exports),
|
|
2586
|
+
surface: Object.fromEntries([...runtime.surface].map(([s, n]) => [s, [...n].sort()])),
|
|
2587
|
+
closure: runtime.closure
|
|
2588
|
+
};
|
|
2589
|
+
await writeText(path4.join(runtime.dir, manifestName), JSON.stringify(manifest));
|
|
2590
|
+
}
|
|
2591
|
+
function prebuiltAssets(runtime) {
|
|
2592
|
+
const join = (name) => `${runtime.assetPath}/${name}`;
|
|
2593
|
+
return {
|
|
2594
|
+
static: [join(PREBUILT_MODULE), ...runtime.closure.static.map(join)],
|
|
2595
|
+
dynamic: runtime.closure.dynamic.map(join)
|
|
2596
|
+
};
|
|
2597
|
+
}
|
|
2598
|
+
|
|
2599
|
+
// src/client/react-compiler.ts
|
|
2600
|
+
import {
|
|
2601
|
+
transform,
|
|
2602
|
+
transformSync
|
|
2603
|
+
} from "oxc-transform";
|
|
2604
|
+
function compilerOptions(options) {
|
|
2605
|
+
return {
|
|
2606
|
+
jsx: "preserve",
|
|
2607
|
+
sourcemap: false,
|
|
2608
|
+
reactCompiler: {
|
|
2609
|
+
panicThreshold: "critical_errors",
|
|
2610
|
+
target: options.target
|
|
2611
|
+
}
|
|
2612
|
+
};
|
|
2613
|
+
}
|
|
2614
|
+
async function transformReactCompiler(source, file, options) {
|
|
2615
|
+
return reactCompilerResult(await transform(file, source, compilerOptions(options)), source, file);
|
|
2616
|
+
}
|
|
2617
|
+
function reactCompilerResult(result, source, file) {
|
|
2618
|
+
if (!result.code) {
|
|
2619
|
+
const failures = result.errors.filter((error) => !error.message.startsWith("[ReactCompiler]"));
|
|
2620
|
+
if (failures.length > 0) {
|
|
2621
|
+
console.error(`React Compiler (oxc) failed for ${file}:
|
|
2622
|
+
${failures.map((error) => error.message).join(`
|
|
2623
|
+
`)}`);
|
|
2624
|
+
}
|
|
2625
|
+
return source;
|
|
2626
|
+
}
|
|
2627
|
+
return result.code;
|
|
2628
|
+
}
|
|
2629
|
+
function shouldReactCompile(source, file) {
|
|
2630
|
+
if (/\.[cm]?[jt]sx$/.test(file))
|
|
2631
|
+
return true;
|
|
2632
|
+
return /\b(?:use[A-Z][\w$]*|createElement|memo|forwardRef)\b/.test(source);
|
|
2633
|
+
}
|
|
2634
|
+
|
|
2635
|
+
// src/client/build.ts
|
|
2636
|
+
async function emitStaticClientChunks(config, outDir) {
|
|
2637
|
+
const chunks = getBundlerExtensions().staticClientChunks(config);
|
|
2638
|
+
if (chunks.length === 0)
|
|
2639
|
+
return;
|
|
2640
|
+
const chunksDir = path5.join(outDir, "chunks");
|
|
2641
|
+
await ensureDir(chunksDir);
|
|
2642
|
+
for (const chunk of chunks) {
|
|
2643
|
+
await writeText(path5.join(chunksDir, chunk.name), chunk.contents);
|
|
2644
|
+
}
|
|
2645
|
+
}
|
|
2646
|
+
var virtualEntryNamespace = "pnext-client-entry";
|
|
2647
|
+
function nextCompatEnabled(config) {
|
|
2648
|
+
return getCompatModeExtensions().nextEnabled(config);
|
|
2649
|
+
}
|
|
2650
|
+
var conventionCache = new Map;
|
|
2651
|
+
function memoConvention(key, compute) {
|
|
2652
|
+
if (conventionCache.has(key))
|
|
2653
|
+
return conventionCache.get(key);
|
|
2654
|
+
const value = compute();
|
|
2655
|
+
conventionCache.set(key, value);
|
|
2656
|
+
return value;
|
|
2657
|
+
}
|
|
2658
|
+
function appUsesActions(routes, hasServerActions) {
|
|
2659
|
+
if (hasServerActions !== false)
|
|
2660
|
+
return true;
|
|
2661
|
+
return routes.some((route) => route.clientEntryReasons?.some((reason) => reason === "actions" || reason === "form"));
|
|
2662
|
+
}
|
|
2663
|
+
function routeUsesActions(route, appActions) {
|
|
2664
|
+
if (!appActions)
|
|
2665
|
+
return false;
|
|
2666
|
+
return route.clientEntryReasons?.some((reason) => reason === "actions" || reason === "form") === true;
|
|
2667
|
+
}
|
|
2668
|
+
function routeThrowsClientControlFlow(route) {
|
|
2669
|
+
return route.clientEntryReasons?.includes("control-flow") === true;
|
|
2670
|
+
}
|
|
2671
|
+
function routeErrorFile(config, route) {
|
|
2672
|
+
return routeConventionFile(config, route, "error");
|
|
2673
|
+
}
|
|
2674
|
+
function routeNotFoundFile(config, route) {
|
|
2675
|
+
return routeConventionFile(config, route, "not-found");
|
|
2676
|
+
}
|
|
2677
|
+
function routeConventionFile(config, route, name) {
|
|
2678
|
+
if (!nextCompatEnabled(config))
|
|
2679
|
+
return;
|
|
2680
|
+
const appPath = path5.resolve(config.appPath);
|
|
2681
|
+
const start = path5.dirname(path5.resolve(route.file));
|
|
2682
|
+
const relative = path5.relative(appPath, start);
|
|
2683
|
+
if (relative.startsWith("..") || path5.isAbsolute(relative))
|
|
2684
|
+
return;
|
|
2685
|
+
return memoConvention(`${name}\x00${start}`, () => {
|
|
2686
|
+
let dir = start;
|
|
2687
|
+
for (;; ) {
|
|
2688
|
+
for (const ext of ["tsx", "ts", "jsx", "js"]) {
|
|
2689
|
+
const candidate = path5.join(dir, `${name}.${ext}`);
|
|
2690
|
+
if (existsSync3(candidate))
|
|
2691
|
+
return candidate;
|
|
2692
|
+
}
|
|
2693
|
+
if (dir === appPath)
|
|
2694
|
+
return;
|
|
2695
|
+
const parent = path5.dirname(dir);
|
|
2696
|
+
if (parent === dir)
|
|
2697
|
+
return;
|
|
2698
|
+
dir = parent;
|
|
2699
|
+
}
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
function globalErrorFile(config) {
|
|
2703
|
+
if (!nextCompatEnabled(config))
|
|
2704
|
+
return;
|
|
2705
|
+
const appPath = path5.resolve(config.appPath);
|
|
2706
|
+
return memoConvention(`global-error\x00${appPath}`, () => {
|
|
2707
|
+
for (const ext of ["tsx", "ts", "jsx", "js"]) {
|
|
2708
|
+
const candidate = path5.join(appPath, `global-error.${ext}`);
|
|
2709
|
+
if (existsSync3(candidate))
|
|
2710
|
+
return candidate;
|
|
2711
|
+
}
|
|
2712
|
+
return;
|
|
2713
|
+
});
|
|
2714
|
+
}
|
|
2715
|
+
function hasClientRootLayout(config, route) {
|
|
2716
|
+
if (route.client)
|
|
2717
|
+
return false;
|
|
2718
|
+
const rootLayout = shellLayoutOrder(config, route)[0];
|
|
2719
|
+
if (!rootLayout)
|
|
2720
|
+
return false;
|
|
2721
|
+
return route.clientReferences.some((reference) => reference.file === rootLayout);
|
|
2722
|
+
}
|
|
2723
|
+
function shellLayoutOrder(config, route) {
|
|
2724
|
+
return memoConvention(`layouts\x00${config.appPath}\x00${route.file}`, () => findLayouts(config.appPath, route.file));
|
|
2725
|
+
}
|
|
2726
|
+
function prebuiltLocation(config, outDir, key, dev) {
|
|
2727
|
+
if (dev) {
|
|
2728
|
+
return {
|
|
2729
|
+
dir: prebuiltRuntimeDir(config, key),
|
|
2730
|
+
publicPath: `/__pnext/runtime/${key}`,
|
|
2731
|
+
assetPath: `__pnext/runtime/${key}`
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
const served = nextCompatEnabled(config) ? `/_next/static/rt/${key}` : `/assets/rt/${key}`;
|
|
2735
|
+
return {
|
|
2736
|
+
dir: path5.join(outDir, "rt", key),
|
|
2737
|
+
publicPath: withAssetPrefix(config, served),
|
|
2738
|
+
assetPath: `assets/rt/${key}`
|
|
2739
|
+
};
|
|
2740
|
+
}
|
|
2741
|
+
function prebuiltRuntimeDir(config, key) {
|
|
2742
|
+
return path5.join(config.root, "node_modules", ".cache", "pnext", "client-runtime", key);
|
|
2743
|
+
}
|
|
2744
|
+
function prebuiltEnabled() {
|
|
2745
|
+
return process.env.PNEXT_PREBUILT_RUNTIME === "1";
|
|
2746
|
+
}
|
|
2747
|
+
function surfaceSignature(config, route, actions) {
|
|
2748
|
+
const nextCompat = nextCompatEnabled(config);
|
|
2749
|
+
const facts = clientRuntimeFacts([
|
|
2750
|
+
{
|
|
2751
|
+
route,
|
|
2752
|
+
shell: hasClientRootLayout(config, route)
|
|
2753
|
+
}
|
|
2754
|
+
], nextCompat, !routeSuspenseFree(config, route));
|
|
2755
|
+
return JSON.stringify([
|
|
2756
|
+
nextCompat,
|
|
2757
|
+
actions,
|
|
2758
|
+
Boolean(routeErrorFile(config, route)),
|
|
2759
|
+
Boolean(globalErrorFile(config)),
|
|
2760
|
+
Boolean(routeNotFoundFile(config, route)),
|
|
2761
|
+
routeThrowsClientControlFlow(route) === true,
|
|
2762
|
+
Boolean(route.needsRouterEntry),
|
|
2763
|
+
facts
|
|
2764
|
+
]);
|
|
2765
|
+
}
|
|
2766
|
+
async function preparePrebuilt(config, outDir, dev, sourceOf, signature = "", reactLite = false) {
|
|
2767
|
+
if (!prebuiltEnabled())
|
|
2768
|
+
return;
|
|
2769
|
+
const buildOptions = baseClientBuildOptions(config, dev);
|
|
2770
|
+
const key = prebuiltRuntimeKey(config, buildOptions, signature);
|
|
2771
|
+
const { dir, publicPath, assetPath } = prebuiltLocation(config, outDir, key, dev);
|
|
2772
|
+
const options = {
|
|
2773
|
+
aliases: Object.keys(getImportAliasExtensions().aliases(config, "client")),
|
|
2774
|
+
key,
|
|
2775
|
+
dir,
|
|
2776
|
+
publicPath,
|
|
2777
|
+
assetPath,
|
|
2778
|
+
buildOptions,
|
|
2779
|
+
plugins: () => clientBuildPlugins(config, createClientSourcePipeline(config), [], reactLite)
|
|
2780
|
+
};
|
|
2781
|
+
const runtime = await ensurePrebuiltRuntime(options);
|
|
2782
|
+
const unprobed = new Set;
|
|
2783
|
+
return {
|
|
2784
|
+
runtime,
|
|
2785
|
+
plugin: prebuiltExternalPlugin(runtime, prebuiltSpecifierFilter(config), unprobed, sourceOf),
|
|
2786
|
+
async settle() {
|
|
2787
|
+
await settlePrebuiltRuntime(runtime, options, outDir, unprobed);
|
|
2788
|
+
}
|
|
2789
|
+
};
|
|
2790
|
+
}
|
|
2791
|
+
function deferredDynamicChunkHref(reference, routeId) {
|
|
2792
|
+
const query = routeId ? `?r=${encodeURIComponent(routeId)}` : "";
|
|
2793
|
+
return `/__pnext/client-dyn/${reference.id}.js${query}`;
|
|
2794
|
+
}
|
|
2795
|
+
var devClientPublicPath = "/__pnext/client";
|
|
2796
|
+
function deferredDynamicEntries(route) {
|
|
2797
|
+
const entries = new Map;
|
|
2798
|
+
for (const reference of route.clientReferences) {
|
|
2799
|
+
if (reference.dynamic && !ssrClientReference(reference)) {
|
|
2800
|
+
entries.set(reference.id, reference.file);
|
|
2801
|
+
}
|
|
2802
|
+
}
|
|
2803
|
+
for (const [id, ref] of deferredDynamicRouteRefs.get(route.id) ?? [])
|
|
2804
|
+
entries.set(id, ref.file);
|
|
2805
|
+
return [...entries].map(([id, file]) => ({ id, file }));
|
|
2806
|
+
}
|
|
2807
|
+
var deferredDynamicRefs = new Map;
|
|
2808
|
+
var deferredDynamicRouteRefs = new Map;
|
|
2809
|
+
function registerDeferredDynamicRef(id, ref) {
|
|
2810
|
+
deferredDynamicRefs.set(id, ref);
|
|
2811
|
+
if (!ref.routeId)
|
|
2812
|
+
return;
|
|
2813
|
+
const refs = deferredDynamicRouteRefs.get(ref.routeId) ?? new Map;
|
|
2814
|
+
deferredDynamicRouteRefs.set(ref.routeId, refs);
|
|
2815
|
+
refs.set(id, ref);
|
|
2816
|
+
}
|
|
2817
|
+
function deferredDynamicRefById(id) {
|
|
2818
|
+
return deferredDynamicRefs.get(id);
|
|
2819
|
+
}
|
|
2820
|
+
var DEFERRED_DYNAMIC_SIDECAR = "dyn-refs.json";
|
|
2821
|
+
function devDeferredDynamicHref(dev, routeId) {
|
|
2822
|
+
if (!dev || !devDynamicSplitEnabled())
|
|
2823
|
+
return;
|
|
2824
|
+
return (reference) => reference.dynamic && !ssrClientReference(reference) ? deferredDynamicChunkHref(reference, routeId) : undefined;
|
|
2825
|
+
}
|
|
2826
|
+
function deferredDynamicExternalPlugin() {
|
|
2827
|
+
return {
|
|
2828
|
+
name: "pnext-dyn-split-external",
|
|
2829
|
+
setup(build4) {
|
|
2830
|
+
build4.onResolve({ filter: /^\/__pnext\/client-dyn\// }, (args) => ({
|
|
2831
|
+
path: args.path,
|
|
2832
|
+
external: true
|
|
2833
|
+
}));
|
|
2834
|
+
}
|
|
2835
|
+
};
|
|
2836
|
+
}
|
|
2837
|
+
async function buildClientEntry({ config, route, outDir, dev }) {
|
|
2838
|
+
conventionCache.clear();
|
|
2839
|
+
await ensureDir(outDir);
|
|
2840
|
+
const suspense = routeSuspenseFree(config, route) === false;
|
|
2841
|
+
const source = clientEntrySource({
|
|
2842
|
+
deferredDynamicHref: devDeferredDynamicHref(dev, route.id),
|
|
2843
|
+
pageFile: route.client ? route.file : undefined,
|
|
2844
|
+
clientReferences: route.clientReferences,
|
|
2845
|
+
nextCompat: nextCompatEnabled(config),
|
|
2846
|
+
suspense,
|
|
2847
|
+
actions: true,
|
|
2848
|
+
errorFile: routeErrorFile(config, route),
|
|
2849
|
+
globalErrorFile: globalErrorFile(config),
|
|
2850
|
+
notFoundFile: routeNotFoundFile(config, route),
|
|
2851
|
+
controlFlow: routeThrowsClientControlFlow(route),
|
|
2852
|
+
router: Boolean(route.needsRouterEntry),
|
|
2853
|
+
clientRootLayout: hasClientRootLayout(config, route),
|
|
2854
|
+
shellLayoutOrder: shellLayoutOrder(config, route)
|
|
2855
|
+
});
|
|
2856
|
+
const entryName = clientEntryName(route);
|
|
2857
|
+
const outfile = path5.join(outDir, `${entryName}.js`);
|
|
2858
|
+
const pipeline = createClientSourcePipeline(config, dev === true, route.id);
|
|
2859
|
+
pipeline.warmRoutes([route]);
|
|
2860
|
+
const runtimeFacts = {
|
|
2861
|
+
...clientRuntimeFacts([
|
|
2862
|
+
{
|
|
2863
|
+
route,
|
|
2864
|
+
shell: hasClientRootLayout(config, route)
|
|
2865
|
+
}
|
|
2866
|
+
], nextCompatEnabled(config), suspense),
|
|
2867
|
+
dev: dev === true
|
|
2868
|
+
};
|
|
2869
|
+
const prebuilt = await clientProfile.timeAsync("prebuilt", () => preparePrebuilt(config, outDir, dev === true, (importer) => importer === CLIENT_RUNTIME_MODULE ? clientRuntimeSource(runtimeFacts) : pipeline.sourceOf(importer) ?? readTextSyncSafe(importer), surfaceSignature(config, route, true), !suspense));
|
|
2870
|
+
const split = Boolean(devDeferredDynamicHref(dev));
|
|
2871
|
+
const runEntryBuild = (dynEntries2) => build3({
|
|
2872
|
+
...baseClientBuildOptions(config, dev),
|
|
2873
|
+
entryPoints: [
|
|
2874
|
+
{ in: virtualEntryPath(route.id), out: entryName },
|
|
2875
|
+
...dynEntries2.map((entry) => ({ in: entry.file, out: entry.id }))
|
|
2876
|
+
],
|
|
2877
|
+
...split ? { publicPath: devClientPublicPath } : {},
|
|
2878
|
+
outdir: outDir,
|
|
2879
|
+
metafile: true,
|
|
2880
|
+
plugins: clientBuildPlugins(config, pipeline, [
|
|
2881
|
+
...prebuilt ? [prebuilt.plugin] : [],
|
|
2882
|
+
virtualEntryPlugin([{ route, source }]),
|
|
2883
|
+
...split ? [deferredDynamicExternalPlugin()] : [],
|
|
2884
|
+
clientRuntimePlugin(runtimeFacts)
|
|
2885
|
+
], !suspense)
|
|
2886
|
+
});
|
|
2887
|
+
let metafile;
|
|
2888
|
+
let dynEntries = split ? deferredDynamicEntries(route) : [];
|
|
2889
|
+
try {
|
|
2890
|
+
const result = await profileClientBuild(route, dev, () => clientProfile.timeAsync("esbuild", () => runEntryBuild(dynEntries)));
|
|
2891
|
+
metafile = result.metafile;
|
|
2892
|
+
} catch (error) {
|
|
2893
|
+
throw withClientImportTrace(error, config, route, source);
|
|
2894
|
+
}
|
|
2895
|
+
if (split) {
|
|
2896
|
+
const discovered = deferredDynamicEntries(route);
|
|
2897
|
+
if (discovered.length !== dynEntries.length) {
|
|
2898
|
+
dynEntries = discovered;
|
|
2899
|
+
metafile = (await clientProfile.timeAsync("esbuildDynEntries", () => runEntryBuild(dynEntries))).metafile;
|
|
2900
|
+
}
|
|
2901
|
+
}
|
|
2902
|
+
await clientProfile.timeAsync("prebuiltSettle", () => prebuilt?.settle() ?? Promise.resolve());
|
|
2903
|
+
if (metafile) {
|
|
2904
|
+
metafile = await clientProfile.timeAsync("fold", () => foldInitialChunks({
|
|
2905
|
+
outDir,
|
|
2906
|
+
metafile,
|
|
2907
|
+
buildOptions: baseClientBuildOptions(config, dev)
|
|
2908
|
+
}));
|
|
2909
|
+
}
|
|
2910
|
+
await clientProfile.timeAsync("write", () => nameSharedClientChunks(outDir, metafile));
|
|
2911
|
+
if (dev && devDynamicSplitEnabled() && pipeline.deferredRefs().size > 0) {
|
|
2912
|
+
await writeText(path5.join(outDir, DEFERRED_DYNAMIC_SIDECAR), JSON.stringify(Object.fromEntries(pipeline.deferredRefs())));
|
|
2913
|
+
}
|
|
2914
|
+
clientProfile.report(`client entry ${route.id}`);
|
|
2915
|
+
return outfile;
|
|
2916
|
+
}
|
|
2917
|
+
async function buildClientEntries({
|
|
2918
|
+
config,
|
|
2919
|
+
routes,
|
|
2920
|
+
outDir,
|
|
2921
|
+
verbose,
|
|
2922
|
+
hasServerActions,
|
|
2923
|
+
pipeline: warmed,
|
|
2924
|
+
dev
|
|
2925
|
+
}) {
|
|
2926
|
+
if (!warmed)
|
|
2927
|
+
conventionCache.clear();
|
|
2928
|
+
const actions = appUsesActions(routes, hasServerActions);
|
|
2929
|
+
const entries = clientProfile.time("entrySources", () => routes.filter((route) => route.client || route.clientReferences.length > 0 || route.needsRouterEntry).map((route) => ({
|
|
2930
|
+
route,
|
|
2931
|
+
entryName: clientEntryName(route),
|
|
2932
|
+
source: clientEntrySource({
|
|
2933
|
+
deferredDynamicHref: devDeferredDynamicHref(dev, route.id),
|
|
2934
|
+
pageFile: route.client ? route.file : undefined,
|
|
2935
|
+
clientReferences: route.clientReferences,
|
|
2936
|
+
nextCompat: nextCompatEnabled(config),
|
|
2937
|
+
suspense: !routeSuspenseFree(config, route),
|
|
2938
|
+
actions: routeUsesActions(route, actions),
|
|
2939
|
+
errorFile: routeErrorFile(config, route),
|
|
2940
|
+
globalErrorFile: globalErrorFile(config),
|
|
2941
|
+
notFoundFile: routeNotFoundFile(config, route),
|
|
2942
|
+
controlFlow: routeThrowsClientControlFlow(route),
|
|
2943
|
+
router: Boolean(route.needsRouterEntry),
|
|
2944
|
+
clientRootLayout: hasClientRootLayout(config, route),
|
|
2945
|
+
shellLayoutOrder: shellLayoutOrder(config, route)
|
|
2946
|
+
})
|
|
2947
|
+
})));
|
|
2948
|
+
if (entries.length === 0)
|
|
2949
|
+
return;
|
|
2950
|
+
const pipeline = warmed ?? createClientSourcePipeline(config, dev === true);
|
|
2951
|
+
pipeline.warmRoutes(entries.map((entry) => entry.route));
|
|
2952
|
+
await ensureDir(outDir);
|
|
2953
|
+
const groups = surfaceGroups(config, entries, actions);
|
|
2954
|
+
clientProfile.count("group#", groups.length);
|
|
2955
|
+
const built = [];
|
|
2956
|
+
for (const group of groups) {
|
|
2957
|
+
built.push(await buildEntryGroup({ config, outDir, entries, group, pipeline, verbose, dev }));
|
|
2958
|
+
}
|
|
2959
|
+
let merged = { inputs: {}, outputs: {} };
|
|
2960
|
+
for (const item of built) {
|
|
2961
|
+
if (!item.metafile)
|
|
2962
|
+
continue;
|
|
2963
|
+
Object.assign(merged.inputs, item.metafile.inputs);
|
|
2964
|
+
Object.assign(merged.outputs, item.metafile.outputs);
|
|
2965
|
+
}
|
|
2966
|
+
merged = await clientProfile.timeAsync("fold", () => foldInitialChunks({
|
|
2967
|
+
outDir,
|
|
2968
|
+
metafile: merged,
|
|
2969
|
+
buildOptions: baseClientBuildOptions(config, dev)
|
|
2970
|
+
}));
|
|
2971
|
+
const renames = await clientProfile.timeAsync("write", () => nameSharedClientChunks(outDir, merged));
|
|
2972
|
+
if (!dev) {
|
|
2973
|
+
clientProfile.time("entryImports", () => {
|
|
2974
|
+
for (const item of built) {
|
|
2975
|
+
if (item.metafile) {
|
|
2976
|
+
assignClientEntryImports(item.group.entries, merged, outDir, renames, item.prebuilt);
|
|
2977
|
+
}
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
}
|
|
2981
|
+
if (!dev) {
|
|
2982
|
+
await clientProfile.timeAsync("fingerprint", () => fingerprintClientEntries(outDir, entries));
|
|
2983
|
+
}
|
|
2984
|
+
if (dev && devDynamicSplitEnabled() && pipeline.deferredRefs().size > 0) {
|
|
2985
|
+
await writeText(path5.join(outDir, DEFERRED_DYNAMIC_SIDECAR), JSON.stringify(Object.fromEntries(pipeline.deferredRefs())));
|
|
2986
|
+
}
|
|
2987
|
+
clientProfile.report("client stage");
|
|
2988
|
+
}
|
|
2989
|
+
async function fingerprintClientEntries(outDir, entries) {
|
|
2990
|
+
await Promise.all(entries.map(async (entry) => {
|
|
2991
|
+
const from = path5.join(outDir, `${entry.entryName}.js`);
|
|
2992
|
+
const bytes = await readFile(from).catch(() => {
|
|
2993
|
+
return;
|
|
2994
|
+
});
|
|
2995
|
+
if (!bytes)
|
|
2996
|
+
return;
|
|
2997
|
+
const name = `pnext-client-${assetContentHash(bytes)}.js`;
|
|
2998
|
+
entry.route.clientEntry = `assets/${name}`;
|
|
2999
|
+
const to = path5.join(outDir, name);
|
|
3000
|
+
if (to === from)
|
|
3001
|
+
return;
|
|
3002
|
+
const sourceMap = `${from}.map`;
|
|
3003
|
+
if (existsSync3(sourceMap)) {
|
|
3004
|
+
const source = bytes.toString("utf8").replaceAll(`${entry.entryName}.js.map`, `${name}.map`);
|
|
3005
|
+
await writeFile(to, source);
|
|
3006
|
+
await rm(from, { force: true });
|
|
3007
|
+
await rename(sourceMap, `${to}.map`);
|
|
3008
|
+
return;
|
|
3009
|
+
}
|
|
3010
|
+
await rename(from, to);
|
|
3011
|
+
}));
|
|
3012
|
+
}
|
|
3013
|
+
function surfaceGroups(config, entries, actions) {
|
|
3014
|
+
const facts = (members) => clientRuntimeFacts(members.map((entry) => ({
|
|
3015
|
+
route: entry.route,
|
|
3016
|
+
shell: hasClientRootLayout(config, entry.route)
|
|
3017
|
+
})), nextCompatEnabled(config), !members.every((entry) => routeSuspenseFree(config, entry.route)));
|
|
3018
|
+
if (!prebuiltEnabled() || process.env.PNEXT_PREBUILT_GROUPS === "0")
|
|
3019
|
+
return [{ signature: "", facts: facts(entries), entries }];
|
|
3020
|
+
const bySignature = new Map;
|
|
3021
|
+
for (const entry of entries) {
|
|
3022
|
+
const signature = surfaceSignature(config, entry.route, routeUsesActions(entry.route, actions));
|
|
3023
|
+
const members = bySignature.get(signature) ?? [];
|
|
3024
|
+
bySignature.set(signature, members);
|
|
3025
|
+
members.push(entry);
|
|
3026
|
+
}
|
|
3027
|
+
return [...bySignature].map(([signature, members]) => ({
|
|
3028
|
+
signature,
|
|
3029
|
+
facts: facts(members),
|
|
3030
|
+
entries: members
|
|
3031
|
+
}));
|
|
3032
|
+
}
|
|
3033
|
+
async function buildEntryGroup({
|
|
3034
|
+
config,
|
|
3035
|
+
outDir,
|
|
3036
|
+
entries,
|
|
3037
|
+
group,
|
|
3038
|
+
pipeline,
|
|
3039
|
+
verbose,
|
|
3040
|
+
dev
|
|
3041
|
+
}) {
|
|
3042
|
+
const facts = dev ? { ...group.facts, dev: true } : group.facts;
|
|
3043
|
+
const reactLite = facts.suspense === false;
|
|
3044
|
+
const prebuilt = await clientProfile.timeAsync("prebuilt", () => preparePrebuilt(config, outDir, dev === true, (importer) => {
|
|
3045
|
+
if (importer === CLIENT_RUNTIME_MODULE)
|
|
3046
|
+
return clientRuntimeSource(facts);
|
|
3047
|
+
const entry = entries.find((item) => item.route.id === importer);
|
|
3048
|
+
return entry?.source ?? pipeline.sourceOf(importer) ?? readTextSyncSafe(importer);
|
|
3049
|
+
}, group.signature, reactLite));
|
|
3050
|
+
let metafile;
|
|
3051
|
+
const split = Boolean(dev) && devDynamicSplitEnabled();
|
|
3052
|
+
const groupDynEntries = () => {
|
|
3053
|
+
const byId = new Map;
|
|
3054
|
+
if (!split)
|
|
3055
|
+
return [];
|
|
3056
|
+
for (const entry of group.entries) {
|
|
3057
|
+
for (const dynEntry of deferredDynamicEntries(entry.route))
|
|
3058
|
+
byId.set(dynEntry.id, dynEntry);
|
|
3059
|
+
}
|
|
3060
|
+
return [...byId.values()];
|
|
3061
|
+
};
|
|
3062
|
+
let dynEntries = groupDynEntries();
|
|
3063
|
+
const runGroupBuild = () => build3({
|
|
3064
|
+
...baseClientBuildOptions(config, dev),
|
|
3065
|
+
entryPoints: [
|
|
3066
|
+
...group.entries.map((entry) => ({
|
|
3067
|
+
in: virtualEntryPath(entry.route.id),
|
|
3068
|
+
out: entry.entryName
|
|
3069
|
+
})),
|
|
3070
|
+
...dynEntries.map((entry) => ({ in: entry.file, out: entry.id }))
|
|
3071
|
+
],
|
|
3072
|
+
...split ? { publicPath: devClientPublicPath } : {},
|
|
3073
|
+
outdir: outDir,
|
|
3074
|
+
metafile: true,
|
|
3075
|
+
plugins: clientBuildPlugins(config, pipeline, [
|
|
3076
|
+
...prebuilt ? [prebuilt.plugin] : [],
|
|
3077
|
+
virtualEntryPlugin(group.entries),
|
|
3078
|
+
...split ? [deferredDynamicExternalPlugin()] : [],
|
|
3079
|
+
clientRuntimePlugin(facts)
|
|
3080
|
+
], reactLite)
|
|
3081
|
+
});
|
|
3082
|
+
try {
|
|
3083
|
+
const result = await clientProfile.timeAsync("esbuild", () => runGroupBuild());
|
|
3084
|
+
metafile = result.metafile;
|
|
3085
|
+
} catch (error) {
|
|
3086
|
+
if (!hasUnresolvedImportError(error))
|
|
3087
|
+
throw error;
|
|
3088
|
+
for (const { route } of group.entries) {
|
|
3089
|
+
await buildClientEntry({ config, route, outDir, dev });
|
|
3090
|
+
}
|
|
3091
|
+
throw error;
|
|
3092
|
+
}
|
|
3093
|
+
await clientProfile.timeAsync("prebuiltSettle", () => prebuilt?.settle() ?? Promise.resolve());
|
|
3094
|
+
if (verbose && metafile)
|
|
3095
|
+
reportClientBundleSizes(group.entries, metafile, outDir);
|
|
3096
|
+
return { group, metafile, prebuilt };
|
|
3097
|
+
}
|
|
3098
|
+
function routeSuspenseFree(config, route) {
|
|
3099
|
+
if (!nextCompatEnabled(config))
|
|
3100
|
+
return false;
|
|
3101
|
+
return memoConvention(`suspenseFree:${route.id}`, () => {
|
|
3102
|
+
const seeds = [];
|
|
3103
|
+
if (route.client)
|
|
3104
|
+
seeds.push(route.file);
|
|
3105
|
+
for (const reference of route.clientReferences)
|
|
3106
|
+
seeds.push(reference.file);
|
|
3107
|
+
for (const file of [
|
|
3108
|
+
routeErrorFile(config, route),
|
|
3109
|
+
globalErrorFile(config),
|
|
3110
|
+
routeNotFoundFile(config, route),
|
|
3111
|
+
...hasClientRootLayout(config, route) ? shellLayoutOrder(config, route) : []
|
|
3112
|
+
]) {
|
|
3113
|
+
if (file)
|
|
3114
|
+
seeds.push(file);
|
|
3115
|
+
}
|
|
3116
|
+
return clientSuspenseFree(seeds);
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
function routeClientSources(routes) {
|
|
3120
|
+
const files = new Set;
|
|
3121
|
+
for (const route of routes) {
|
|
3122
|
+
if (route.client)
|
|
3123
|
+
files.add(route.file);
|
|
3124
|
+
for (const reference of route.clientReferences)
|
|
3125
|
+
files.add(reference.file);
|
|
3126
|
+
}
|
|
3127
|
+
return files;
|
|
3128
|
+
}
|
|
3129
|
+
function assignClientEntryImports(entries, metafile, outDir, renames, prebuilt) {
|
|
3130
|
+
const outputs = metafile.outputs;
|
|
3131
|
+
const byBasename = new Map;
|
|
3132
|
+
for (const item of Object.keys(outputs)) {
|
|
3133
|
+
const basename = path5.basename(item);
|
|
3134
|
+
if (!byBasename.has(basename))
|
|
3135
|
+
byBasename.set(basename, item);
|
|
3136
|
+
}
|
|
3137
|
+
for (const { route, entryName } of entries) {
|
|
3138
|
+
const entryOutput = byBasename.get(`${entryName}.js`);
|
|
3139
|
+
if (!entryOutput)
|
|
3140
|
+
continue;
|
|
3141
|
+
const staticImports = new Set;
|
|
3142
|
+
const dynamicImports = new Set;
|
|
3143
|
+
const visited = new Set([`${entryOutput}:static`]);
|
|
3144
|
+
const queue = [{ output: entryOutput, dynamic: false }];
|
|
3145
|
+
for (let head = 0;head < queue.length; head += 1) {
|
|
3146
|
+
const current = queue[head];
|
|
3147
|
+
if (!current)
|
|
3148
|
+
continue;
|
|
3149
|
+
for (const imported of outputs[current.output]?.imports ?? []) {
|
|
3150
|
+
if (imported.kind !== "import-statement" && imported.kind !== "dynamic-import")
|
|
3151
|
+
continue;
|
|
3152
|
+
const dynamic = current.dynamic || imported.kind === "dynamic-import";
|
|
3153
|
+
const visitKey = `${imported.path}:${dynamic ? "dynamic" : "static"}`;
|
|
3154
|
+
if (visited.has(visitKey))
|
|
3155
|
+
continue;
|
|
3156
|
+
visited.add(visitKey);
|
|
3157
|
+
if (imported.external) {
|
|
3158
|
+
if (!prebuilt || imported.path !== prebuiltModuleUrl(prebuilt.runtime))
|
|
3159
|
+
continue;
|
|
3160
|
+
const assets = prebuiltAssets(prebuilt.runtime);
|
|
3161
|
+
for (const asset of assets.static)
|
|
3162
|
+
(dynamic ? dynamicImports : staticImports).add(asset);
|
|
3163
|
+
for (const asset of assets.dynamic)
|
|
3164
|
+
dynamicImports.add(asset);
|
|
3165
|
+
continue;
|
|
3166
|
+
}
|
|
3167
|
+
queue.push({ output: imported.path, dynamic });
|
|
3168
|
+
const basename = path5.basename(imported.path);
|
|
3169
|
+
const relative = path5.relative(outDir, path5.resolve(imported.path));
|
|
3170
|
+
const publicRelative = path5.join("assets", path5.dirname(relative), renames.get(basename) ?? basename).split(path5.sep).join("/");
|
|
3171
|
+
(dynamic ? dynamicImports : staticImports).add(publicRelative);
|
|
3172
|
+
}
|
|
3173
|
+
}
|
|
3174
|
+
if (staticImports.size > 0)
|
|
3175
|
+
route.clientEntryImports = [...staticImports];
|
|
3176
|
+
for (const asset of staticImports)
|
|
3177
|
+
dynamicImports.delete(asset);
|
|
3178
|
+
if (dynamicImports.size > 0)
|
|
3179
|
+
route.clientDynamicImports = [...dynamicImports];
|
|
3180
|
+
}
|
|
3181
|
+
}
|
|
3182
|
+
function reportClientBundleSizes(entries, metafile, outDir) {
|
|
3183
|
+
const log = createVerboseLogger(true, "client");
|
|
3184
|
+
const outputs = metafile.outputs;
|
|
3185
|
+
const rows = entries.map(({ route, entryName }) => {
|
|
3186
|
+
const key = `${path5.relative(process.cwd(), path5.join(outDir, `${entryName}.js`))}`;
|
|
3187
|
+
const output = outputs[key] ? key : Object.keys(outputs).find((item) => path5.basename(item) === `${entryName}.js`);
|
|
3188
|
+
const bytes = output ? reachableOutputBytes(output, outputs) : 0;
|
|
3189
|
+
return { route: route.route, bytes };
|
|
3190
|
+
}).sort((a, b) => b.bytes - a.bytes);
|
|
3191
|
+
for (const row of rows) {
|
|
3192
|
+
log.log(`${row.route} ${dim(`\u2192 ${formatBytes(row.bytes)}`)}`);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
function reachableOutputBytes(entry, outputs) {
|
|
3196
|
+
const visited = new Set;
|
|
3197
|
+
const queue = [entry];
|
|
3198
|
+
let bytes = 0;
|
|
3199
|
+
while (queue.length > 0) {
|
|
3200
|
+
const current = queue.shift();
|
|
3201
|
+
if (!current || visited.has(current))
|
|
3202
|
+
continue;
|
|
3203
|
+
visited.add(current);
|
|
3204
|
+
const output = outputs[current];
|
|
3205
|
+
if (!output)
|
|
3206
|
+
continue;
|
|
3207
|
+
bytes += output.bytes;
|
|
3208
|
+
for (const imported of output.imports) {
|
|
3209
|
+
if (imported.kind === "import-statement" || imported.kind === "dynamic-import") {
|
|
3210
|
+
queue.push(imported.path);
|
|
3211
|
+
}
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
return bytes;
|
|
3215
|
+
}
|
|
3216
|
+
function formatBytes(bytes) {
|
|
3217
|
+
if (bytes < 1024)
|
|
3218
|
+
return `${bytes} B`;
|
|
3219
|
+
const units = ["KB", "MB"];
|
|
3220
|
+
let value = bytes / 1024;
|
|
3221
|
+
let unitIndex = 0;
|
|
3222
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
3223
|
+
value /= 1024;
|
|
3224
|
+
unitIndex += 1;
|
|
3225
|
+
}
|
|
3226
|
+
return `${value >= 10 ? value.toFixed(1) : value.toFixed(2)} ${units[unitIndex]}`;
|
|
3227
|
+
}
|
|
3228
|
+
function baseClientBuildOptions(config, dev = false) {
|
|
3229
|
+
const extensions = getBundlerExtensions();
|
|
3230
|
+
const pure = extensions.clientPureFunctions(config);
|
|
3231
|
+
const inject = extensions.clientInjects();
|
|
3232
|
+
return {
|
|
3233
|
+
...inject.length > 0 ? { inject } : {},
|
|
3234
|
+
chunkNames: "chunks/[name]-[hash]",
|
|
3235
|
+
bundle: true,
|
|
3236
|
+
format: "esm",
|
|
3237
|
+
splitting: true,
|
|
3238
|
+
target: "es2022",
|
|
3239
|
+
jsx: "automatic",
|
|
3240
|
+
jsxImportSource: "preact",
|
|
3241
|
+
minify: !dev,
|
|
3242
|
+
...pure.length > 0 ? { pure } : {},
|
|
3243
|
+
sourcemap: !dev && config.productionBrowserSourceMaps === true ? "linked" : false,
|
|
3244
|
+
conditions: ["browser", "style"],
|
|
3245
|
+
define: {
|
|
3246
|
+
...publicEnvDefines(),
|
|
3247
|
+
...extensions.clientDefines(),
|
|
3248
|
+
"process.browser": "true",
|
|
3249
|
+
...deploymentIdDefine()
|
|
3250
|
+
},
|
|
3251
|
+
logOverride: { "suspicious-logical-operator": "silent" },
|
|
3252
|
+
loader: {
|
|
3253
|
+
".woff": "file",
|
|
3254
|
+
".woff2": "file",
|
|
3255
|
+
".ttf": "file",
|
|
3256
|
+
".otf": "file",
|
|
3257
|
+
".eot": "file"
|
|
3258
|
+
}
|
|
3259
|
+
};
|
|
3260
|
+
}
|
|
3261
|
+
function deploymentIdDefine() {
|
|
3262
|
+
const deploymentId = process.env.NEXT_DEPLOYMENT_ID;
|
|
3263
|
+
return deploymentId === undefined ? {} : { "process.env.NEXT_DEPLOYMENT_ID": JSON.stringify(deploymentId) };
|
|
3264
|
+
}
|
|
3265
|
+
function instrumentedPlugins(plugins) {
|
|
3266
|
+
if (!clientProfile.enabled)
|
|
3267
|
+
return plugins;
|
|
3268
|
+
return plugins.map((plugin) => ({
|
|
3269
|
+
name: plugin.name,
|
|
3270
|
+
setup(build4) {
|
|
3271
|
+
const wrap = (kind, register) => (options, callback) => register(options, async (args) => {
|
|
3272
|
+
clientProfile.count(`${plugin.name}:${kind}#`);
|
|
3273
|
+
const start = performance.now();
|
|
3274
|
+
try {
|
|
3275
|
+
return await callback(args);
|
|
3276
|
+
} finally {
|
|
3277
|
+
clientProfile.count(`${plugin.name}:${kind}ms`, performance.now() - start);
|
|
3278
|
+
}
|
|
3279
|
+
});
|
|
3280
|
+
return plugin.setup({
|
|
3281
|
+
...build4,
|
|
3282
|
+
onResolve: wrap("resolve", build4.onResolve.bind(build4)),
|
|
3283
|
+
onLoad: wrap("load", build4.onLoad.bind(build4))
|
|
3284
|
+
});
|
|
3285
|
+
}
|
|
3286
|
+
}));
|
|
3287
|
+
}
|
|
3288
|
+
function coalesceResolveHooks(plugins, name) {
|
|
3289
|
+
if (process.env.PNEXT_CLIENT_COALESCE === "0")
|
|
3290
|
+
return plugins;
|
|
3291
|
+
return [
|
|
3292
|
+
{
|
|
3293
|
+
name,
|
|
3294
|
+
async setup(build4) {
|
|
3295
|
+
const registered = [];
|
|
3296
|
+
let sealed = false;
|
|
3297
|
+
const dispatch = async (args, skip) => {
|
|
3298
|
+
for (const entry of registered) {
|
|
3299
|
+
if (entry.plugin === skip)
|
|
3300
|
+
continue;
|
|
3301
|
+
if (entry.namespace !== undefined && entry.namespace !== args.namespace)
|
|
3302
|
+
continue;
|
|
3303
|
+
if (!entry.filter.test(args.path))
|
|
3304
|
+
continue;
|
|
3305
|
+
const result = await entry.callback(args);
|
|
3306
|
+
if (result == null)
|
|
3307
|
+
continue;
|
|
3308
|
+
for (const message of [...result.errors ?? [], ...result.warnings ?? []]) {
|
|
3309
|
+
message.pluginName ??= entry.plugin;
|
|
3310
|
+
}
|
|
3311
|
+
return result;
|
|
3312
|
+
}
|
|
3313
|
+
return;
|
|
3314
|
+
};
|
|
3315
|
+
for (const plugin of plugins) {
|
|
3316
|
+
const proxy = {
|
|
3317
|
+
...build4,
|
|
3318
|
+
onResolve(options, callback) {
|
|
3319
|
+
if (sealed)
|
|
3320
|
+
return build4.onResolve(options, callback);
|
|
3321
|
+
registered.push({
|
|
3322
|
+
plugin: plugin.name,
|
|
3323
|
+
filter: new RegExp(options.filter.source, options.filter.flags.replace(/[gy]/g, "")),
|
|
3324
|
+
namespace: options.namespace,
|
|
3325
|
+
callback
|
|
3326
|
+
});
|
|
3327
|
+
},
|
|
3328
|
+
resolve: (specifier, options = {}) => dispatch({
|
|
3329
|
+
path: specifier,
|
|
3330
|
+
importer: options.importer ?? "",
|
|
3331
|
+
namespace: options.namespace ?? "",
|
|
3332
|
+
resolveDir: options.resolveDir ?? "",
|
|
3333
|
+
kind: options.kind ?? "import-statement",
|
|
3334
|
+
pluginData: options.pluginData,
|
|
3335
|
+
with: options.with ?? {}
|
|
3336
|
+
}, plugin.name).then((result) => result && resolveHookSettled(result) ? resolvedFromHook(specifier, result) : build4.resolve(specifier, { ...options, pluginName: name }))
|
|
3337
|
+
};
|
|
3338
|
+
await plugin.setup(proxy);
|
|
3339
|
+
}
|
|
3340
|
+
sealed = true;
|
|
3341
|
+
if (registered.length === 0)
|
|
3342
|
+
return;
|
|
3343
|
+
const filter = new RegExp(registered.map((entry) => `(?:${entry.filter.source})`).join("|"));
|
|
3344
|
+
build4.onResolve({ filter }, (args) => dispatch(args));
|
|
3345
|
+
}
|
|
3346
|
+
}
|
|
3347
|
+
];
|
|
3348
|
+
}
|
|
3349
|
+
function resolvedFromHook(specifier, result) {
|
|
3350
|
+
const resolved = result.path ?? (result.external ? specifier : "");
|
|
3351
|
+
return {
|
|
3352
|
+
errors: result.errors ?? [],
|
|
3353
|
+
warnings: result.warnings ?? [],
|
|
3354
|
+
path: resolved,
|
|
3355
|
+
external: result.external ?? false,
|
|
3356
|
+
sideEffects: result.sideEffects ?? true,
|
|
3357
|
+
namespace: result.namespace ?? (result.path ? "file" : ""),
|
|
3358
|
+
suffix: result.suffix ?? "",
|
|
3359
|
+
pluginData: result.pluginData
|
|
3360
|
+
};
|
|
3361
|
+
}
|
|
3362
|
+
function resolveHookSettled(result) {
|
|
3363
|
+
return result.path !== undefined || result.external !== undefined || (result.errors?.length ?? 0) > 0 || (result.warnings?.length ?? 0) > 0;
|
|
3364
|
+
}
|
|
3365
|
+
function clientBuildPlugins(config, pipeline, extra = [], reactLite = false) {
|
|
3366
|
+
return coalesceResolveHooks(instrumentedPlugins([
|
|
3367
|
+
serverOnlyClientImportPlugin(),
|
|
3368
|
+
...extra,
|
|
3369
|
+
...getBundlerExtensions().clientEsbuildPlugins(config),
|
|
3370
|
+
linkedPackageClientResolvePlugin(config),
|
|
3371
|
+
clientStaticAssetPlugin(config),
|
|
3372
|
+
pipeline.plugin(),
|
|
3373
|
+
importAliasPlugin(config, reactLite),
|
|
3374
|
+
cssModuleClientPlugin()
|
|
3375
|
+
]), "pnext-client-resolve-chain");
|
|
3376
|
+
}
|
|
3377
|
+
function importAliasPlugin(config, reactLite = false) {
|
|
3378
|
+
const aliases = {
|
|
3379
|
+
...frameworkRuntimeAliasEntries(),
|
|
3380
|
+
...getImportAliasExtensions().aliases(config, "client")
|
|
3381
|
+
};
|
|
3382
|
+
if (reactLite && aliases.react) {
|
|
3383
|
+
aliases.react = path5.resolve(__PNEXT_PACKAGE_ROOT__ + "/src/client", "..", "compat", "react", "client-lite.ts");
|
|
3384
|
+
}
|
|
3385
|
+
const specifiers = Object.keys(aliases);
|
|
3386
|
+
return {
|
|
3387
|
+
name: "pnext-import-alias",
|
|
3388
|
+
setup(build4) {
|
|
3389
|
+
build4.onResolve({ filter: /^(?:react(?:-dom)?|next)(?:\/|$)/ }, (args) => {
|
|
3390
|
+
if (aliases[args.path])
|
|
3391
|
+
return;
|
|
3392
|
+
const message = getImportAliasExtensions().missingImportError(config, args.path);
|
|
3393
|
+
return message ? { errors: [{ text: message }] } : undefined;
|
|
3394
|
+
});
|
|
3395
|
+
if (specifiers.length === 0)
|
|
3396
|
+
return;
|
|
3397
|
+
build4.onResolve({ filter: new RegExp(`^(${specifiers.map(escapeRegex).join("|")})$`) }, async (args) => {
|
|
3398
|
+
const target = aliases[args.path];
|
|
3399
|
+
if (!target)
|
|
3400
|
+
return;
|
|
3401
|
+
if (path5.isAbsolute(target))
|
|
3402
|
+
return { path: target };
|
|
3403
|
+
return build4.resolve(target, {
|
|
3404
|
+
kind: args.kind,
|
|
3405
|
+
importer: args.importer,
|
|
3406
|
+
namespace: args.namespace,
|
|
3407
|
+
resolveDir: args.resolveDir
|
|
3408
|
+
});
|
|
3409
|
+
});
|
|
3410
|
+
}
|
|
3411
|
+
};
|
|
3412
|
+
}
|
|
3413
|
+
var frameworkAliasedSpecifier = /^(?:preact|react|react-dom|next)(?:\/|$)/;
|
|
3414
|
+
function linkedPackageClientResolvePlugin(config) {
|
|
3415
|
+
const compatPreact = path5.resolve(__PNEXT_PACKAGE_ROOT__ + "/src/client", "..", "compat", "react", "preact.ts");
|
|
3416
|
+
const compatReactClient = path5.resolve(__PNEXT_PACKAGE_ROOT__ + "/src/client", "..", "compat", "react", "client.ts");
|
|
3417
|
+
return {
|
|
3418
|
+
name: "pnext-linked-package-client-resolve",
|
|
3419
|
+
setup(build4) {
|
|
3420
|
+
build4.onResolve({ filter: /^\.\/preact$/ }, (args) => {
|
|
3421
|
+
if (path5.resolve(args.importer) === compatReactClient)
|
|
3422
|
+
return { path: compatPreact };
|
|
3423
|
+
const hasRealSibling = [".ts", ".tsx", ".js", ".mjs", ".jsx"].some((ext) => existsSync3(path5.join(args.resolveDir, `preact${ext}`)));
|
|
3424
|
+
if (hasRealSibling)
|
|
3425
|
+
return;
|
|
3426
|
+
return { path: compatPreact };
|
|
3427
|
+
});
|
|
3428
|
+
const linked = new Map;
|
|
3429
|
+
build4.onResolve({ filter: /^[^./#][^:]*$/ }, (args) => {
|
|
3430
|
+
if (frameworkAliasedSpecifier.test(args.path))
|
|
3431
|
+
return;
|
|
3432
|
+
const dir = args.resolveDir || config.root;
|
|
3433
|
+
const key = `${dir}\x00${args.path}`;
|
|
3434
|
+
let resolved = linked.get(key);
|
|
3435
|
+
if (resolved === undefined && !linked.has(key)) {
|
|
3436
|
+
resolved = resolveLinkedPackageSpecifier(config.root, path5.join(dir, "pnext-resolve.ts"), args.path, ["browser", "style", "import", "default"]);
|
|
3437
|
+
linked.set(key, resolved);
|
|
3438
|
+
}
|
|
3439
|
+
return resolved ? { path: resolved } : undefined;
|
|
3440
|
+
});
|
|
3441
|
+
}
|
|
3442
|
+
};
|
|
3443
|
+
}
|
|
3444
|
+
var staticImageAssetFilter = /\.(?:png|jpe?g|gif|webp|avif|svg|ico|bmp)(?:$|[?#])/;
|
|
3445
|
+
function clientStaticAssetPlugin(config) {
|
|
3446
|
+
return {
|
|
3447
|
+
name: "pnext-client-static-assets",
|
|
3448
|
+
setup(build4) {
|
|
3449
|
+
build4.onResolve({ filter: staticImageAssetFilter }, (args) => {
|
|
3450
|
+
if (getAssetExtensions().hasLoaderRuleFor(args.path)) {
|
|
3451
|
+
return;
|
|
3452
|
+
}
|
|
3453
|
+
return {
|
|
3454
|
+
path: resolveAssetPath(args.path, args.resolveDir),
|
|
3455
|
+
namespace: "pnext-client-static-asset"
|
|
3456
|
+
};
|
|
3457
|
+
});
|
|
3458
|
+
build4.onLoad({ filter: /.*/, namespace: "pnext-client-static-asset" }, async (args) => ({
|
|
3459
|
+
contents: await staticImageModuleSource(config, args.path),
|
|
3460
|
+
loader: "js"
|
|
3461
|
+
}));
|
|
3462
|
+
}
|
|
3463
|
+
};
|
|
3464
|
+
}
|
|
3465
|
+
function resolveAssetPath(specifier, resolveDir) {
|
|
3466
|
+
const { sourcePath, hash } = splitAssetHash(specifier);
|
|
3467
|
+
const resolved = path5.isAbsolute(sourcePath) ? sourcePath : path5.resolve(resolveDir, sourcePath);
|
|
3468
|
+
return `${resolved}${hash}`;
|
|
3469
|
+
}
|
|
3470
|
+
function splitAssetHash(specifier) {
|
|
3471
|
+
const index = specifier.search(/[?#]/);
|
|
3472
|
+
return index === -1 ? { sourcePath: specifier, hash: "" } : { sourcePath: specifier.slice(0, index), hash: specifier.slice(index) };
|
|
3473
|
+
}
|
|
3474
|
+
async function staticImageModuleSource(config, file) {
|
|
3475
|
+
const { sourcePath } = splitAssetHash(file);
|
|
3476
|
+
const bytes = new Uint8Array(await readFile(sourcePath));
|
|
3477
|
+
const emitted = [];
|
|
3478
|
+
const emit = (relative) => {
|
|
3479
|
+
emitted.push(relative);
|
|
3480
|
+
return `/${relative}`;
|
|
3481
|
+
};
|
|
3482
|
+
const compat = await getAssetExtensions().staticAssetModule({ sourcePath, bytes, emit });
|
|
3483
|
+
const source = compat ?? coreStaticAssetModule(sourcePath, bytes, emit);
|
|
3484
|
+
for (const relative of emitted) {
|
|
3485
|
+
const target = path5.join(config.outPath, "public", ...relative.split("/"));
|
|
3486
|
+
await mkdir(path5.dirname(target), { recursive: true });
|
|
3487
|
+
if (!existsSync3(target))
|
|
3488
|
+
await copyFile(sourcePath, target);
|
|
3489
|
+
}
|
|
3490
|
+
return source;
|
|
3491
|
+
}
|
|
3492
|
+
function coreStaticAssetModule(sourcePath, bytes, emit) {
|
|
3493
|
+
const ext = path5.extname(sourcePath).toLowerCase() || ".bin";
|
|
3494
|
+
const hash = createHash3("sha256").update(bytes).digest("hex").slice(0, 8);
|
|
3495
|
+
const base = path5.basename(sourcePath, path5.extname(sourcePath)).replace(/[^A-Za-z0-9_-]+/g, "-");
|
|
3496
|
+
const relative = path5.posix.join("_next", "static", "media", `${base}.${hash}${ext}`);
|
|
3497
|
+
const src = emit(relative);
|
|
3498
|
+
return `const src = ${JSON.stringify(src)};
|
|
3499
|
+
export default src;
|
|
3500
|
+
export { src };
|
|
3501
|
+
`;
|
|
3502
|
+
}
|
|
3503
|
+
function createClientSourcePipeline(config, dev = false, routeId) {
|
|
3504
|
+
const sourceRewrite = nextCompatEnabled(config);
|
|
3505
|
+
const compiler = getCompatModeExtensions().reactCompilerOptions(config);
|
|
3506
|
+
const asyncPre = hasClientSourceAsyncPreTransforms();
|
|
3507
|
+
const roots = clientSourceRoots(config).map((root) => escapeRegex(root)).join("|");
|
|
3508
|
+
const sep = escapeRegex(path5.sep);
|
|
3509
|
+
const filter = new RegExp(`^(?:${roots})(?:${sep}${nonSegmentPattern("node_modules", sep)})*${sep}[^${sep}]*\\.(?:[cm]?[jt]sx?)$`);
|
|
3510
|
+
const transpiled = sourceRewrite ? transpiledPackageFilter(roots) : undefined;
|
|
3511
|
+
const prepared = new Map;
|
|
3512
|
+
const deferredRefs = new Map;
|
|
3513
|
+
const loaded = new Map;
|
|
3514
|
+
function prepare(resolved) {
|
|
3515
|
+
const existing = prepared.get(resolved);
|
|
3516
|
+
if (existing)
|
|
3517
|
+
return existing;
|
|
3518
|
+
const pending = runPipeline(resolved);
|
|
3519
|
+
prepared.set(resolved, pending);
|
|
3520
|
+
return pending;
|
|
3521
|
+
}
|
|
3522
|
+
async function runPipeline(resolved) {
|
|
3523
|
+
const inNodeModules = resolved.split(path5.sep).includes("node_modules");
|
|
3524
|
+
if (inNodeModules && (!sourceRewrite || !isTranspiledPackageFile(resolved)))
|
|
3525
|
+
return;
|
|
3526
|
+
clientProfile.count("loadFiles");
|
|
3527
|
+
const original = await clientProfile.timeAsync("read", () => readSourceText(resolved));
|
|
3528
|
+
let contents = original;
|
|
3529
|
+
if (asyncPre) {
|
|
3530
|
+
contents = await applyClientSourceAsyncPreTransforms(contents, resolved, config.root);
|
|
3531
|
+
}
|
|
3532
|
+
contents = clientProfile.time("chain", () => applyClientSourceTransforms(contents, resolved, config.root));
|
|
3533
|
+
contents = clientProfile.time("dynamic", () => rewriteLiteralDynamicCalls(contents, resolved));
|
|
3534
|
+
if (dev && devDynamicSplitEnabled()) {
|
|
3535
|
+
contents = rewriteDeferredDynamicImports(contents, resolved, (specifier) => resolveImport(rootFromFile(resolved), resolved, specifier, config.workspaceRoot), (target) => {
|
|
3536
|
+
const id = clientReferenceId(target.file, target.exportName);
|
|
3537
|
+
const ref = { ...target, routeId };
|
|
3538
|
+
registerDeferredDynamicRef(id, ref);
|
|
3539
|
+
deferredRefs.set(id, ref);
|
|
3540
|
+
return deferredDynamicChunkHref({ id }, routeId);
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
if (compiler && !inNodeModules && shouldReactCompile(contents, resolved)) {
|
|
3544
|
+
contents = await clientProfile.timeAsync("reactCompiler", () => reactCompiled(resolved, contents, compiler));
|
|
3545
|
+
}
|
|
3546
|
+
loaded.set(resolved, contents);
|
|
3547
|
+
if (contents === original && !/\.m?js$/.test(resolved))
|
|
3548
|
+
return;
|
|
3549
|
+
return { contents, loader: clientSourceLoader(resolved) };
|
|
3550
|
+
}
|
|
3551
|
+
return {
|
|
3552
|
+
sourceOf(file) {
|
|
3553
|
+
return loaded.get(file);
|
|
3554
|
+
},
|
|
3555
|
+
deferredRefs() {
|
|
3556
|
+
return deferredRefs;
|
|
3557
|
+
},
|
|
3558
|
+
warmRoutes(routes) {
|
|
3559
|
+
this.warm(routeClientSources(routes));
|
|
3560
|
+
},
|
|
3561
|
+
warm(files) {
|
|
3562
|
+
for (const file of files) {
|
|
3563
|
+
const resolved = path5.resolve(file);
|
|
3564
|
+
if (!filter.test(resolved) && !transpiled?.test(resolved))
|
|
3565
|
+
continue;
|
|
3566
|
+
prepare(resolved).catch(() => {
|
|
3567
|
+
return;
|
|
3568
|
+
});
|
|
3569
|
+
}
|
|
3570
|
+
},
|
|
3571
|
+
plugin() {
|
|
3572
|
+
return {
|
|
3573
|
+
name: "pnext-client-source",
|
|
3574
|
+
setup(build4) {
|
|
3575
|
+
build4.onLoad({ filter, namespace: "file" }, (args) => prepare(path5.resolve(args.path)));
|
|
3576
|
+
if (transpiled) {
|
|
3577
|
+
build4.onLoad({ filter: transpiled, namespace: "file" }, (args) => prepare(path5.resolve(args.path)));
|
|
3578
|
+
}
|
|
3579
|
+
}
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
};
|
|
3583
|
+
}
|
|
3584
|
+
function startClientSources(config) {
|
|
3585
|
+
conventionCache.clear();
|
|
3586
|
+
return createClientSourcePipeline(config);
|
|
3587
|
+
}
|
|
3588
|
+
var reactCompiledCache = new Map;
|
|
3589
|
+
function reactCompiled(file, source, options) {
|
|
3590
|
+
const hash = Bun.hash.wyhash(`${options.target}\x00${source}`);
|
|
3591
|
+
const cached = reactCompiledCache.get(file);
|
|
3592
|
+
if (cached?.hash === hash)
|
|
3593
|
+
return cached.code;
|
|
3594
|
+
const code = transformReactCompiler(source, file, options);
|
|
3595
|
+
reactCompiledCache.set(file, { hash, code });
|
|
3596
|
+
return code;
|
|
3597
|
+
}
|
|
3598
|
+
function clientSourceRoots(config) {
|
|
3599
|
+
const roots = new Set;
|
|
3600
|
+
for (const root of [config.root, config.workspaceRoot]) {
|
|
3601
|
+
const resolved = path5.resolve(root);
|
|
3602
|
+
roots.add(resolved);
|
|
3603
|
+
try {
|
|
3604
|
+
roots.add(realpathSync.native(resolved));
|
|
3605
|
+
} catch {}
|
|
3606
|
+
}
|
|
3607
|
+
return [...roots];
|
|
3608
|
+
}
|
|
3609
|
+
function clientSourceLoader(file) {
|
|
3610
|
+
if (file.endsWith(".tsx"))
|
|
3611
|
+
return "tsx";
|
|
3612
|
+
if (/\.[cm]?ts$/.test(file))
|
|
3613
|
+
return "ts";
|
|
3614
|
+
return file.endsWith(".cjs") ? "js" : "jsx";
|
|
3615
|
+
}
|
|
3616
|
+
function nonSegmentPattern(word, sep) {
|
|
3617
|
+
const parts = [`${word}[^${sep}]+`];
|
|
3618
|
+
for (let index = 0;index < word.length; index += 1) {
|
|
3619
|
+
const prefix = word.slice(0, index);
|
|
3620
|
+
parts.push(prefix, `${prefix}[^${sep}${word[index]}][^${sep}]*`);
|
|
3621
|
+
}
|
|
3622
|
+
return `(?:${parts.join("|")})`;
|
|
3623
|
+
}
|
|
3624
|
+
function transpiledPackageFilter(roots) {
|
|
3625
|
+
const sep = escapeRegex(path5.sep);
|
|
3626
|
+
const names = getExternalPackagePolicy().transpiled?.();
|
|
3627
|
+
if (names?.length === 0)
|
|
3628
|
+
return;
|
|
3629
|
+
const packages = names ? `(?:${names.map((name) => escapeRegex(name).split("/").join(sep)).join("|")})${sep}` : "";
|
|
3630
|
+
return new RegExp(`^(?:${roots}).*${sep}node_modules${sep}${packages}.*\\.(?:[cm]?[jt]sx?)$`);
|
|
3631
|
+
}
|
|
3632
|
+
function isTranspiledPackageFile(file) {
|
|
3633
|
+
const parts = file.split(path5.sep);
|
|
3634
|
+
const nodeModules = parts.lastIndexOf("node_modules");
|
|
3635
|
+
if (nodeModules === -1)
|
|
3636
|
+
return false;
|
|
3637
|
+
const first = parts[nodeModules + 1];
|
|
3638
|
+
if (!first)
|
|
3639
|
+
return false;
|
|
3640
|
+
const name = first.startsWith("@") ? `${first}/${parts[nodeModules + 2] ?? ""}` : first;
|
|
3641
|
+
return getExternalPackagePolicy().transpile(name);
|
|
3642
|
+
}
|
|
3643
|
+
function virtualEntryPath(routeId) {
|
|
3644
|
+
return `${virtualEntryNamespace}:${routeId}`;
|
|
3645
|
+
}
|
|
3646
|
+
function virtualEntryPlugin(entries) {
|
|
3647
|
+
const sources = new Map(entries.map((entry) => [entry.route.id, entry.source]));
|
|
3648
|
+
return {
|
|
3649
|
+
name: "pnext-virtual-client-entry",
|
|
3650
|
+
setup(build4) {
|
|
3651
|
+
build4.onResolve({ filter: new RegExp(`^${escapeRegex(virtualEntryNamespace)}:`) }, (args) => ({
|
|
3652
|
+
path: args.path.slice(virtualEntryNamespace.length + 1),
|
|
3653
|
+
namespace: virtualEntryNamespace
|
|
3654
|
+
}));
|
|
3655
|
+
build4.onLoad({ filter: /.*/, namespace: virtualEntryNamespace }, (args) => {
|
|
3656
|
+
const contents = sources.get(args.path);
|
|
3657
|
+
if (contents === undefined)
|
|
3658
|
+
return;
|
|
3659
|
+
return { contents, loader: "ts", resolveDir: process.cwd() };
|
|
3660
|
+
});
|
|
3661
|
+
}
|
|
3662
|
+
};
|
|
3663
|
+
}
|
|
3664
|
+
function clientRuntimePlugin(facts) {
|
|
3665
|
+
return {
|
|
3666
|
+
name: "pnext-client-runtime",
|
|
3667
|
+
setup(build4) {
|
|
3668
|
+
build4.onResolve({ filter: new RegExp(`^${escapeRegex(CLIENT_RUNTIME_MODULE)}$`) }, () => ({
|
|
3669
|
+
path: CLIENT_RUNTIME_MODULE,
|
|
3670
|
+
namespace: CLIENT_RUNTIME_MODULE
|
|
3671
|
+
}));
|
|
3672
|
+
build4.onLoad({ filter: /.*/, namespace: CLIENT_RUNTIME_MODULE }, () => ({
|
|
3673
|
+
contents: clientRuntimeSource(facts),
|
|
3674
|
+
loader: "ts",
|
|
3675
|
+
resolveDir: process.cwd()
|
|
3676
|
+
}));
|
|
3677
|
+
}
|
|
3678
|
+
};
|
|
3679
|
+
}
|
|
3680
|
+
async function profileClientBuild(route, dev, task) {
|
|
3681
|
+
if (!traceEnabled("server"))
|
|
3682
|
+
return task();
|
|
3683
|
+
const start = performance.now();
|
|
3684
|
+
try {
|
|
3685
|
+
return await task();
|
|
3686
|
+
} finally {
|
|
3687
|
+
const mode = dev ? "dev" : "prod";
|
|
3688
|
+
console.log(`dev-profile client build ${mode} ${route.route} in ${formatDuration(performance.now() - start)}`);
|
|
3689
|
+
}
|
|
3690
|
+
}
|
|
3691
|
+
function serverOnlyClientImportPlugin() {
|
|
3692
|
+
return {
|
|
3693
|
+
name: "pnext-server-only-client-import",
|
|
3694
|
+
setup(build4) {
|
|
3695
|
+
build4.onResolve({ filter: /^(next|next\/headers|next\/font|next\/font\/google|next\/font\/local)$/ }, (args) => ({
|
|
3696
|
+
errors: [
|
|
3697
|
+
{
|
|
3698
|
+
text: `${args.path} is server-only and cannot be imported from Client Components.`
|
|
3699
|
+
}
|
|
3700
|
+
]
|
|
3701
|
+
}));
|
|
3702
|
+
}
|
|
3703
|
+
};
|
|
3704
|
+
}
|
|
3705
|
+
var traceSourceExtensions = [".tsx", ".ts", ".jsx", ".js", ".mts", ".cts", ".module.css", ".css"];
|
|
3706
|
+
function withClientImportTrace(error, config, route, entrySource) {
|
|
3707
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3708
|
+
const trace = clientImportTrace(config, route, entrySource, error);
|
|
3709
|
+
if (!trace)
|
|
3710
|
+
return error;
|
|
3711
|
+
const next = new Error(`${message}
|
|
3712
|
+
|
|
3713
|
+
${trace}`);
|
|
3714
|
+
if (error instanceof Error) {
|
|
3715
|
+
next.name = error.name;
|
|
3716
|
+
next.stack = error.stack ? `${error.stack}
|
|
3717
|
+
|
|
3718
|
+
${trace}` : next.stack;
|
|
3719
|
+
Object.assign(next, error);
|
|
3720
|
+
}
|
|
3721
|
+
return next;
|
|
3722
|
+
}
|
|
3723
|
+
function clientImportTrace(config, route, entrySource, error) {
|
|
3724
|
+
const unresolved = unresolvedImportFromError(error);
|
|
3725
|
+
if (!unresolved)
|
|
3726
|
+
return;
|
|
3727
|
+
const entryFile = `${route.id}.ts`;
|
|
3728
|
+
const failedFile = unresolved.file ? resolveTraceFile(unresolved.file) : undefined;
|
|
3729
|
+
const parents = new Map;
|
|
3730
|
+
const visited = new Set;
|
|
3731
|
+
const queue = [{ file: entryFile, source: entrySource }];
|
|
3732
|
+
let unresolvedImporter;
|
|
3733
|
+
while (queue.length > 0) {
|
|
3734
|
+
const current = queue.shift();
|
|
3735
|
+
if (!current || visited.has(current.file))
|
|
3736
|
+
continue;
|
|
3737
|
+
visited.add(current.file);
|
|
3738
|
+
if (visited.size > 2000)
|
|
3739
|
+
break;
|
|
3740
|
+
for (const specifier of localImports(current.file, current.source)) {
|
|
3741
|
+
const resolved = resolveTraceImport(config.root, current.file, specifier);
|
|
3742
|
+
if (!resolved) {
|
|
3743
|
+
if (specifier === unresolved.specifier && (!failedFile || sameFile(current.file, failedFile))) {
|
|
3744
|
+
unresolvedImporter = current.file;
|
|
3745
|
+
queue.length = 0;
|
|
3746
|
+
break;
|
|
3747
|
+
}
|
|
3748
|
+
continue;
|
|
3749
|
+
}
|
|
3750
|
+
if (!parents.has(resolved))
|
|
3751
|
+
parents.set(resolved, { file: current.file, specifier });
|
|
3752
|
+
if (failedFile && sameFile(resolved, failedFile)) {
|
|
3753
|
+
unresolvedImporter = resolved;
|
|
3754
|
+
queue.length = 0;
|
|
3755
|
+
break;
|
|
3756
|
+
}
|
|
3757
|
+
if (!visited.has(resolved) && !isAssetLike(resolved)) {
|
|
3758
|
+
queue.push({ file: resolved, source: readTextSyncSafe(resolved) });
|
|
3759
|
+
}
|
|
3760
|
+
}
|
|
3761
|
+
}
|
|
3762
|
+
if (!unresolvedImporter)
|
|
3763
|
+
return;
|
|
3764
|
+
const trace = tracePath(entryFile, unresolvedImporter, parents);
|
|
3765
|
+
if (trace.length === 0)
|
|
3766
|
+
return;
|
|
3767
|
+
return [
|
|
3768
|
+
`pnext client import trace for route ${route.route}:`,
|
|
3769
|
+
...trace.map((file, index) => `${index === 0 ? " " : " -> "}${displayTraceFile(config, file)}`),
|
|
3770
|
+
` -> ${unresolved.specifier} (unresolved in browser client bundle)`
|
|
3771
|
+
].join(`
|
|
3772
|
+
`);
|
|
3773
|
+
}
|
|
3774
|
+
function hasUnresolvedImportError(error) {
|
|
3775
|
+
const errors = error.errors;
|
|
3776
|
+
if (Array.isArray(errors)) {
|
|
3777
|
+
return errors.some((item) => item.text?.startsWith("Could not resolve "));
|
|
3778
|
+
}
|
|
3779
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3780
|
+
return message.includes("Could not resolve ");
|
|
3781
|
+
}
|
|
3782
|
+
function unresolvedImportFromError(error) {
|
|
3783
|
+
const buildError = error;
|
|
3784
|
+
const diagnostic = buildError.errors?.find((item) => item.text?.startsWith("Could not resolve "));
|
|
3785
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3786
|
+
const text = diagnostic?.text ?? message;
|
|
3787
|
+
const specifier = /Could not resolve "([^"]+)"/.exec(text)?.[1];
|
|
3788
|
+
if (!specifier)
|
|
3789
|
+
return;
|
|
3790
|
+
return {
|
|
3791
|
+
specifier,
|
|
3792
|
+
file: diagnostic?.location?.file ?? errorFileFromMessage(message)
|
|
3793
|
+
};
|
|
3794
|
+
}
|
|
3795
|
+
function errorFileFromMessage(message) {
|
|
3796
|
+
const match = /\n\s+([^:\n]+):\d+:\d+:\s+ERROR:\s+Could not resolve /.exec(message);
|
|
3797
|
+
return match?.[1];
|
|
3798
|
+
}
|
|
3799
|
+
function resolveTraceImport(root, fromFile, specifier) {
|
|
3800
|
+
if (path5.isAbsolute(specifier))
|
|
3801
|
+
return resolveTraceCandidate(specifier);
|
|
3802
|
+
return resolveImport(root, fromFile, specifier);
|
|
3803
|
+
}
|
|
3804
|
+
function resolveTraceFile(file) {
|
|
3805
|
+
return canonicalTraceFile(path5.isAbsolute(file) ? path5.resolve(file) : path5.resolve(process.cwd(), file));
|
|
3806
|
+
}
|
|
3807
|
+
function resolveTraceCandidate(base) {
|
|
3808
|
+
const ext = path5.extname(base);
|
|
3809
|
+
const candidates = ext ? [base] : [
|
|
3810
|
+
...traceSourceExtensions.map((extension) => `${base}${extension}`),
|
|
3811
|
+
...traceSourceExtensions.map((extension) => path5.join(base, `index${extension}`)),
|
|
3812
|
+
base
|
|
3813
|
+
];
|
|
3814
|
+
const resolved = candidates.find(isTraceFile);
|
|
3815
|
+
return resolved ? canonicalTraceFile(resolved) : undefined;
|
|
3816
|
+
}
|
|
3817
|
+
function isTraceFile(file) {
|
|
3818
|
+
try {
|
|
3819
|
+
return statSync3(file).isFile();
|
|
3820
|
+
} catch {
|
|
3821
|
+
return false;
|
|
3822
|
+
}
|
|
3823
|
+
}
|
|
3824
|
+
function readTextSyncSafe(file) {
|
|
3825
|
+
try {
|
|
3826
|
+
return existsSync3(file) ? readFileSync2(file, "utf8") : "";
|
|
3827
|
+
} catch {
|
|
3828
|
+
return "";
|
|
3829
|
+
}
|
|
3830
|
+
}
|
|
3831
|
+
function tracePath(entryFile, targetFile, parents) {
|
|
3832
|
+
const pathItems = [targetFile];
|
|
3833
|
+
let current = targetFile;
|
|
3834
|
+
while (current !== entryFile) {
|
|
3835
|
+
const parent = parents.get(current);
|
|
3836
|
+
if (!parent)
|
|
3837
|
+
return [];
|
|
3838
|
+
current = parent.file;
|
|
3839
|
+
pathItems.push(current);
|
|
3840
|
+
}
|
|
3841
|
+
return pathItems.reverse();
|
|
3842
|
+
}
|
|
3843
|
+
function sameFile(a, b) {
|
|
3844
|
+
return canonicalTraceFile(a) === canonicalTraceFile(b);
|
|
3845
|
+
}
|
|
3846
|
+
function displayTraceFile(config, file) {
|
|
3847
|
+
if (!path5.isAbsolute(file))
|
|
3848
|
+
return `${file} (generated client entry)`;
|
|
3849
|
+
const root = canonicalTraceFile(config.root);
|
|
3850
|
+
const workspaceRoot = canonicalTraceFile(config.workspaceRoot);
|
|
3851
|
+
const resolvedFile = canonicalTraceFile(file);
|
|
3852
|
+
const rootRelative = path5.relative(root, resolvedFile);
|
|
3853
|
+
if (!rootRelative.startsWith("..") && !path5.isAbsolute(rootRelative))
|
|
3854
|
+
return rootRelative;
|
|
3855
|
+
const workspaceRelative = path5.relative(workspaceRoot, resolvedFile);
|
|
3856
|
+
if (!workspaceRelative.startsWith("..") && !path5.isAbsolute(workspaceRelative))
|
|
3857
|
+
return workspaceRelative;
|
|
3858
|
+
return resolvedFile;
|
|
3859
|
+
}
|
|
3860
|
+
function canonicalTraceFile(file) {
|
|
3861
|
+
try {
|
|
3862
|
+
return realpathSync.native(file);
|
|
3863
|
+
} catch {
|
|
3864
|
+
return path5.resolve(file);
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
function clientCacheStaticHash(route, nextCompat, config) {
|
|
3868
|
+
conventionCache.clear();
|
|
3869
|
+
const hash = createHash3("sha256");
|
|
3870
|
+
hash.update(frameworkFingerprint());
|
|
3871
|
+
hash.update("\x00");
|
|
3872
|
+
hash.update(clientEntrySource({
|
|
3873
|
+
deferredDynamicHref: devDeferredDynamicHref(true),
|
|
3874
|
+
pageFile: route.client ? route.file : undefined,
|
|
3875
|
+
clientReferences: route.clientReferences,
|
|
3876
|
+
nextCompat,
|
|
3877
|
+
errorFile: nextCompat && config ? routeErrorFile(config, route) : undefined,
|
|
3878
|
+
globalErrorFile: nextCompat && config ? globalErrorFile(config) : undefined,
|
|
3879
|
+
notFoundFile: nextCompat && config ? routeNotFoundFile(config, route) : undefined,
|
|
3880
|
+
controlFlow: nextCompat ? routeThrowsClientControlFlow(route) : false,
|
|
3881
|
+
router: Boolean(route.needsRouterEntry),
|
|
3882
|
+
clientRootLayout: config ? hasClientRootLayout(config, route) : false,
|
|
3883
|
+
shellLayoutOrder: config ? shellLayoutOrder(config, route) : []
|
|
3884
|
+
}));
|
|
3885
|
+
hash.update("\x00");
|
|
3886
|
+
hash.update(route.id);
|
|
3887
|
+
hash.update("\x00");
|
|
3888
|
+
hash.update(route.client ? route.file : "");
|
|
3889
|
+
hash.update(JSON.stringify(route.clientReferences));
|
|
3890
|
+
return hash.digest("hex");
|
|
3891
|
+
}
|
|
3892
|
+
function clientCacheKeyFrom(staticHash, sources) {
|
|
3893
|
+
const hash = createHash3("sha256").update(staticHash).update("\x00");
|
|
3894
|
+
for (const source of sources) {
|
|
3895
|
+
hash.update(source.file);
|
|
3896
|
+
hash.update("\x00");
|
|
3897
|
+
hash.update(source.hash);
|
|
3898
|
+
hash.update("\x00");
|
|
3899
|
+
}
|
|
3900
|
+
return hash.digest("hex").slice(0, 16);
|
|
3901
|
+
}
|
|
3902
|
+
function clientSourceHash(source) {
|
|
3903
|
+
return Bun.hash(source).toString(36);
|
|
3904
|
+
}
|
|
3905
|
+
async function readClientCacheSource(file) {
|
|
3906
|
+
const stats = await stat(file).catch(() => {
|
|
3907
|
+
return;
|
|
3908
|
+
});
|
|
3909
|
+
return {
|
|
3910
|
+
file,
|
|
3911
|
+
mtimeMs: stats?.mtimeMs ?? 0,
|
|
3912
|
+
size: stats?.size ?? 0,
|
|
3913
|
+
hash: clientSourceHash(await readText(file))
|
|
3914
|
+
};
|
|
3915
|
+
}
|
|
3916
|
+
async function clientCacheKeyParts(route, nextCompat, config, staticHash) {
|
|
3917
|
+
const staticDigest = staticHash ?? clientCacheStaticHash(route, nextCompat, config);
|
|
3918
|
+
const sources = await Promise.all((await clientSourceFiles(route, config?.workspaceRoot)).map((file) => readClientCacheSource(file)));
|
|
3919
|
+
return { key: clientCacheKeyFrom(staticDigest, sources), staticHash: staticDigest, sources };
|
|
3920
|
+
}
|
|
3921
|
+
async function clientSourceFiles(route, workspaceRoot) {
|
|
3922
|
+
const entryFiles = [
|
|
3923
|
+
...route.client ? [route.file] : [],
|
|
3924
|
+
...route.clientReferences.map((reference) => reference.file)
|
|
3925
|
+
];
|
|
3926
|
+
const files = new Set;
|
|
3927
|
+
await Promise.all(entryFiles.map((file) => collectClientSourceFile(file, files, workspaceRoot)));
|
|
3928
|
+
return [...files].sort();
|
|
3929
|
+
}
|
|
3930
|
+
async function collectClientSourceFile(file, files, workspaceRoot) {
|
|
3931
|
+
if (files.has(file) || !existsSync3(file))
|
|
3932
|
+
return;
|
|
3933
|
+
files.add(file);
|
|
3934
|
+
if (isAssetLike(file))
|
|
3935
|
+
return;
|
|
3936
|
+
const source = rewriteLiteralDynamicCalls(await readText(file), file);
|
|
3937
|
+
await Promise.all([...localImports(file, source)].map((specifier) => {
|
|
3938
|
+
const resolved = resolveImport(rootFromFile(file), file, specifier, workspaceRoot);
|
|
3939
|
+
return resolved ? collectClientSourceFile(resolved, files, workspaceRoot) : Promise.resolve();
|
|
3940
|
+
}));
|
|
3941
|
+
}
|
|
3942
|
+
function localImports(file, source) {
|
|
3943
|
+
const facts = scanFacts(file, source);
|
|
3944
|
+
const imports = new Set;
|
|
3945
|
+
for (const edge of facts.imports)
|
|
3946
|
+
imports.add(edge.specifier);
|
|
3947
|
+
for (const edge of facts.dynamicImports)
|
|
3948
|
+
imports.add(edge.specifier);
|
|
3949
|
+
return imports;
|
|
3950
|
+
}
|
|
3951
|
+
function isAssetLike(file) {
|
|
3952
|
+
return /\.(css|svg|png|jpe?g|gif|webp|ico|woff2?)$/.test(file);
|
|
3953
|
+
}
|
|
3954
|
+
function rootFromFile(file) {
|
|
3955
|
+
const parts = file.split(path5.sep);
|
|
3956
|
+
const srcApp = parts.lastIndexOf("src");
|
|
3957
|
+
if (srcApp !== -1 && parts[srcApp + 1] === "app")
|
|
3958
|
+
return parts.slice(0, srcApp).join(path5.sep) || path5.sep;
|
|
3959
|
+
const app = parts.lastIndexOf("app");
|
|
3960
|
+
if (app !== -1)
|
|
3961
|
+
return parts.slice(0, app).join(path5.sep) || path5.sep;
|
|
3962
|
+
return path5.dirname(file);
|
|
3963
|
+
}
|
|
3964
|
+
async function nameSharedClientChunks(outDir, metafile) {
|
|
3965
|
+
if (metafile && traceEnabled("client")) {
|
|
3966
|
+
await writeText(path5.join(outDir, "metafile.json"), JSON.stringify(metafile));
|
|
3967
|
+
}
|
|
3968
|
+
const chunksDir = path5.join(outDir, "chunks");
|
|
3969
|
+
const chunkFiles = await listFiles(chunksDir);
|
|
3970
|
+
const files = chunkFiles.filter((file) => file.endsWith(".js"));
|
|
3971
|
+
const labels = metafile ? chunkLabelsFromMetafile(metafile) : new Map;
|
|
3972
|
+
const renames = new Map;
|
|
3973
|
+
const mapRenames = new Map;
|
|
3974
|
+
await Promise.all(chunkFiles.filter((file) => file.endsWith(".js.map")).map(async (file) => {
|
|
3975
|
+
const basename = path5.basename(file);
|
|
3976
|
+
const next = `chunk-map-${assetContentHash(await readFile(file))}.js.map`;
|
|
3977
|
+
mapRenames.set(basename, next);
|
|
3978
|
+
if (basename !== next)
|
|
3979
|
+
await rename(file, path5.join(chunksDir, next));
|
|
3980
|
+
}));
|
|
3981
|
+
const sources = new Map(await Promise.all(files.map(async (file) => [file, replaceChunkNames(await readText(file), mapRenames)])));
|
|
3982
|
+
const sourceByName = new Map([...sources].map(([file, source]) => [path5.basename(file), source]));
|
|
3983
|
+
const names = [...sourceByName.keys()].sort();
|
|
3984
|
+
const dependencies = new Map(names.map((name) => [
|
|
3985
|
+
name,
|
|
3986
|
+
new Set(names.filter((candidate) => sourceByName.get(name).includes(candidate)))
|
|
3987
|
+
]));
|
|
3988
|
+
const groups = chunkDependencyGroups(names, dependencies);
|
|
3989
|
+
const groupByName = new Map(groups.flatMap((group, index) => group.map((name) => [name, index])));
|
|
3990
|
+
const pending = new Set(groups.map((_, index) => index));
|
|
3991
|
+
const finalSources = new Map;
|
|
3992
|
+
while (pending.size > 0) {
|
|
3993
|
+
let progressed = false;
|
|
3994
|
+
for (const groupIndex of pending) {
|
|
3995
|
+
const group = groups[groupIndex];
|
|
3996
|
+
const waitsFor = group.some((name) => [...dependencies.get(name) ?? []].some((dependency) => {
|
|
3997
|
+
const dependencyGroup = groupByName.get(dependency);
|
|
3998
|
+
return dependencyGroup !== groupIndex && pending.has(dependencyGroup);
|
|
3999
|
+
}));
|
|
4000
|
+
if (waitsFor)
|
|
4001
|
+
continue;
|
|
4002
|
+
const rewritten = new Map(group.map((name) => [name, replaceChunkNames(sourceByName.get(name), renames)]));
|
|
4003
|
+
const cyclic = group.length > 1 || (dependencies.get(group[0])?.has(group[0]) ?? false);
|
|
4004
|
+
if (cyclic) {
|
|
4005
|
+
const placeholders = new Map(group.map((name, index) => [name, `__pnext_cycle_${index}.js`]));
|
|
4006
|
+
const canonical = group.map((name) => replaceChunkNames(rewritten.get(name), placeholders)).join("\x00");
|
|
4007
|
+
const groupHash = assetContentHash(canonical);
|
|
4008
|
+
for (const [index, name] of group.entries()) {
|
|
4009
|
+
renames.set(name, `${clientChunkLabel(name, labels)}-${groupHash}-${index}.js`);
|
|
4010
|
+
}
|
|
4011
|
+
} else {
|
|
4012
|
+
const name = group[0];
|
|
4013
|
+
renames.set(name, `${clientChunkLabel(name, labels)}-${assetContentHash(rewritten.get(name))}.js`);
|
|
4014
|
+
}
|
|
4015
|
+
for (const name of group) {
|
|
4016
|
+
finalSources.set(name, replaceChunkNames(rewritten.get(name), renames));
|
|
4017
|
+
}
|
|
4018
|
+
pending.delete(groupIndex);
|
|
4019
|
+
progressed = true;
|
|
4020
|
+
}
|
|
4021
|
+
if (!progressed)
|
|
4022
|
+
throw new Error("Unable to order client chunk dependency groups");
|
|
4023
|
+
}
|
|
4024
|
+
if (renames.size === 0)
|
|
4025
|
+
return renames;
|
|
4026
|
+
await Promise.all([...renames].map(async ([from, to]) => {
|
|
4027
|
+
const fromPath = path5.join(chunksDir, from);
|
|
4028
|
+
const toPath = path5.join(chunksDir, to);
|
|
4029
|
+
if (from === to || !existsSync3(fromPath))
|
|
4030
|
+
return;
|
|
4031
|
+
try {
|
|
4032
|
+
await rename(fromPath, toPath);
|
|
4033
|
+
} catch (error) {
|
|
4034
|
+
if (error.code !== "ENOENT")
|
|
4035
|
+
throw error;
|
|
4036
|
+
}
|
|
4037
|
+
await writeText(toPath, finalSources.get(from));
|
|
4038
|
+
}));
|
|
4039
|
+
const outputs = (await listFiles(outDir)).filter((item) => item.endsWith(".js"));
|
|
4040
|
+
await Promise.all(outputs.map(async (file) => {
|
|
4041
|
+
const originalName = path5.basename(file);
|
|
4042
|
+
const source = sources.get(file) ?? await readText(file);
|
|
4043
|
+
const next = finalSources.get(originalName) ?? replaceChunkNames(source, renames);
|
|
4044
|
+
if (next !== source)
|
|
4045
|
+
await writeText(file, next);
|
|
4046
|
+
}));
|
|
4047
|
+
return renames;
|
|
4048
|
+
}
|
|
4049
|
+
function clientChunkLabel(file, labels) {
|
|
4050
|
+
if (file.startsWith("chunk-"))
|
|
4051
|
+
return labels.get(file) ?? "shared";
|
|
4052
|
+
return path5.basename(file, ".js").replace(/-(?:[A-Z0-9]{8}|[0-9a-f]{16})$/, "");
|
|
4053
|
+
}
|
|
4054
|
+
function chunkLabelsFromMetafile(metafile) {
|
|
4055
|
+
const labels = new Map;
|
|
4056
|
+
for (const [outputPath, output] of Object.entries(metafile.outputs)) {
|
|
4057
|
+
const basename = path5.basename(outputPath);
|
|
4058
|
+
if (!basename.startsWith("chunk-") || !basename.endsWith(".js"))
|
|
4059
|
+
continue;
|
|
4060
|
+
const weights = new Map;
|
|
4061
|
+
for (const [input, { bytesInOutput }] of Object.entries(output.inputs)) {
|
|
4062
|
+
const label = chunkInputLabel(input);
|
|
4063
|
+
if (!label)
|
|
4064
|
+
continue;
|
|
4065
|
+
weights.set(label, (weights.get(label) ?? 0) + bytesInOutput);
|
|
4066
|
+
}
|
|
4067
|
+
const top = [...weights.entries()].sort((a, b) => b[1] - a[1])[0];
|
|
4068
|
+
if (top)
|
|
4069
|
+
labels.set(basename, top[0]);
|
|
4070
|
+
}
|
|
4071
|
+
return labels;
|
|
4072
|
+
}
|
|
4073
|
+
function chunkInputLabel(input) {
|
|
4074
|
+
const posix = input.split(path5.sep).join("/");
|
|
4075
|
+
const packageIndex = posix.lastIndexOf("node_modules/");
|
|
4076
|
+
if (packageIndex !== -1) {
|
|
4077
|
+
const parts = posix.slice(packageIndex + "node_modules/".length).split("/");
|
|
4078
|
+
const name = parts[0]?.startsWith("@") ? `${parts[0]}-${parts[1] ?? ""}` : parts[0];
|
|
4079
|
+
return sanitizeChunkLabel(name ?? "");
|
|
4080
|
+
}
|
|
4081
|
+
return sanitizeChunkLabel(posix.split("/").at(-1)?.replace(/\.[^.]+$/, "") ?? "");
|
|
4082
|
+
}
|
|
4083
|
+
function sanitizeChunkLabel(label) {
|
|
4084
|
+
const clean = label.replace(/^@/, "").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 40);
|
|
4085
|
+
return clean || undefined;
|
|
4086
|
+
}
|
|
4087
|
+
function chunkDependencyGroups(names, dependencies) {
|
|
4088
|
+
let nextIndex = 0;
|
|
4089
|
+
const indices = new Map;
|
|
4090
|
+
const lowLinks = new Map;
|
|
4091
|
+
const stack = [];
|
|
4092
|
+
const onStack = new Set;
|
|
4093
|
+
const groups = [];
|
|
4094
|
+
const visit = (name) => {
|
|
4095
|
+
const index = nextIndex++;
|
|
4096
|
+
indices.set(name, index);
|
|
4097
|
+
lowLinks.set(name, index);
|
|
4098
|
+
stack.push(name);
|
|
4099
|
+
onStack.add(name);
|
|
4100
|
+
for (const dependency of dependencies.get(name) ?? []) {
|
|
4101
|
+
if (!indices.has(dependency)) {
|
|
4102
|
+
visit(dependency);
|
|
4103
|
+
lowLinks.set(name, Math.min(lowLinks.get(name), lowLinks.get(dependency)));
|
|
4104
|
+
} else if (onStack.has(dependency)) {
|
|
4105
|
+
lowLinks.set(name, Math.min(lowLinks.get(name), indices.get(dependency)));
|
|
4106
|
+
}
|
|
4107
|
+
}
|
|
4108
|
+
if (lowLinks.get(name) !== indices.get(name))
|
|
4109
|
+
return;
|
|
4110
|
+
const group = [];
|
|
4111
|
+
for (;; ) {
|
|
4112
|
+
const member = stack.pop();
|
|
4113
|
+
onStack.delete(member);
|
|
4114
|
+
group.push(member);
|
|
4115
|
+
if (member === name)
|
|
4116
|
+
break;
|
|
4117
|
+
}
|
|
4118
|
+
groups.push(group.sort());
|
|
4119
|
+
};
|
|
4120
|
+
for (const name of names)
|
|
4121
|
+
if (!indices.has(name))
|
|
4122
|
+
visit(name);
|
|
4123
|
+
return groups;
|
|
4124
|
+
}
|
|
4125
|
+
function replaceChunkNames(source, renames) {
|
|
4126
|
+
const pattern = new RegExp([...renames.keys()].map(escapeRegex).join("|"), "g");
|
|
4127
|
+
return source.replace(pattern, (match) => renames.get(match) ?? match);
|
|
4128
|
+
}
|
|
4129
|
+
|
|
4130
|
+
export { emitStaticClientChunks, prebuiltRuntimeDir, registerDeferredDynamicRef, deferredDynamicRefById, DEFERRED_DYNAMIC_SIDECAR, buildClientEntry, buildClientEntries, startClientSources, clientCacheStaticHash, clientSourceHash, clientCacheKeyParts };
|