@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
|
@@ -9,9 +9,18 @@
|
|
|
9
9
|
* the alias/asset helpers this module reuses from it - lives in `./server`.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { builtinModules, createRequire } from 'node:module'
|
|
13
|
-
import {
|
|
14
|
-
|
|
12
|
+
import { builtinModules, createRequire, isBuiltin } from 'node:module'
|
|
13
|
+
import {
|
|
14
|
+
appendFileSync,
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
readFileSync,
|
|
18
|
+
readlinkSync,
|
|
19
|
+
realpathSync,
|
|
20
|
+
statSync,
|
|
21
|
+
writeFileSync,
|
|
22
|
+
} from 'node:fs'
|
|
23
|
+
import { copyFile, mkdir, readFile, rename, rm, symlink, writeFile } from 'node:fs/promises'
|
|
15
24
|
import path from 'node:path'
|
|
16
25
|
import { fileURLToPath } from 'node:url'
|
|
17
26
|
import type { Loader, Metafile, OnLoadResult, OnResolveResult, Plugin } from 'esbuild'
|
|
@@ -33,6 +42,7 @@ import {
|
|
|
33
42
|
noteDevArtifactWritten,
|
|
34
43
|
} from './module-cache'
|
|
35
44
|
import { frameworkFingerprint } from './fingerprint'
|
|
45
|
+
import { isReleaseCompile } from './load'
|
|
36
46
|
import {
|
|
37
47
|
getExternalPackagePolicy,
|
|
38
48
|
isEsmModuleEntry,
|
|
@@ -56,6 +66,7 @@ import {
|
|
|
56
66
|
import { noteCompiledClientReference } from '../client/reference'
|
|
57
67
|
import { reactCompatEnabled } from '../render/hooks'
|
|
58
68
|
import { writeFileAtomic } from '../utils/fs'
|
|
69
|
+
import { releaseArtifactSource } from './modules'
|
|
59
70
|
import { formatDuration } from '../utils/verbose'
|
|
60
71
|
import {
|
|
61
72
|
addCommonJsNamedExports,
|
|
@@ -87,6 +98,9 @@ import {
|
|
|
87
98
|
coreAliases,
|
|
88
99
|
firstAliasForSpecifier,
|
|
89
100
|
hashBundleSpecifier,
|
|
101
|
+
nodeModuleSourceLoadsAsIs,
|
|
102
|
+
recordNativeNodeModuleFiles,
|
|
103
|
+
statSignature,
|
|
90
104
|
packageJsxLoaders,
|
|
91
105
|
runtimeAliasBuildPlugin,
|
|
92
106
|
serverAssetPlugin,
|
|
@@ -114,6 +128,11 @@ export function clearServerRuntimeCaches() {
|
|
|
114
128
|
vendorReuseLoaded.clear()
|
|
115
129
|
vendorReuseProbes.clear()
|
|
116
130
|
vendorLayerProbeMemo.clear()
|
|
131
|
+
nativeServerEntries.clear()
|
|
132
|
+
nativeServerFiles.clear()
|
|
133
|
+
nativeVerdictStores.clear()
|
|
134
|
+
stateCompanions.clear()
|
|
135
|
+
packageFileScans.clear()
|
|
117
136
|
// The marker memo tracks files under `.pnext`; a wipe takes them with it.
|
|
118
137
|
esmDistPackages.clear()
|
|
119
138
|
clearProvidedEntryResolutions()
|
|
@@ -130,6 +149,14 @@ export async function externalServerPackageHref(
|
|
|
130
149
|
nested = false,
|
|
131
150
|
) {
|
|
132
151
|
let plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
|
|
152
|
+
const demand = (file: string) => {
|
|
153
|
+
if (!nested) noteReleaseDependency(config, file, specifier, resolveDir, target, conditionTarget)
|
|
154
|
+
return pathToFileHref(file)
|
|
155
|
+
}
|
|
156
|
+
if (target === 'server' && conditionTarget === 'server' && isPackageSpecifier(specifier)) {
|
|
157
|
+
const native = await nativeServerEntry(config, specifier, resolveDir)
|
|
158
|
+
if (native) return demand(native)
|
|
159
|
+
}
|
|
133
160
|
const aliased = await crossLayerReusePlan(
|
|
134
161
|
config,
|
|
135
162
|
specifier,
|
|
@@ -158,7 +185,526 @@ export async function externalServerPackageHref(
|
|
|
158
185
|
if (plan.group) dropVendorGroup(plan.group.key)
|
|
159
186
|
file = await vendorBundle(plan, nested)
|
|
160
187
|
}
|
|
161
|
-
return
|
|
188
|
+
return demand(file)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// --------------------------------------------------------------------------
|
|
192
|
+
// release dependency graph
|
|
193
|
+
// --------------------------------------------------------------------------
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* A release bundles each App Router layer's server dependencies into one graph, as Next does. The
|
|
197
|
+
* compile still vendors (or loads natively) what the build's own prerender imports, and logs each
|
|
198
|
+
* App Router demand so the release can repoint its modules at the linked graph once all compile.
|
|
199
|
+
*/
|
|
200
|
+
export interface ReleaseDependency {
|
|
201
|
+
/** What the compile handed out: a vendor artifact or a natively loaded file. */
|
|
202
|
+
file: string
|
|
203
|
+
layer: ReleaseDependencyLayer
|
|
204
|
+
specifier: string
|
|
205
|
+
/** The file this layer resolves `specifier` to: the graph's entry. */
|
|
206
|
+
entry?: string
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** The App Router node layers, `<target>:<conditionTarget>`: RSC and client components' SSR. */
|
|
210
|
+
export type ReleaseDependencyLayer = 'server:server' | 'client:client'
|
|
211
|
+
|
|
212
|
+
const releaseDependencyLayers = new Set<string>(['server:server', 'client:client'])
|
|
213
|
+
const loggedReleaseDependencies = new Set<string>()
|
|
214
|
+
|
|
215
|
+
export function releaseDependencyLog(config: ResolvedConfig) {
|
|
216
|
+
return path.join(cacheRoot(config.outPath), 'dependencies.jsonl')
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** @internal Each build starts from an emptied out dir. */
|
|
220
|
+
export function clearReleaseDependencyLog() {
|
|
221
|
+
loggedReleaseDependencies.clear()
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/** Appended, not held: the build and its warm child both compile. */
|
|
225
|
+
function noteReleaseDependency(
|
|
226
|
+
config: ResolvedConfig,
|
|
227
|
+
file: string,
|
|
228
|
+
specifier: string,
|
|
229
|
+
resolveDir: string,
|
|
230
|
+
target: CompatAliasTarget,
|
|
231
|
+
conditionTarget: ServerBundleTarget,
|
|
232
|
+
) {
|
|
233
|
+
const layer = `${target}:${conditionTarget}` as ReleaseDependencyLayer
|
|
234
|
+
if (!isReleaseCompile() || !releaseDependencyLayers.has(layer)) return
|
|
235
|
+
const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
236
|
+
appendReleaseLog(config, { file, layer, specifier, entry } satisfies Partial<ReleaseDependency>)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** A release module compiled for another layer than its artifact directory's (a `'use client'` file). */
|
|
240
|
+
export function noteReleaseModuleLayer(
|
|
241
|
+
config: ResolvedConfig,
|
|
242
|
+
module: string,
|
|
243
|
+
target: CompatAliasTarget,
|
|
244
|
+
conditionTarget: ServerBundleTarget,
|
|
245
|
+
) {
|
|
246
|
+
if (isReleaseCompile())
|
|
247
|
+
appendReleaseLog(config, { module, layer: `${target}:${conditionTarget}` })
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function appendReleaseLog(config: ResolvedConfig, record: object) {
|
|
251
|
+
const line = JSON.stringify(record)
|
|
252
|
+
const log = releaseDependencyLog(config)
|
|
253
|
+
if (loggedReleaseDependencies.has(`${log}\0${line}`)) return
|
|
254
|
+
loggedReleaseDependencies.add(`${log}\0${line}`)
|
|
255
|
+
mkdirSync(path.dirname(log), { recursive: true })
|
|
256
|
+
appendFileSync(log, `${line}\n`)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Link one layer's dependency entries in one splitting build: each package file becomes one module and
|
|
261
|
+
* unused code drops out. `serverExternalPackages` and Next's built-in list stay external where the
|
|
262
|
+
* importer resolves them as the root does; Next bundles another copy. A package that reads files beside
|
|
263
|
+
* itself (`import.meta.url`) or imports computed paths loads in place, as in 0.1.6: Next's webpack
|
|
264
|
+
* contexts bundle computed imports instead. Returns the files written and the entries left in place.
|
|
265
|
+
*/
|
|
266
|
+
export async function linkReleaseDependencies(
|
|
267
|
+
config: ResolvedConfig,
|
|
268
|
+
layer: ReleaseDependencyLayer,
|
|
269
|
+
entries: readonly { in: string; out: string }[],
|
|
270
|
+
outdir: string,
|
|
271
|
+
) {
|
|
272
|
+
const [target, conditionTarget] = layer.split(':') as [CompatAliasTarget, ServerBundleTarget]
|
|
273
|
+
const unresolved = new Set<string>()
|
|
274
|
+
const inPlace = new Set<string>()
|
|
275
|
+
const ordered = [...entries].sort((a, b) => (a.out < b.out ? -1 : 1))
|
|
276
|
+
let linked = ordered
|
|
277
|
+
let outputs: { path: string; text: string }[]
|
|
278
|
+
for (;;) {
|
|
279
|
+
let result
|
|
280
|
+
try {
|
|
281
|
+
result = await build({
|
|
282
|
+
entryPoints: linked.map(entry => ({
|
|
283
|
+
in:
|
|
284
|
+
getBundlerExtensions().serverBundleEntry(entry.in, path.dirname(entry.in), entry.in) ??
|
|
285
|
+
entry.in,
|
|
286
|
+
out: entry.out,
|
|
287
|
+
})),
|
|
288
|
+
absWorkingDir: config.root,
|
|
289
|
+
bundle: true,
|
|
290
|
+
splitting: true,
|
|
291
|
+
write: false,
|
|
292
|
+
metafile: true,
|
|
293
|
+
outdir,
|
|
294
|
+
chunkNames: 'chunks/[name]-[hash]',
|
|
295
|
+
format: 'esm',
|
|
296
|
+
platform: 'neutral',
|
|
297
|
+
target: 'es2022',
|
|
298
|
+
conditions: serverBundleConditions(conditionTarget),
|
|
299
|
+
mainFields: ['module', 'main'],
|
|
300
|
+
loader: packageJsxLoaders,
|
|
301
|
+
jsx: 'automatic',
|
|
302
|
+
jsxImportSource: 'preact',
|
|
303
|
+
logLevel: 'silent',
|
|
304
|
+
...serverDefineOptions(),
|
|
305
|
+
plugins: profiledVendorPlugins([
|
|
306
|
+
serverAssetPlugin(config),
|
|
307
|
+
...getBundlerExtensions().serverEsbuildPlugins(
|
|
308
|
+
config,
|
|
309
|
+
vendorPluginOptions(config, target, conditionTarget),
|
|
310
|
+
),
|
|
311
|
+
runtimeAliasBuildPlugin(config, target, {
|
|
312
|
+
bundleRequireAliases: true,
|
|
313
|
+
reactServerLayer: target === 'server',
|
|
314
|
+
}),
|
|
315
|
+
releaseExternalsPlugin(config, conditionTarget, unresolved, inPlace),
|
|
316
|
+
]),
|
|
317
|
+
})
|
|
318
|
+
} catch (error) {
|
|
319
|
+
// An import that resolves nowhere stays a bare external, as in a vendor bundle: it fails when it runs.
|
|
320
|
+
const missing = unresolvedBareImports(error).filter(specifier => !unresolved.has(specifier))
|
|
321
|
+
if (missing.length === 0) throw error
|
|
322
|
+
for (const specifier of missing) unresolved.add(specifier)
|
|
323
|
+
continue
|
|
324
|
+
}
|
|
325
|
+
const found = (await inPlacePackages(config, result)).filter(root => !inPlace.has(root))
|
|
326
|
+
if (found.length === 0) {
|
|
327
|
+
outputs = result.outputFiles
|
|
328
|
+
break
|
|
329
|
+
}
|
|
330
|
+
for (const root of found) inPlace.add(root)
|
|
331
|
+
linked = ordered.filter(entry => !inPlace.has(nearestManifestDir(path.dirname(entry.in))!))
|
|
332
|
+
if (linked.length === 0) return { files: [], inPlace: ordered.map(entry => entry.in) }
|
|
333
|
+
}
|
|
334
|
+
const entryFiles = new Set(linked.map(entry => path.join(outdir, `${entry.out}.js`)))
|
|
335
|
+
const texts = new Map(outputs.map(output => [output.path, output.text]))
|
|
336
|
+
// Written beside the graph and renamed into place, so a crash never leaves half a graph.
|
|
337
|
+
const staging = `${outdir}.${process.pid}.tmp`
|
|
338
|
+
await rm(staging, { recursive: true, force: true })
|
|
339
|
+
try {
|
|
340
|
+
await mkdir(path.join(staging, 'chunks'), { recursive: true })
|
|
341
|
+
for (const output of outputs) {
|
|
342
|
+
const code = entryFiles.has(output.path)
|
|
343
|
+
? addCommonJsNamedExports(output.text, sharedCommonJsBody(output.path, output.text, texts))
|
|
344
|
+
: output.text
|
|
345
|
+
await writeFile(
|
|
346
|
+
path.join(staging, path.relative(outdir, output.path)),
|
|
347
|
+
releaseArtifactSource(
|
|
348
|
+
output.path,
|
|
349
|
+
target === 'server' ? applyBundledSourceTransforms(code, output.path) : code,
|
|
350
|
+
),
|
|
351
|
+
)
|
|
352
|
+
}
|
|
353
|
+
await rm(outdir, { recursive: true, force: true })
|
|
354
|
+
await rename(staging, outdir)
|
|
355
|
+
} catch (error) {
|
|
356
|
+
await rm(staging, { recursive: true, force: true })
|
|
357
|
+
throw error
|
|
358
|
+
}
|
|
359
|
+
return {
|
|
360
|
+
files: outputs.map(output => output.path).sort(),
|
|
361
|
+
inPlace: ordered.filter(entry => !linked.includes(entry)).map(entry => entry.in),
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// `import.meta` paths and an `import()` of a computed path resolve where the file lies; an import marked
|
|
366
|
+
// `webpackIgnore`/`@vite-ignore` is meant to stay a runtime import, as Next's bundle keeps it.
|
|
367
|
+
const IMPORT_META_PATH = /\bimport\.meta\.(?:url|dirname|filename|resolve)\b/
|
|
368
|
+
const COMPUTED_IMPORT =
|
|
369
|
+
/(?<![.\w$])import\s*\(((?:\s|\/\*[\s\S]*?\*\/|\/\/[^\n]*\n)*)(?!['"`)\s/])/g
|
|
370
|
+
|
|
371
|
+
function readsBesideItself(source: string) {
|
|
372
|
+
if (IMPORT_META_PATH.test(source)) return true
|
|
373
|
+
for (const match of source.matchAll(COMPUTED_IMPORT)) {
|
|
374
|
+
if (!/webpackIgnore:\s*true|@vite-ignore/.test(match[1]!)) return true
|
|
375
|
+
}
|
|
376
|
+
return false
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Package dirs whose files read beside themselves or import computed paths. */
|
|
380
|
+
async function inPlacePackages(
|
|
381
|
+
config: ResolvedConfig,
|
|
382
|
+
result: { metafile?: Metafile; warnings: { text: string; location: { file: string } | null }[] },
|
|
383
|
+
) {
|
|
384
|
+
const files = new Set(
|
|
385
|
+
result.warnings
|
|
386
|
+
.filter(warning => warning.text.includes('is not a string literal') && warning.location)
|
|
387
|
+
.map(warning => path.resolve(config.root, warning.location!.file)),
|
|
388
|
+
)
|
|
389
|
+
const installed = Object.keys(result.metafile?.inputs ?? {})
|
|
390
|
+
.filter(input => !input.includes(':') && input.includes('node_modules/'))
|
|
391
|
+
.map(input => path.resolve(config.root, input))
|
|
392
|
+
await Promise.all(
|
|
393
|
+
installed.map(async file => {
|
|
394
|
+
const source = await readFile(file, 'utf8').catch(() => '')
|
|
395
|
+
if (readsBesideItself(source)) files.add(file)
|
|
396
|
+
}),
|
|
397
|
+
)
|
|
398
|
+
return [
|
|
399
|
+
...new Set(
|
|
400
|
+
[...files]
|
|
401
|
+
.filter(file => file.includes(`${path.sep}node_modules${path.sep}`))
|
|
402
|
+
.map(file => nearestManifestDir(path.dirname(canonicalVendorEntry(file)!)))
|
|
403
|
+
.filter((root): root is string => root !== undefined),
|
|
404
|
+
),
|
|
405
|
+
]
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Under splitting, a CommonJS entry's body can land in a shared chunk the entry only calls
|
|
410
|
+
* (`export default require_x()`): its named exports are read from that body.
|
|
411
|
+
*/
|
|
412
|
+
function sharedCommonJsBody(file: string, code: string, texts: ReadonlyMap<string, string>) {
|
|
413
|
+
const wrapper = /(?:^|\n)export default (require_[\w$]+)\(\);?/.exec(code)?.[1]
|
|
414
|
+
const chunk =
|
|
415
|
+
wrapper &&
|
|
416
|
+
new RegExp(`import\\s*\\{[^}]*\\b${wrapper}\\b[^}]*\\}\\s*from\\s*"([^"]+)"`).exec(code)?.[1]
|
|
417
|
+
const text = chunk && texts.get(path.resolve(path.dirname(file), chunk))
|
|
418
|
+
if (!text) return code
|
|
419
|
+
const start = text.indexOf(`var ${wrapper} = `)
|
|
420
|
+
if (start === -1) return code
|
|
421
|
+
const next = text.indexOf('\nvar require_', start + 1)
|
|
422
|
+
return text.slice(start, next === -1 ? undefined : next)
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const frameworkPackageName = (
|
|
426
|
+
JSON.parse(readFileSync(path.resolve(import.meta.dirname, '../../package.json'), 'utf8')) as {
|
|
427
|
+
name: string
|
|
428
|
+
}
|
|
429
|
+
).name
|
|
430
|
+
|
|
431
|
+
function releaseExternalsPlugin(
|
|
432
|
+
config: ResolvedConfig,
|
|
433
|
+
conditionTarget: ServerBundleTarget,
|
|
434
|
+
unresolved: ReadonlySet<string>,
|
|
435
|
+
inPlace: ReadonlySet<string>,
|
|
436
|
+
): Plugin {
|
|
437
|
+
const policy = getExternalPackagePolicy()
|
|
438
|
+
return {
|
|
439
|
+
name: 'pnext-release-externals',
|
|
440
|
+
setup(build) {
|
|
441
|
+
build.onResolve({ filter: /^[^./]/ }, async args => {
|
|
442
|
+
const name = packageNameFromSpecifier(args.path)
|
|
443
|
+
if (!name || args.kind === 'entry-point') return undefined
|
|
444
|
+
// Builtins, unresolvable imports and the framework (the runtime's own instance) stay external.
|
|
445
|
+
if (
|
|
446
|
+
unresolved.has(args.path) ||
|
|
447
|
+
name === frameworkPackageName ||
|
|
448
|
+
isBuiltin(args.path) ||
|
|
449
|
+
builtinModules.includes(name) ||
|
|
450
|
+
isBunBuiltin(name)
|
|
451
|
+
) {
|
|
452
|
+
return { path: args.path, external: true }
|
|
453
|
+
}
|
|
454
|
+
const conditions =
|
|
455
|
+
args.kind === 'require-call'
|
|
456
|
+
? serverBundleRequireConditions(conditionTarget)
|
|
457
|
+
: serverBundleConditions(conditionTarget)
|
|
458
|
+
const external = policy.external(name) || policy.releaseExternal?.(name)
|
|
459
|
+
if (!external && inPlace.size === 0) return undefined
|
|
460
|
+
const fromImporter = resolveNestedPackageFromImporter(args.importer, args.path, conditions)
|
|
461
|
+
// A package that loads in place is imported where it lies, when Bun loads it as-is.
|
|
462
|
+
const loaded = fromImporter && canonicalVendorEntry(fromImporter)!
|
|
463
|
+
if (
|
|
464
|
+
loaded &&
|
|
465
|
+
inPlace.has(nearestManifestDir(path.dirname(loaded))!) &&
|
|
466
|
+
isEsmModuleEntry(loaded) &&
|
|
467
|
+
(await nativeServerGraph(loaded))
|
|
468
|
+
) {
|
|
469
|
+
return { path: loaded, external: true }
|
|
470
|
+
}
|
|
471
|
+
if (!external) return undefined
|
|
472
|
+
const fromRoot = resolveVendorPackageSpecifier(
|
|
473
|
+
config.root,
|
|
474
|
+
path.join(config.root, 'pnext-resolve.ts'),
|
|
475
|
+
args.path,
|
|
476
|
+
conditions,
|
|
477
|
+
)
|
|
478
|
+
return fromImporter && fromImporter === fromRoot
|
|
479
|
+
? { path: args.path, external: true }
|
|
480
|
+
: undefined
|
|
481
|
+
})
|
|
482
|
+
},
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
function unresolvedBareImports(error: unknown) {
|
|
487
|
+
const errors = (error as { errors?: { text?: string }[] } | undefined)?.errors ?? []
|
|
488
|
+
return errors.flatMap(({ text }) => {
|
|
489
|
+
const specifier = /^Could not resolve "([^"]+)"/.exec(text ?? '')?.[1]
|
|
490
|
+
return specifier && isPackageSpecifier(specifier) ? [specifier] : []
|
|
491
|
+
})
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// --------------------------------------------------------------------------
|
|
495
|
+
// native server dependencies
|
|
496
|
+
// --------------------------------------------------------------------------
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* On the server a node_modules package loads natively through Bun, as Vite SSR and Node load it: one
|
|
500
|
+
* module instance per resolved file and no build. A bundle per subpath would inline its own copy of
|
|
501
|
+
* every shared internal file and split module state (better-auth's request-state slots).
|
|
502
|
+
*
|
|
503
|
+
* It is bundled only when pnext has to transform something its server graph reaches: an aliased
|
|
504
|
+
* import (react, next/*), a `'use client'`/`'use server'`/`'use cache'` file, a non-JS file, a
|
|
505
|
+
* `transpilePackages` member, or a bare import Bun resolves to a different file than the server
|
|
506
|
+
* layer's runtime conditions (`react-server`) select. `PNEXT_SERVER_NATIVE=0` bundles everything.
|
|
507
|
+
*/
|
|
508
|
+
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
509
|
+
const serverNativeEnabled = () => process.env.PNEXT_SERVER_NATIVE !== '0'
|
|
510
|
+
|
|
511
|
+
/** The server layer's conditions minus bundler-only ones (`module`): what Node would honour. */
|
|
512
|
+
const serverRuntimeConditions = (kind: string) =>
|
|
513
|
+
(kind === 'require-call'
|
|
514
|
+
? serverBundleRequireConditions('server')
|
|
515
|
+
: serverBundleConditions('server')
|
|
516
|
+
).filter(condition => condition !== 'module')
|
|
517
|
+
|
|
518
|
+
const nativeServerEntries = new Map<string, Promise<string | undefined>>()
|
|
519
|
+
|
|
520
|
+
/** The file Bun loads for `specifier` when its whole server graph needs no transform. */
|
|
521
|
+
function nativeServerEntry(config: ResolvedConfig, specifier: string, resolveDir: string) {
|
|
522
|
+
const packageName = packageNameFromSpecifier(specifier)
|
|
523
|
+
if (
|
|
524
|
+
!serverNativeEnabled() ||
|
|
525
|
+
serverDefineOptions().define ||
|
|
526
|
+
(packageName && getExternalPackagePolicy().transpile(packageName))
|
|
527
|
+
) {
|
|
528
|
+
return Promise.resolve(undefined)
|
|
529
|
+
}
|
|
530
|
+
const key = `${resolveDir}\0${specifier}`
|
|
531
|
+
let entry = nativeServerEntries.get(key)
|
|
532
|
+
if (!entry) {
|
|
533
|
+
const verdicts = nativeVerdictsFor(config)
|
|
534
|
+
const storeKey = `${vendorPortablePath(config, resolveDir)}\0${specifier}`
|
|
535
|
+
const stored = verdicts.get(storeKey)
|
|
536
|
+
const storedEntry = stored && path.resolve(config.workspaceRoot, stored.resolved)
|
|
537
|
+
if (stored && statSignature(storedEntry!) === stored.signature) {
|
|
538
|
+
entry = Promise.resolve(stored.native ? storedEntry : undefined)
|
|
539
|
+
} else {
|
|
540
|
+
entry = nativeServerEntryUncached(config, specifier, resolveDir)
|
|
541
|
+
.then(native => {
|
|
542
|
+
const resolved = nativeResolve(specifier, resolveDir)
|
|
543
|
+
const signature = resolved && statSignature(resolved)
|
|
544
|
+
if (resolved && signature) {
|
|
545
|
+
const relative = path.relative(config.workspaceRoot, resolved)
|
|
546
|
+
verdicts.set(storeKey, { resolved: relative, signature, native: Boolean(native) })
|
|
547
|
+
persistNativeVerdicts()
|
|
548
|
+
}
|
|
549
|
+
return native
|
|
550
|
+
})
|
|
551
|
+
.catch(() => undefined)
|
|
552
|
+
}
|
|
553
|
+
nativeServerEntries.set(key, entry)
|
|
554
|
+
}
|
|
555
|
+
return entry
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Verdicts outlive the process, as vendor artifacts do: re-walking every native graph made a dev
|
|
560
|
+
* restart pay for all of them again. One holds while its resolved entry's stat does; the store name
|
|
561
|
+
* carries what else a verdict reads (pnext's generation, `transpilePackages`, the alias set).
|
|
562
|
+
*/
|
|
563
|
+
interface NativeVerdict {
|
|
564
|
+
resolved: string
|
|
565
|
+
signature: string
|
|
566
|
+
native: boolean
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const nativeVerdictStores = new Map<
|
|
570
|
+
string,
|
|
571
|
+
{ file: string; verdicts: Map<string, NativeVerdict> }
|
|
572
|
+
>()
|
|
573
|
+
let nativeVerdictFlushArmed = false
|
|
574
|
+
|
|
575
|
+
function nativeVerdictsFor(config: ResolvedConfig) {
|
|
576
|
+
let store = nativeVerdictStores.get(config.outPath)
|
|
577
|
+
if (!store) {
|
|
578
|
+
const inputs = JSON.stringify([
|
|
579
|
+
getExternalPackagePolicy().transpiled?.() ?? null,
|
|
580
|
+
Object.keys(coreAliases(config, 'server')).sort(),
|
|
581
|
+
])
|
|
582
|
+
const file = vendorStoreFile(config, `native.${hashBundleSpecifier(inputs)}`)
|
|
583
|
+
store = { file, verdicts: new Map() }
|
|
584
|
+
nativeVerdictStores.set(config.outPath, store)
|
|
585
|
+
try {
|
|
586
|
+
const stored = JSON.parse(readFileSync(file, 'utf8')) as Record<string, NativeVerdict>
|
|
587
|
+
for (const [key, verdict] of Object.entries(stored)) store.verdicts.set(key, verdict)
|
|
588
|
+
} catch {
|
|
589
|
+
// No store yet; verdicts are written at exit.
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
return store.verdicts
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function persistNativeVerdicts() {
|
|
596
|
+
if (nativeVerdictFlushArmed) return
|
|
597
|
+
nativeVerdictFlushArmed = true
|
|
598
|
+
process.once('exit', () => {
|
|
599
|
+
for (const { file, verdicts } of nativeVerdictStores.values()) {
|
|
600
|
+
try {
|
|
601
|
+
// Another process (a build's warm child) may have written since this one loaded.
|
|
602
|
+
const stored = JSON.parse(readFileSync(file, 'utf8')) as Record<string, NativeVerdict>
|
|
603
|
+
for (const [key, verdict] of Object.entries(stored)) {
|
|
604
|
+
if (!verdicts.has(key)) verdicts.set(key, verdict)
|
|
605
|
+
}
|
|
606
|
+
} catch {
|
|
607
|
+
// Nothing on disk yet.
|
|
608
|
+
}
|
|
609
|
+
try {
|
|
610
|
+
mkdirSync(path.dirname(file), { recursive: true })
|
|
611
|
+
writeFileSync(file, JSON.stringify(Object.fromEntries(verdicts)))
|
|
612
|
+
} catch {
|
|
613
|
+
// A missing store only costs the walk.
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
})
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
async function nativeServerEntryUncached(
|
|
620
|
+
config: ResolvedConfig,
|
|
621
|
+
specifier: string,
|
|
622
|
+
resolveDir: string,
|
|
623
|
+
) {
|
|
624
|
+
const importer = path.join(resolveDir, 'pnext-resolve.ts')
|
|
625
|
+
const entry = nativeResolve(specifier, resolveDir)
|
|
626
|
+
// A CommonJS entry keeps its vendor bundle: its importers' interop (named-export facades,
|
|
627
|
+
// `require` of the artifact) is built around one.
|
|
628
|
+
if (!entry || !isEsmModuleEntry(entry)) return undefined
|
|
629
|
+
if (nativeImportDiffers(importer, specifier, 'import-statement', entry)) return undefined
|
|
630
|
+
const graph = await nativeServerGraph(entry)
|
|
631
|
+
if (!graph) return undefined
|
|
632
|
+
recordNativeNodeModuleFiles(config, graph)
|
|
633
|
+
return entry
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function nativeResolve(specifier: string, fromDir: string) {
|
|
637
|
+
try {
|
|
638
|
+
return canonicalVendorEntry(Bun.resolveSync(specifier, fromDir))
|
|
639
|
+
} catch {
|
|
640
|
+
return undefined
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Whether the server layer selects another file than Bun loaded, with or without `react-server`:
|
|
646
|
+
* every pnext process runs with that condition, but a native graph must not depend on it.
|
|
647
|
+
*/
|
|
648
|
+
function nativeImportDiffers(importer: string, specifier: string, kind: string, loaded: string) {
|
|
649
|
+
const conditions = serverRuntimeConditions(kind)
|
|
650
|
+
return [conditions, conditions.filter(condition => condition !== 'react-server')].some(set => {
|
|
651
|
+
const selected = resolveNestedPackageFromImporter(importer, specifier, set)
|
|
652
|
+
return selected !== undefined && canonicalVendorEntry(selected) !== loaded
|
|
653
|
+
})
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Per file: what it loads natively, or undefined when it needs a transform. */
|
|
657
|
+
const nativeServerFiles = new Map<string, Promise<string[] | undefined>>()
|
|
658
|
+
|
|
659
|
+
function nativeServerFile(file: string) {
|
|
660
|
+
let imports = nativeServerFiles.get(file)
|
|
661
|
+
if (!imports) {
|
|
662
|
+
imports = nativeServerFileUncached(file).catch(() => undefined)
|
|
663
|
+
nativeServerFiles.set(file, imports)
|
|
664
|
+
}
|
|
665
|
+
return imports
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const nativeImportScanner = new Bun.Transpiler({ loader: 'js' })
|
|
669
|
+
|
|
670
|
+
async function nativeServerFileUncached(file: string) {
|
|
671
|
+
// An isolated store (`.pnpm`, `.bun`) is a layout the deployed function does not mirror.
|
|
672
|
+
if (!/\.[cm]?js$/.test(file) || file.includes(`${path.sep}node_modules${path.sep}.`)) {
|
|
673
|
+
return undefined
|
|
674
|
+
}
|
|
675
|
+
const source = await readFile(file, 'utf8')
|
|
676
|
+
// The runtime load plugin's own test: a file it would rewrite is not native.
|
|
677
|
+
if (!nodeModuleSourceLoadsAsIs(source)) return undefined
|
|
678
|
+
const policy = getExternalPackagePolicy()
|
|
679
|
+
const loads: string[] = []
|
|
680
|
+
for (const { path: specifier, kind } of nativeImportScanner.scanImports(source)) {
|
|
681
|
+
if (isBuiltin(specifier) || isBunBuiltin(specifier)) continue
|
|
682
|
+
const packageName = isPackageSpecifier(specifier)
|
|
683
|
+
? packageNameFromSpecifier(specifier)
|
|
684
|
+
: undefined
|
|
685
|
+
// A serverExternalPackages import already loads natively through the runtime resolver.
|
|
686
|
+
if (packageName && policy.external(packageName)) continue
|
|
687
|
+
if (packageName && policy.transpile(packageName)) return undefined
|
|
688
|
+
// Unresolvable fails the same way bundled (a bare external), so it decides nothing.
|
|
689
|
+
const loaded = nativeResolve(specifier, path.dirname(file))
|
|
690
|
+
if (!loaded) continue
|
|
691
|
+
if (packageName && nativeImportDiffers(file, specifier, kind, loaded)) return undefined
|
|
692
|
+
loads.push(loaded)
|
|
693
|
+
}
|
|
694
|
+
return loads
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
async function nativeServerGraph(entry: string) {
|
|
698
|
+
const seen = new Set<string>()
|
|
699
|
+
let level = [entry]
|
|
700
|
+
while (level.length > 0) {
|
|
701
|
+
const fresh = [...new Set(level)].filter(file => !seen.has(file))
|
|
702
|
+
for (const file of fresh) seen.add(file)
|
|
703
|
+
const loads = await Promise.all(fresh.map(nativeServerFile))
|
|
704
|
+
if (loads.some(found => !found)) return undefined
|
|
705
|
+
level = loads.flat() as string[]
|
|
706
|
+
}
|
|
707
|
+
return seen
|
|
162
708
|
}
|
|
163
709
|
|
|
164
710
|
/**
|
|
@@ -295,7 +841,7 @@ function vendorBuildPlan(
|
|
|
295
841
|
async function writeVendorArtifact(file: string, code: string) {
|
|
296
842
|
const id = await publishVendorContent(file, code)
|
|
297
843
|
if (id === undefined) {
|
|
298
|
-
await writeFileAtomic(file, code)
|
|
844
|
+
await writeFileAtomic(file, releaseArtifactSource(file, code))
|
|
299
845
|
noteDevArtifactWritten(file)
|
|
300
846
|
}
|
|
301
847
|
return id
|
|
@@ -316,9 +862,17 @@ function vendorContentFile(vendorDir: string, id: string) {
|
|
|
316
862
|
async function publishVendorContent(file: string, code: string) {
|
|
317
863
|
const vendorDir = path.dirname(file)
|
|
318
864
|
if (vendorContentDisabled() || path.basename(vendorDir) !== 'vendor') return undefined
|
|
865
|
+
// First writer wins (see `linkVendorArtifact`): an alias already published keeps its bytes.
|
|
866
|
+
const published = /^([0-9a-f]+)\.content\.mjs$/.exec(publishedVendorContent(file) ?? '')?.[1]
|
|
867
|
+
if (published) {
|
|
868
|
+
noteVendorContentId(file, published)
|
|
869
|
+
noteDevArtifactWritten(file)
|
|
870
|
+
return published
|
|
871
|
+
}
|
|
319
872
|
const id = hashBundleSpecifier(canonicalVendorCode(code))
|
|
320
873
|
const content = vendorContentFile(vendorDir, id)
|
|
321
|
-
if (!cachedExistsSync(content))
|
|
874
|
+
if (!cachedExistsSync(content))
|
|
875
|
+
await writeFileAtomic(content, releaseArtifactSource(content, code))
|
|
322
876
|
noteDevArtifactWritten(content)
|
|
323
877
|
await linkVendorArtifact(file, content)
|
|
324
878
|
noteVendorContentId(file, id)
|
|
@@ -326,16 +880,40 @@ async function publishVendorContent(file: string, code: string) {
|
|
|
326
880
|
return id
|
|
327
881
|
}
|
|
328
882
|
|
|
329
|
-
|
|
883
|
+
function publishedVendorContent(file: string) {
|
|
884
|
+
try {
|
|
885
|
+
return existsSync(file) ? readlinkSync(file) : undefined
|
|
886
|
+
} catch {
|
|
887
|
+
return undefined
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
/**
|
|
892
|
+
* The layer-keyed path becomes an alias to the shared bytes. The first writer wins: a build and its
|
|
893
|
+
* warm child both publish, and re-pointing a live alias hands later importers a second module
|
|
894
|
+
* instance (the loader keys on the real path). Only a dangling alias is replaced, by rename, so a
|
|
895
|
+
* reader never sees it missing.
|
|
896
|
+
*/
|
|
330
897
|
async function linkVendorArtifact(file: string, content: string) {
|
|
331
|
-
|
|
898
|
+
const link = (at: string) => symlink(path.basename(content), at)
|
|
899
|
+
try {
|
|
900
|
+
await link(file)
|
|
901
|
+
return
|
|
902
|
+
} catch (error) {
|
|
903
|
+
if ((error as NodeJS.ErrnoException).code === 'EEXIST' && existsSync(file)) return
|
|
904
|
+
}
|
|
905
|
+
const temp = `${file}.${process.pid.toString(36)}.${(++vendorLinkSequence).toString(36)}.link`
|
|
332
906
|
try {
|
|
333
|
-
await
|
|
907
|
+
await link(temp)
|
|
908
|
+
await rename(temp, file)
|
|
334
909
|
} catch {
|
|
910
|
+
await rm(temp, { force: true }).catch(() => undefined)
|
|
335
911
|
await copyFile(content, file)
|
|
336
912
|
}
|
|
337
913
|
}
|
|
338
914
|
|
|
915
|
+
let vendorLinkSequence = 0
|
|
916
|
+
|
|
339
917
|
/** What one layer's build of an entry saw, for the cross-layer comparison. */
|
|
340
918
|
interface VendorLayerRecord {
|
|
341
919
|
inputs: string
|
|
@@ -1030,9 +1608,23 @@ function vendorGroupPlan(
|
|
|
1030
1608
|
const packageName = packageNameFromSpecifier(member.specifier)
|
|
1031
1609
|
const packageRoot = packageName && vendorPackageRoot(member.entry, packageName)
|
|
1032
1610
|
if (!packageName || !packageRoot || !isEsmModuleEntry(member.entry)) return undefined
|
|
1611
|
+
const key = `${config.outPath}\0${target}\0${conditionTarget}\0${packageRoot}`
|
|
1033
1612
|
return {
|
|
1034
|
-
key
|
|
1613
|
+
key,
|
|
1035
1614
|
member,
|
|
1615
|
+
companions:
|
|
1616
|
+
target === 'server'
|
|
1617
|
+
? () =>
|
|
1618
|
+
stateSharingCompanions(
|
|
1619
|
+
config,
|
|
1620
|
+
key,
|
|
1621
|
+
packageName,
|
|
1622
|
+
packageRoot,
|
|
1623
|
+
member,
|
|
1624
|
+
resolveDir,
|
|
1625
|
+
conditionTarget,
|
|
1626
|
+
)
|
|
1627
|
+
: undefined,
|
|
1036
1628
|
build: (members, external, nested) =>
|
|
1037
1629
|
buildVendorGroup(
|
|
1038
1630
|
config,
|
|
@@ -1047,6 +1639,122 @@ function vendorGroupPlan(
|
|
|
1047
1639
|
}
|
|
1048
1640
|
}
|
|
1049
1641
|
|
|
1642
|
+
/**
|
|
1643
|
+
* A package bundled on the server keeps one instance of a stateful file only if every subpath
|
|
1644
|
+
* reaching it compiles in one splitting build. So the first demand pulls in the published subpaths
|
|
1645
|
+
* that share a stateful file with it (better-auth's `defineRequestState(...)` slots, a React
|
|
1646
|
+
* context), and nothing else: most packages load natively, and a build of every published subpath
|
|
1647
|
+
* would also run code the app never imports.
|
|
1648
|
+
*/
|
|
1649
|
+
function stateSharingCompanions(
|
|
1650
|
+
config: ResolvedConfig,
|
|
1651
|
+
groupKey: string,
|
|
1652
|
+
packageName: string,
|
|
1653
|
+
packageRoot: string,
|
|
1654
|
+
member: VendorGroupMember,
|
|
1655
|
+
resolveDir: string,
|
|
1656
|
+
conditionTarget: ServerBundleTarget,
|
|
1657
|
+
) {
|
|
1658
|
+
const key = `${groupKey}\0${member.specifier}`
|
|
1659
|
+
let companions = stateCompanions.get(key)
|
|
1660
|
+
if (!companions) {
|
|
1661
|
+
companions = (async () => {
|
|
1662
|
+
const shared = await statefulPackageFiles(member.entry, packageRoot)
|
|
1663
|
+
if (shared.length === 0) return []
|
|
1664
|
+
const found: VendorGroupMember[] = []
|
|
1665
|
+
for (const subpath of packageExportSubpaths(packageRoot)) {
|
|
1666
|
+
const specifier = subpath === '.' ? packageName : `${packageName}${subpath.slice(1)}`
|
|
1667
|
+
if (specifier === member.specifier) continue
|
|
1668
|
+
const sibling = vendorBuildPlan(
|
|
1669
|
+
config,
|
|
1670
|
+
specifier,
|
|
1671
|
+
'server',
|
|
1672
|
+
resolveDir,
|
|
1673
|
+
conditionTarget,
|
|
1674
|
+
).group
|
|
1675
|
+
if (sibling?.key !== groupKey || existsSync(sibling.member.file)) continue
|
|
1676
|
+
const reach = await packageReach(sibling.member.entry, packageRoot)
|
|
1677
|
+
if (shared.some(file => reach.has(file))) found.push(sibling.member)
|
|
1678
|
+
}
|
|
1679
|
+
return found.length <= MAX_STATE_COMPANIONS ? found : []
|
|
1680
|
+
})().catch(() => [])
|
|
1681
|
+
stateCompanions.set(key, companions)
|
|
1682
|
+
}
|
|
1683
|
+
return companions
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
/** Past this many, the package is a hub of adapters; it keeps a build per demanded subpath. */
|
|
1687
|
+
const MAX_STATE_COMPANIONS = 16
|
|
1688
|
+
const stateCompanions = new Map<string, Promise<VendorGroupMember[]>>()
|
|
1689
|
+
|
|
1690
|
+
function packageExportSubpaths(packageRoot: string) {
|
|
1691
|
+
try {
|
|
1692
|
+
const { exports } = JSON.parse(
|
|
1693
|
+
readFileSync(path.join(packageRoot, 'package.json'), 'utf8'),
|
|
1694
|
+
) as {
|
|
1695
|
+
exports?: unknown
|
|
1696
|
+
}
|
|
1697
|
+
if (!exports || typeof exports !== 'object' || Array.isArray(exports)) return []
|
|
1698
|
+
return Object.keys(exports).filter(
|
|
1699
|
+
subpath => subpath.startsWith('.') && !subpath.includes('*') && !subpath.endsWith('.json'),
|
|
1700
|
+
)
|
|
1701
|
+
} catch {
|
|
1702
|
+
return []
|
|
1703
|
+
}
|
|
1704
|
+
}
|
|
1705
|
+
|
|
1706
|
+
/** A top-level binding created by `new` or a non-PURE call: an identity each copy would duplicate. */
|
|
1707
|
+
const MODULE_STATE =
|
|
1708
|
+
/^(?:export\s+)?(?:const|let|var)\s[^=;]*=\s*(?:(?:\/\*\s*[#@]__PURE__\s*\*\/\s*)?new\s|(?!require\s*\()[\w$.]+\s*\()/m
|
|
1709
|
+
|
|
1710
|
+
async function statefulPackageFiles(entry: string, packageRoot: string) {
|
|
1711
|
+
const files = [...(await packageReach(entry, packageRoot))]
|
|
1712
|
+
const sources = await Promise.all(files.map(file => packageFileScan(file)))
|
|
1713
|
+
return files.filter((_, index) => sources[index]?.stateful)
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
const packageFileScans = new Map<
|
|
1717
|
+
string,
|
|
1718
|
+
Promise<{ imports: string[]; stateful: boolean } | undefined>
|
|
1719
|
+
>()
|
|
1720
|
+
const packageScanner = new Bun.Transpiler({ loader: 'js' })
|
|
1721
|
+
|
|
1722
|
+
function packageFileScan(file: string) {
|
|
1723
|
+
let scan = packageFileScans.get(file)
|
|
1724
|
+
if (!scan) {
|
|
1725
|
+
scan = readFile(file, 'utf8')
|
|
1726
|
+
.then(source => ({
|
|
1727
|
+
imports: packageScanner
|
|
1728
|
+
.scanImports(source)
|
|
1729
|
+
.map(({ path: specifier }) => specifier)
|
|
1730
|
+
.filter(specifier => specifier.startsWith('.')),
|
|
1731
|
+
stateful: MODULE_STATE.test(source),
|
|
1732
|
+
}))
|
|
1733
|
+
.catch(() => undefined)
|
|
1734
|
+
packageFileScans.set(file, scan)
|
|
1735
|
+
}
|
|
1736
|
+
return scan
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
/** The package's own files `entry` reaches through relative imports. */
|
|
1740
|
+
async function packageReach(entry: string, packageRoot: string) {
|
|
1741
|
+
const seen = new Set<string>()
|
|
1742
|
+
let level = [entry]
|
|
1743
|
+
while (level.length > 0) {
|
|
1744
|
+
const fresh = [...new Set(level)].filter(
|
|
1745
|
+
file => !seen.has(file) && file.startsWith(packageRoot + path.sep) && /\.[cm]?js$/.test(file),
|
|
1746
|
+
)
|
|
1747
|
+
for (const file of fresh) seen.add(file)
|
|
1748
|
+
const scans = await Promise.all(fresh.map(packageFileScan))
|
|
1749
|
+
level = scans.flatMap((scan, index) =>
|
|
1750
|
+
(scan?.imports ?? []).flatMap(
|
|
1751
|
+
specifier => nativeResolve(specifier, path.dirname(fresh[index]!)) ?? [],
|
|
1752
|
+
),
|
|
1753
|
+
)
|
|
1754
|
+
}
|
|
1755
|
+
return seen
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1050
1758
|
/**
|
|
1051
1759
|
* The installed package directory an entry belongs to — the grouping identity,
|
|
1052
1760
|
* so a nested (non-hoisted) copy never shares a group with the hoisted one.
|
|
@@ -2666,9 +3374,12 @@ function externalBuiltinBuildPlugin(): Plugin {
|
|
|
2666
3374
|
setup(build) {
|
|
2667
3375
|
build.onResolve({ filter: /^[^./].*/ }, args => {
|
|
2668
3376
|
const packageName = packageNameFromSpecifier(args.path)
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
3377
|
+
// `isBuiltin` also knows the `node:`-only ones (`node:sqlite`, `node:test`).
|
|
3378
|
+
const builtin =
|
|
3379
|
+
isBuiltin(args.path) ||
|
|
3380
|
+
builtinModules.includes(packageName ?? '') ||
|
|
3381
|
+
isBunBuiltin(packageName ?? '')
|
|
3382
|
+
if (!packageName || !builtin) return undefined
|
|
2672
3383
|
return { path: args.path, external: true }
|
|
2673
3384
|
})
|
|
2674
3385
|
},
|