@wular/pnext 0.1.6 → 0.1.8
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-pycbtjt1.js +46 -0
- package/dist/server/_chunks/analyze-4d9e00g9.js +449 -0
- package/dist/server/_chunks/boot-bhemypxz.js +38 -0
- package/dist/server/_chunks/build-c991dv65.js +86 -0
- package/dist/server/_chunks/build-kh5e3x48.js +3341 -0
- package/dist/server/_chunks/build-tier-0a96kmx3.js +2456 -0
- package/dist/server/_chunks/compile-040r79gj.js +22 -0
- package/dist/server/_chunks/config-0tvz03a6.js +24 -0
- package/dist/server/_chunks/config-27rj7830.js +36 -0
- package/dist/server/_chunks/create-9rvwshsp.js +135 -0
- package/dist/server/_chunks/dev-ex9exynt.js +220 -0
- package/dist/server/_chunks/esbuild-yyktfrtt.js +15 -0
- package/dist/server/_chunks/generate-myw9hef7.js +22 -0
- package/dist/server/_chunks/index-0r5eqfc7.js +279 -0
- package/dist/server/_chunks/index-0yjv5v47.js +105 -0
- package/dist/server/_chunks/index-11xpbfej.js +17 -0
- package/dist/server/_chunks/index-141vcwk6.js +109 -0
- package/dist/server/_chunks/index-1pzm1hdy.js +4130 -0
- package/dist/server/_chunks/index-29pq0kax.js +590 -0
- package/dist/server/_chunks/index-2vz0wzdn.js +59 -0
- package/dist/server/_chunks/index-2wfv4kvx.js +182 -0
- package/dist/server/_chunks/index-38xpdxwd.js +194 -0
- package/dist/server/_chunks/index-3e075kny.js +128 -0
- package/dist/server/_chunks/index-3ycf5ckv.js +472 -0
- package/dist/server/_chunks/index-4399bvd2.js +18 -0
- package/dist/server/_chunks/index-4da76t62.js +297 -0
- package/dist/server/_chunks/index-4t92hdxd.js +172 -0
- package/dist/server/_chunks/index-4zzebszd.js +10 -0
- package/dist/server/_chunks/index-52k54ykj.js +95 -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-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-71885zaq.js +284 -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-8ct7rqy3.js +198 -0
- package/dist/server/_chunks/index-8kcxdfya.js +206 -0
- package/dist/server/_chunks/index-9ch50vtq.js +18 -0
- package/dist/server/_chunks/index-9f49w76j.js +213 -0
- package/dist/server/_chunks/index-9hz3s0m8.js +851 -0
- package/dist/server/_chunks/index-akkrvjn6.js +535 -0
- package/dist/server/_chunks/index-b6wtkdp4.js +24 -0
- package/dist/server/_chunks/index-b7j671dj.js +207 -0
- package/dist/server/_chunks/index-bcws5eh2.js +35 -0
- package/dist/server/_chunks/index-bzd2ef7t.js +10 -0
- package/dist/server/_chunks/index-cjgqs13g.js +65 -0
- package/dist/server/_chunks/index-ckkdpht5.js +41 -0
- package/dist/server/_chunks/index-cn86z041.js +296 -0
- package/dist/server/_chunks/index-cvr4z22m.js +92 -0
- package/dist/server/_chunks/index-cyfednwb.js +87 -0
- package/dist/server/_chunks/index-d158y67j.js +152 -0
- package/dist/server/_chunks/index-d1jsmhhk.js +955 -0
- package/dist/server/_chunks/index-dg0f05zs.js +199 -0
- package/dist/server/_chunks/index-dgr390q7.js +9438 -0
- package/dist/server/_chunks/index-drwv61cc.js +185 -0
- package/dist/server/_chunks/index-e3a6yx84.js +18 -0
- package/dist/server/_chunks/index-e88tyv35.js +45 -0
- package/dist/server/_chunks/index-en7n9f7m.js +22 -0
- package/dist/server/_chunks/index-entm8bmz.js +28 -0
- package/dist/server/_chunks/index-evhd413j.js +57 -0
- package/dist/server/_chunks/index-ezgj0tr1.js +88 -0
- package/dist/server/_chunks/index-fjv96re1.js +5295 -0
- package/dist/server/_chunks/index-fn1chkkd.js +214 -0
- package/dist/server/_chunks/index-fp8r0sqs.js +64 -0
- package/dist/server/_chunks/index-gcmj16xy.js +92 -0
- package/dist/server/_chunks/index-gkndxkxf.js +736 -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-hdbwtdv2.js +352 -0
- package/dist/server/_chunks/index-he6n2bvp.js +1270 -0
- package/dist/server/_chunks/index-j0m1jxqa.js +1253 -0
- package/dist/server/_chunks/index-j2zrcp1k.js +66 -0
- package/dist/server/_chunks/index-j681c48a.js +2530 -0
- package/dist/server/_chunks/index-jbj7ehef.js +50 -0
- package/dist/server/_chunks/index-jcsjq3d1.js +20 -0
- package/dist/server/_chunks/index-jks0x0a1.js +14 -0
- package/dist/server/_chunks/index-jn9rbhrx.js +1599 -0
- package/dist/server/_chunks/index-jt8e1pje.js +12 -0
- package/dist/server/_chunks/index-kdhxpwyq.js +483 -0
- package/dist/server/_chunks/index-knp0s912.js +7 -0
- package/dist/server/_chunks/index-kqyy1d4f.js +50 -0
- package/dist/server/_chunks/index-kresp2n8.js +445 -0
- package/dist/server/_chunks/index-m63czgq5.js +590 -0
- package/dist/server/_chunks/index-ms9v9tnx.js +31 -0
- package/dist/server/_chunks/index-n4hxpng2.js +11 -0
- package/dist/server/_chunks/index-n629dy38.js +1071 -0
- package/dist/server/_chunks/index-nf3df4jy.js +266 -0
- package/dist/server/_chunks/index-nqh3xhdc.js +57 -0
- package/dist/server/_chunks/index-pym1cezq.js +39 -0
- package/dist/server/_chunks/index-q3r9z8mq.js +213 -0
- package/dist/server/_chunks/index-q738kkhj.js +100 -0
- package/dist/server/_chunks/index-qcrvt6er.js +112 -0
- package/dist/server/_chunks/index-qjj6gddn.js +54 -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-rgj5mx84.js +101 -0
- package/dist/server/_chunks/index-rjegyfx4.js +1435 -0
- package/dist/server/_chunks/index-rt9qq1va.js +4 -0
- package/dist/server/_chunks/index-rthrsed5.js +356 -0
- package/dist/server/_chunks/index-sq3kvz3g.js +200 -0
- package/dist/server/_chunks/index-stbk3jhn.js +31 -0
- package/dist/server/_chunks/index-tf4j19ge.js +130 -0
- package/dist/server/_chunks/index-tgcakdxc.js +125 -0
- package/dist/server/_chunks/index-tgsfjmep.js +74 -0
- package/dist/server/_chunks/index-tq1f96v7.js +51 -0
- package/dist/server/_chunks/index-tq3b39a5.js +107 -0
- package/dist/server/_chunks/index-typcbcsv.js +819 -0
- package/dist/server/_chunks/index-v1ags1na.js +132 -0
- package/dist/server/_chunks/index-vmr35jjw.js +170 -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-wm36rj4v.js +311 -0
- package/dist/server/_chunks/index-x0y005nh.js +283 -0
- package/dist/server/_chunks/index-x2m17v9c.js +6166 -0
- package/dist/server/_chunks/index-x3yn4ywm.js +22 -0
- package/dist/server/_chunks/index-x9c55pzy.js +871 -0
- package/dist/server/_chunks/index-xh1nbk46.js +1299 -0
- package/dist/server/_chunks/index-xvj80c3a.js +30 -0
- package/dist/server/_chunks/index-xywjg2vc.js +100 -0
- package/dist/server/_chunks/index-y4d02znn.js +66 -0
- package/dist/server/_chunks/index-y7mzvq2q.js +110 -0
- package/dist/server/_chunks/index-ye9hmsvf.js +47 -0
- package/dist/server/_chunks/index-yhxkxghe.js +56 -0
- package/dist/server/_chunks/index-yrjkqtfj.js +86 -0
- package/dist/server/_chunks/index-z88qv16t.js +11 -0
- package/dist/server/_chunks/index-zfrwq472.js +154 -0
- package/dist/server/_chunks/index-zqpqyg8p.js +666 -0
- package/dist/server/_chunks/loader-5en7wpt5.js +80 -0
- package/dist/server/_chunks/modules-t92favpj.js +76 -0
- package/dist/server/_chunks/pipeline-4t9q43ht.js +836 -0
- package/dist/server/_chunks/production-385bw8df.js +55 -0
- package/dist/server/_chunks/proxy-config-60gprphe.js +23 -0
- package/dist/server/_chunks/proxy-gfhmktbk.js +35 -0
- package/dist/server/_chunks/router-xnrva921.js +501 -0
- package/dist/server/_chunks/routes-ht868pdc.js +66 -0
- package/dist/server/_chunks/run-d40d63ag.js +697 -0
- package/dist/server/_chunks/server-2p8fgqq2.js +1839 -0
- package/dist/server/_chunks/tsconfig-defaults-bcn34eqb.js +21 -0
- package/dist/server/_chunks/typegen-dvtcjtaw.js +37 -0
- package/dist/server/api/client-cache.js +14 -0
- package/dist/server/cli/index.js +375 -0
- package/dist/server/cli/start.js +158 -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 +34 -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 +21 -0
- package/dist/server/compat/next/error.js +112 -0
- package/dist/server/compat/next/font/cache.js +16 -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 +24 -0
- package/dist/server/compat/next/font/runtime-client.js +33 -0
- package/dist/server/compat/next/font/runtime.js +43 -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 +15 -0
- package/dist/server/compat/next/link.js +14 -0
- package/dist/server/compat/next/navigation.js +191 -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 +32 -0
- package/dist/server/compat/next/resource-hints.js +30 -0
- package/dist/server/compat/next/rewrites.js +43 -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/reference/compat.md +1 -1
- package/reference/config.md +2 -2
- package/reference/dev.md +1 -1
- package/reference/getting-started.md +1 -1
- package/reference/rendering.md +4 -4
- package/src/api/dynamic.tsx +86 -4
- package/src/api/server.ts +24 -6
- package/src/cli/adapters/vercel-warm.ts +327 -62
- package/src/cli/adapters/vercel.ts +164 -1054
- package/src/cli/analyze.ts +3 -2
- package/src/cli/build/cache.ts +6 -6
- package/src/cli/build.ts +78 -45
- package/src/cli/index.ts +21 -3
- package/src/cli/serve/immutable.ts +31 -0
- package/src/cli/serve/pipeline.ts +97 -93
- package/src/cli/serve/standalone-worker.ts +8 -0
- package/src/cli/serve/standalone.ts +703 -0
- package/src/cli/start.ts +31 -8
- package/src/client/build.ts +1 -1
- package/src/client/chunk-fold.ts +19 -6
- package/src/client/react-tier.ts +3 -2
- package/src/client/reference-stub.ts +23 -36
- package/src/compat/actions/client-plugin.ts +4 -2
- package/src/compat/actions/config.ts +6 -5
- package/src/compat/actions/extensions.ts +5 -5
- 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/bundler/require-context.ts +2 -1
- package/src/compat/bundler/webpack-loaders.ts +3 -2
- package/src/compat/bundler/worker.ts +2 -2
- package/src/compat/cache-control.ts +16 -8
- package/src/compat/css/inline-css.ts +1 -1
- package/src/compat/css/modules.ts +18 -5
- package/src/compat/export/build-complete.ts +1 -1
- package/src/compat/export/emit.ts +4 -4
- package/src/compat/export/standalone.ts +30 -65
- package/src/compat/image-optimizer/optimize.ts +1 -1
- package/src/compat/lifecycle/error-funnel.ts +1 -1
- package/src/compat/lifecycle/instrumentation-client.ts +4 -3
- package/src/compat/lifecycle/instrumentation.ts +117 -24
- package/src/compat/lifecycle/testmode.ts +3 -2
- package/src/compat/metadata-artifacts.ts +1 -1
- package/src/compat/next/config-loader.ts +140 -13
- package/src/compat/next/dynamic.tsx +10 -23
- package/src/compat/next/font/cache.ts +2 -1
- package/src/compat/next/font/runtime.ts +23 -3
- 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/image.ts +1 -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/typecheck/check.ts +6 -4
- package/src/compat/validation/validate.ts +10 -7
- package/src/config.ts +76 -9
- package/src/css/assets.ts +213 -0
- package/src/css/build.ts +190 -247
- package/src/dev/client-chunk-store.ts +2 -1
- package/src/dev/restart/client-key.ts +2 -1
- package/src/dev/restart/global-css.ts +2 -1
- package/src/dev/restart/route-bundle-key.ts +2 -1
- package/src/dev/restart/route-facts.ts +2 -1
- package/src/dev/server.ts +34 -14
- package/src/extensions.ts +4 -0
- package/src/out-paths.ts +10 -0
- package/src/render/hooks.ts +13 -0
- package/src/render/renderer.ts +53 -33
- package/src/request/context.ts +9 -0
- package/src/resolve/dynamic.ts +7 -1
- package/src/resolve/engine.ts +2 -12
- package/src/resolve/imports.ts +12 -52
- package/src/resolve/package-json.ts +56 -0
- package/src/resolve/scan-facts.ts +250 -1
- package/src/routing/href.ts +10 -3
- package/src/routing/match.ts +287 -0
- package/src/routing/metadata-files.ts +20 -6
- 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 +218 -44
- package/src/runtime/module-cache.ts +15 -1
- package/src/runtime/modules.ts +405 -82
- package/src/runtime/prebundle.ts +101 -0
- package/src/runtime/production.ts +456 -0
- package/src/runtime/release-graph.ts +342 -0
- package/src/runtime/vendor-build.ts +750 -17
- package/src/runtime/vendor.ts +16 -3
- package/src/types.ts +4 -3
- package/src/utils/directive.ts +40 -0
- package/src/utils/fs.ts +95 -4
- package/src/utils/resource-query.ts +12 -0
- package/src/cli/serve/entry.ts +0 -45
package/src/resolve/imports.ts
CHANGED
|
@@ -10,6 +10,9 @@ import {
|
|
|
10
10
|
tsConfigFileFor,
|
|
11
11
|
} from './engine'
|
|
12
12
|
import { onExtensionHostReset } from '../extensions'
|
|
13
|
+
import { findWorkspaceRoot, readPackageJson, type PackageJson } from './package-json'
|
|
14
|
+
|
|
15
|
+
export { findWorkspaceRoot }
|
|
13
16
|
|
|
14
17
|
// `Bun` through globalThis: pnext ships TypeScript source, so app compilers without
|
|
15
18
|
// bun-types typecheck this file and a bare `Bun` identifier would fail them.
|
|
@@ -45,6 +48,8 @@ export interface ExternalPackagePolicy {
|
|
|
45
48
|
* transpile.
|
|
46
49
|
*/
|
|
47
50
|
transpiled?: () => readonly string[]
|
|
51
|
+
/** Kept out of a release's server dependency graph beyond `external`: Next's built-in list. */
|
|
52
|
+
releaseExternal?: (packageName: string) => boolean
|
|
48
53
|
/** ESM external mode for package exports interop. */
|
|
49
54
|
esmExternals: () => boolean | 'loose'
|
|
50
55
|
}
|
|
@@ -94,9 +99,14 @@ export function resolveExternalLoadTarget(context: ExternalLoadContext): string
|
|
|
94
99
|
// `#imports` resolve from the importer's OWN package, so they are not
|
|
95
100
|
// shareable across a resolution root; they stay uncached.
|
|
96
101
|
if (context.specifier.startsWith('#')) return externalLoadResolver(context)
|
|
97
|
-
|
|
102
|
+
// A node_modules importer resolves from its own directory, as Node does: a version nested under
|
|
103
|
+
// its package wins over the hoisted one.
|
|
104
|
+
const resolveContext = context.fromFile.includes(`${path.sep}node_modules${path.sep}`)
|
|
105
|
+
? { ...context, root: path.dirname(context.fromFile) }
|
|
106
|
+
: context
|
|
107
|
+
const key = `${context.target}\0${resolutionRootForFile(resolveContext.root, context.fromFile)}\0${context.specifier}`
|
|
98
108
|
if (externalLoadTargets.has(key)) return externalLoadTargets.get(key)
|
|
99
|
-
const target = externalLoadResolver(
|
|
109
|
+
const target = externalLoadResolver(resolveContext)
|
|
100
110
|
externalLoadTargets.set(key, target)
|
|
101
111
|
return target
|
|
102
112
|
}
|
|
@@ -143,22 +153,6 @@ export function setTsConfigPath(file: string | undefined): void {
|
|
|
143
153
|
clearVendorPackageResolutions()
|
|
144
154
|
}
|
|
145
155
|
|
|
146
|
-
interface PackageJson {
|
|
147
|
-
name?: string
|
|
148
|
-
type?: string
|
|
149
|
-
browser?: string
|
|
150
|
-
main?: string
|
|
151
|
-
module?: string
|
|
152
|
-
source?: string
|
|
153
|
-
exports?: PackageExport
|
|
154
|
-
imports?: Record<string, PackageExport>
|
|
155
|
-
dependencies?: Record<string, string>
|
|
156
|
-
devDependencies?: Record<string, string>
|
|
157
|
-
optionalDependencies?: Record<string, string>
|
|
158
|
-
peerDependencies?: Record<string, string>
|
|
159
|
-
workspaces?: string[] | { packages?: string[] }
|
|
160
|
-
}
|
|
161
|
-
|
|
162
156
|
// Compat (or any bundler extension) registers its bare-specifier to file aliases here (e.g. `next/form` ->
|
|
163
157
|
// `src/compat/next/form.tsx`). Core's own route scanner has no static knowledge of those aliases otherwise,
|
|
164
158
|
// so a server component importing a 'use client' compat module would never be walked into and its client
|
|
@@ -886,25 +880,6 @@ export function commonJsModuleHasDefaultExport(source: string, file: string) {
|
|
|
886
880
|
)
|
|
887
881
|
}
|
|
888
882
|
|
|
889
|
-
export function findWorkspaceRoot(root: string) {
|
|
890
|
-
const key = path.resolve(root)
|
|
891
|
-
if (workspaceRootCache.has(key)) return workspaceRootCache.get(key)
|
|
892
|
-
|
|
893
|
-
let dir = key
|
|
894
|
-
while (true) {
|
|
895
|
-
if (readPackageJson(dir).workspaces || existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
|
|
896
|
-
workspaceRootCache.set(key, dir)
|
|
897
|
-
return dir
|
|
898
|
-
}
|
|
899
|
-
const parent = path.dirname(dir)
|
|
900
|
-
if (parent === dir) {
|
|
901
|
-
workspaceRootCache.set(key, undefined)
|
|
902
|
-
return undefined
|
|
903
|
-
}
|
|
904
|
-
dir = parent
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
|
|
908
883
|
function isInside(root: string, file: string) {
|
|
909
884
|
const relative = path.relative(root, file)
|
|
910
885
|
return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative))
|
|
@@ -962,8 +937,6 @@ function readPnpmWorkspacePatterns(root: string) {
|
|
|
962
937
|
}
|
|
963
938
|
|
|
964
939
|
const tsConfigCache = new Map<string, TsConfig>()
|
|
965
|
-
const packageJsonCache = new Map<string, PackageJson>()
|
|
966
|
-
const workspaceRootCache = new Map<string, string | undefined>()
|
|
967
940
|
const workspacePackageCache = new Map<string, Map<string, string>>()
|
|
968
941
|
|
|
969
942
|
function readTsConfig(root: string) {
|
|
@@ -975,14 +948,6 @@ function readTsConfig(root: string) {
|
|
|
975
948
|
return config
|
|
976
949
|
}
|
|
977
950
|
|
|
978
|
-
function readPackageJson(root: string) {
|
|
979
|
-
const existing = packageJsonCache.get(root)
|
|
980
|
-
if (existing) return existing
|
|
981
|
-
const config = readJson<PackageJson>(path.join(root, 'package.json')) ?? {}
|
|
982
|
-
packageJsonCache.set(root, config)
|
|
983
|
-
return config
|
|
984
|
-
}
|
|
985
|
-
|
|
986
951
|
function workspacePackages(root: string) {
|
|
987
952
|
const existing = workspacePackageCache.get(root)
|
|
988
953
|
if (existing) return existing
|
|
@@ -1027,11 +992,6 @@ export function workspacePackageRoots(root: string) {
|
|
|
1027
992
|
return [...workspacePackages(path.resolve(root)).values()].sort()
|
|
1028
993
|
}
|
|
1029
994
|
|
|
1030
|
-
function readJson<T>(file: string) {
|
|
1031
|
-
if (!existsSync(file)) return undefined
|
|
1032
|
-
return JSON.parse(readFileSync(file, 'utf8')) as T
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
995
|
function readJsonc<T>(file: string) {
|
|
1036
996
|
if (!existsSync(file)) return undefined
|
|
1037
997
|
return JSON.parse(stripTrailingCommas(stripJsonComments(readFileSync(file, 'utf8')))) as T
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// package.json reads and workspace-root discovery, which config loading needs without the resolver.
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
type PackageExport = unknown
|
|
6
|
+
|
|
7
|
+
export interface PackageJson {
|
|
8
|
+
name?: string
|
|
9
|
+
type?: string
|
|
10
|
+
browser?: string
|
|
11
|
+
main?: string
|
|
12
|
+
module?: string
|
|
13
|
+
source?: string
|
|
14
|
+
exports?: PackageExport
|
|
15
|
+
imports?: Record<string, PackageExport>
|
|
16
|
+
dependencies?: Record<string, string>
|
|
17
|
+
devDependencies?: Record<string, string>
|
|
18
|
+
optionalDependencies?: Record<string, string>
|
|
19
|
+
peerDependencies?: Record<string, string>
|
|
20
|
+
workspaces?: string[] | { packages?: string[] }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const packageJsonCache = new Map<string, PackageJson>()
|
|
24
|
+
const workspaceRootCache = new Map<string, string | undefined>()
|
|
25
|
+
|
|
26
|
+
export function readPackageJson(root: string) {
|
|
27
|
+
const existing = packageJsonCache.get(root)
|
|
28
|
+
if (existing) return existing
|
|
29
|
+
const config = readJson<PackageJson>(path.join(root, 'package.json')) ?? {}
|
|
30
|
+
packageJsonCache.set(root, config)
|
|
31
|
+
return config
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function findWorkspaceRoot(root: string) {
|
|
35
|
+
const key = path.resolve(root)
|
|
36
|
+
if (workspaceRootCache.has(key)) return workspaceRootCache.get(key)
|
|
37
|
+
|
|
38
|
+
let dir = key
|
|
39
|
+
while (true) {
|
|
40
|
+
if (readPackageJson(dir).workspaces || existsSync(path.join(dir, 'pnpm-workspace.yaml'))) {
|
|
41
|
+
workspaceRootCache.set(key, dir)
|
|
42
|
+
return dir
|
|
43
|
+
}
|
|
44
|
+
const parent = path.dirname(dir)
|
|
45
|
+
if (parent === dir) {
|
|
46
|
+
workspaceRootCache.set(key, undefined)
|
|
47
|
+
return undefined
|
|
48
|
+
}
|
|
49
|
+
dir = parent
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function readJson<T>(file: string) {
|
|
54
|
+
if (!existsSync(file)) return undefined
|
|
55
|
+
return JSON.parse(readFileSync(file, 'utf8')) as T
|
|
56
|
+
}
|
|
@@ -76,6 +76,8 @@ export interface RewriteFacts {
|
|
|
76
76
|
edges: SpecifierEdge[]
|
|
77
77
|
imports: ImportStatement[]
|
|
78
78
|
importMetas: { start: number; end: number }[]
|
|
79
|
+
/** `import()` calls whose argument is not a plain string literal. */
|
|
80
|
+
computedImports: { start: number; end: number }[]
|
|
79
81
|
/**
|
|
80
82
|
* The parse recovered from errors, so the record may be incomplete — folded
|
|
81
83
|
* passes fall back to their textual form rather than silently under-rewrite.
|
|
@@ -146,6 +148,7 @@ const emptyRewriteFacts: RewriteFacts = {
|
|
|
146
148
|
edges: [],
|
|
147
149
|
imports: [],
|
|
148
150
|
importMetas: [],
|
|
151
|
+
computedImports: [],
|
|
149
152
|
unreliable: false,
|
|
150
153
|
}
|
|
151
154
|
|
|
@@ -373,10 +376,14 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
|
|
|
373
376
|
imports.sort((a, b) => a.index - b.index)
|
|
374
377
|
|
|
375
378
|
const dynamicImports: { specifier: string; index: number }[] = []
|
|
379
|
+
const computedImports: { start: number; end: number }[] = []
|
|
376
380
|
for (const entry of result.module.dynamicImports) {
|
|
377
381
|
const raw = source.slice(entry.moduleRequest.start, entry.moduleRequest.end)
|
|
378
382
|
const specifier = literalSpecifier(raw)
|
|
379
|
-
if (specifier === undefined)
|
|
383
|
+
if (specifier === undefined) {
|
|
384
|
+
computedImports.push({ start: entry.start, end: entry.end })
|
|
385
|
+
continue
|
|
386
|
+
}
|
|
380
387
|
dynamicImports.push({ specifier, index: entry.start })
|
|
381
388
|
// The literal's own span, without the whitespace `moduleRequest` includes.
|
|
382
389
|
const lead = raw.length - raw.trimStart().length
|
|
@@ -419,6 +426,7 @@ function parseFacts(file: string, source: string, lang: ParserLang) {
|
|
|
419
426
|
edges,
|
|
420
427
|
imports: importStatements,
|
|
421
428
|
importMetas: result.module.importMetas.map(meta => ({ start: meta.start, end: meta.end })),
|
|
429
|
+
computedImports,
|
|
422
430
|
unreliable: result.errors.length > 0,
|
|
423
431
|
},
|
|
424
432
|
}
|
|
@@ -581,6 +589,247 @@ function child(node: AstNode | undefined, key: string): AstNode | undefined {
|
|
|
581
589
|
return isNode(value) ? value : undefined
|
|
582
590
|
}
|
|
583
591
|
|
|
592
|
+
/** A string literal argument: its cooked value and the span of its quoted text. */
|
|
593
|
+
export interface LiteralSpan {
|
|
594
|
+
specifier: string
|
|
595
|
+
start: number
|
|
596
|
+
end: number
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/** How a module refers to its own location (see `importMetaRefs`). */
|
|
600
|
+
export interface ImportMetaRefs {
|
|
601
|
+
/** `new URL('<literal>', import.meta.url)`. */
|
|
602
|
+
urls: LiteralSpan[]
|
|
603
|
+
/** `import.meta.resolve('<literal>')`. */
|
|
604
|
+
resolves: LiteralSpan[]
|
|
605
|
+
/** Reads `import.meta.url`/`dirname`/`filename`/`resolve`, or takes `import.meta` whole. */
|
|
606
|
+
readsLocation: boolean
|
|
607
|
+
/** Spans of each `import()` whose argument is not a string literal. */
|
|
608
|
+
computedImports: { start: number; end: number }[]
|
|
609
|
+
/** The parse recovered from errors: callers keep their conservative answer. */
|
|
610
|
+
unreliable: boolean
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const noMetaRefs: ImportMetaRefs = {
|
|
614
|
+
urls: [],
|
|
615
|
+
resolves: [],
|
|
616
|
+
readsLocation: false,
|
|
617
|
+
computedImports: [],
|
|
618
|
+
unreliable: false,
|
|
619
|
+
}
|
|
620
|
+
const metaRefMemo = new Map<string, ImportMetaRefs>()
|
|
621
|
+
const locationMembers = new Set(['url', 'dirname', 'filename', 'resolve'])
|
|
622
|
+
|
|
623
|
+
/** Every reference a module makes to its own location, from the AST: never a comment or a string. */
|
|
624
|
+
export function importMetaRefs(file: string, source: string): ImportMetaRefs {
|
|
625
|
+
if (stylesheetOrData.test(file)) return noMetaRefs
|
|
626
|
+
if (!source.includes('import.meta') && !source.includes('import(')) return noMetaRefs
|
|
627
|
+
// The memoized module record answers computed imports and whether `import.meta` occurs at all;
|
|
628
|
+
// only a module that really uses it pays for the AST.
|
|
629
|
+
const record = rewriteFacts(file, source)
|
|
630
|
+
if (record.importMetas.length === 0)
|
|
631
|
+
return { ...noMetaRefs, computedImports: record.computedImports, unreliable: record.unreliable }
|
|
632
|
+
const lang = langForFile(file)
|
|
633
|
+
const key = `${lang}\0${source.length}\0${bun.hash(source).toString()}`
|
|
634
|
+
const cached = metaRefMemo.get(key)
|
|
635
|
+
if (cached) return cached
|
|
636
|
+
const result = parseSync(file, source, { lang })
|
|
637
|
+
const refs: ImportMetaRefs = {
|
|
638
|
+
...noMetaRefs,
|
|
639
|
+
urls: [],
|
|
640
|
+
resolves: [],
|
|
641
|
+
computedImports: record.computedImports,
|
|
642
|
+
}
|
|
643
|
+
refs.unreliable = result.errors.length > 0
|
|
644
|
+
const isImportMeta = (node: AstNode | undefined) =>
|
|
645
|
+
node?.type === 'MetaProperty' &&
|
|
646
|
+
child(node, 'meta')?.name === 'import' &&
|
|
647
|
+
child(node, 'property')?.name === 'meta'
|
|
648
|
+
const memberName = (node: AstNode) =>
|
|
649
|
+
node.computed ? stringValue(child(node, 'property')) : (child(node, 'property')?.name as string)
|
|
650
|
+
const literal = (node: AstNode | undefined): LiteralSpan | undefined => {
|
|
651
|
+
if (!node) return undefined
|
|
652
|
+
const value =
|
|
653
|
+
stringValue(node) ??
|
|
654
|
+
(node.type === 'TemplateLiteral' && (node.expressions as unknown[]).length === 0
|
|
655
|
+
? ((node.quasis as AstNode[])[0]?.value as { cooked?: string } | undefined)?.cooked
|
|
656
|
+
: undefined)
|
|
657
|
+
return value === undefined ? undefined : { specifier: value, start: node.start, end: node.end }
|
|
658
|
+
}
|
|
659
|
+
const visit = (value: unknown, parent: AstNode | undefined) => {
|
|
660
|
+
if (Array.isArray(value)) {
|
|
661
|
+
for (const item of value) visit(item, parent)
|
|
662
|
+
return
|
|
663
|
+
}
|
|
664
|
+
if (!isNode(value)) return
|
|
665
|
+
if (isImportMeta(value)) {
|
|
666
|
+
const member = parent?.type === 'MemberExpression' && parent.object === value
|
|
667
|
+
if (!member || locationMembers.has(memberName(parent) ?? '')) refs.readsLocation = true
|
|
668
|
+
} else if (value.type === 'NewExpression' && child(value, 'callee')?.name === 'URL') {
|
|
669
|
+
const [target, base] = value.arguments as AstNode[]
|
|
670
|
+
const span = literal(target)
|
|
671
|
+
if (span && base?.type === 'MemberExpression' && isImportMeta(child(base, 'object')))
|
|
672
|
+
if (memberName(base) === 'url') refs.urls.push(span)
|
|
673
|
+
} else if (value.type === 'CallExpression') {
|
|
674
|
+
const callee = child(value, 'callee')
|
|
675
|
+
if (callee?.type === 'MemberExpression' && isImportMeta(child(callee, 'object'))) {
|
|
676
|
+
const span = literal((value.arguments as AstNode[])[0])
|
|
677
|
+
if (span && memberName(callee) === 'resolve') refs.resolves.push(span)
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
for (const key in value) {
|
|
681
|
+
if (key !== 'start' && key !== 'end' && key !== 'type') visit(value[key], value)
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
visit(result.program, undefined)
|
|
685
|
+
metaRefMemo.set(key, refs)
|
|
686
|
+
return refs
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/**
|
|
690
|
+
* Whether the module declares top-level state: a `const`/`let`/`var` initialized by `new`, or by a
|
|
691
|
+
* call through a plain name (not `require`, not `/* @__PURE__ *\/`), from the AST: never a line
|
|
692
|
+
* inside a string, and every declarator counts.
|
|
693
|
+
*/
|
|
694
|
+
export function hasModuleState(file: string, source: string): boolean {
|
|
695
|
+
const result = parseSync(file, source, { lang: langForFile(file) })
|
|
696
|
+
const pureEnds = new Set(
|
|
697
|
+
result.comments
|
|
698
|
+
.filter(comment => /^\s*[#@]__PURE__\s*$/.test(comment.value))
|
|
699
|
+
.map(comment => comment.end),
|
|
700
|
+
)
|
|
701
|
+
const annotatedPure = (node: AstNode) => {
|
|
702
|
+
for (let index = node.start - 1; index >= 0; index -= 1) {
|
|
703
|
+
if (pureEnds.has(index + 1)) return true
|
|
704
|
+
if (!/\s/.test(source[index]!)) return false
|
|
705
|
+
}
|
|
706
|
+
return false
|
|
707
|
+
}
|
|
708
|
+
const plainCallee = (node: AstNode | undefined): boolean =>
|
|
709
|
+
node?.type === 'Identifier' ||
|
|
710
|
+
(node?.type === 'MemberExpression' && !node.computed && plainCallee(child(node, 'object')))
|
|
711
|
+
const declarations = (result.program.body as unknown as AstNode[]).flatMap(statement => {
|
|
712
|
+
const declaration =
|
|
713
|
+
statement.type === 'ExportNamedDeclaration' ? child(statement, 'declaration') : statement
|
|
714
|
+
return declaration?.type === 'VariableDeclaration'
|
|
715
|
+
? (declaration.declarations as AstNode[])
|
|
716
|
+
: []
|
|
717
|
+
})
|
|
718
|
+
return declarations.some(declarator => {
|
|
719
|
+
const init = unwrapExpression(child(declarator, 'init'))
|
|
720
|
+
if (init?.type === 'NewExpression') return true
|
|
721
|
+
if (init?.type !== 'CallExpression') return false
|
|
722
|
+
const callee = child(init, 'callee')
|
|
723
|
+
return plainCallee(callee) && callee?.name !== 'require' && !annotatedPure(init)
|
|
724
|
+
})
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
/** A string literal holding an absolute path or `file:` URL, and whether it is an object key. */
|
|
728
|
+
export interface PathLiteral {
|
|
729
|
+
value: string
|
|
730
|
+
start: number
|
|
731
|
+
end: number
|
|
732
|
+
key: boolean
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/** Absolute path literals in emitted code, from the AST; undefined when the parse recovered. */
|
|
736
|
+
export function absolutePathLiterals(file: string, source: string): PathLiteral[] | undefined {
|
|
737
|
+
const result = parseSync(file, source, { lang: langForFile(file) })
|
|
738
|
+
if (result.errors.length > 0) return undefined
|
|
739
|
+
const found: PathLiteral[] = []
|
|
740
|
+
const visit = (value: unknown, parent: AstNode | undefined) => {
|
|
741
|
+
if (Array.isArray(value)) {
|
|
742
|
+
for (const item of value) visit(item, parent)
|
|
743
|
+
return
|
|
744
|
+
}
|
|
745
|
+
if (!isNode(value)) return
|
|
746
|
+
const literal = stringValue(value)
|
|
747
|
+
if (literal !== undefined && (literal.startsWith('/') || literal.startsWith('file:///'))) {
|
|
748
|
+
const key = parent?.type === 'Property' && parent.key === value && !parent.computed
|
|
749
|
+
found.push({ value: literal, start: value.start, end: value.end, key })
|
|
750
|
+
}
|
|
751
|
+
for (const key in value) {
|
|
752
|
+
if (key !== 'start' && key !== 'end' && key !== 'type') visit(value[key], value)
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
visit(result.program, undefined)
|
|
756
|
+
return found
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/** Line comments (`// …`), from the parse; undefined when it recovered. */
|
|
760
|
+
export function lineComments(file: string, source: string) {
|
|
761
|
+
const result = parseSync(file, source, { lang: langForFile(file) })
|
|
762
|
+
if (result.errors.length > 0) return undefined
|
|
763
|
+
return result.comments
|
|
764
|
+
.filter(comment => comment.type === 'Line')
|
|
765
|
+
.map(comment => ({ value: comment.value, start: comment.start, end: comment.end }))
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Drop `type: "json"` attributes from the import declarations and `import()` calls whose target
|
|
770
|
+
* `compiled` claims: the target is JS, which a native load would parse as JSON.
|
|
771
|
+
*/
|
|
772
|
+
export function stripJsonImportAttributes(
|
|
773
|
+
source: string,
|
|
774
|
+
file: string,
|
|
775
|
+
compiled: (specifier: string) => boolean,
|
|
776
|
+
) {
|
|
777
|
+
const result = parseSync(file, source, { lang: 'js', sourceType: 'module' })
|
|
778
|
+
if (result.errors.length > 0) return source
|
|
779
|
+
const edits: { start: number; end: number; value: string }[] = []
|
|
780
|
+
const visit = (node: AstNode) => {
|
|
781
|
+
const target = child(node, 'source')
|
|
782
|
+
const specifier = stringValue(target)
|
|
783
|
+
if (target && specifier !== undefined && compiled(specifier)) {
|
|
784
|
+
const attributes = node.attributes as AstNode[] | undefined
|
|
785
|
+
const options = child(node, 'options')
|
|
786
|
+
if (attributes?.length && attributes.every(jsonTypeAttribute)) {
|
|
787
|
+
const end = source.indexOf('}', attributes.at(-1)!.end) + 1
|
|
788
|
+
edits.push({ start: target.end, end, value: '' })
|
|
789
|
+
} else if (node.type === 'ImportExpression' && options && jsonImportOptions(options)) {
|
|
790
|
+
edits.push({ start: target.end, end: options.end, value: '' })
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
for (const value of Object.values(node)) {
|
|
794
|
+
if (isNode(value)) visit(value)
|
|
795
|
+
else if (Array.isArray(value)) for (const item of value) if (isNode(item)) visit(item)
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
visit(result.program as unknown as AstNode)
|
|
799
|
+
return edits.length > 0 ? spliceSource(source, edits) : source
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function propertyName(node: AstNode | undefined) {
|
|
803
|
+
if (!node) return undefined
|
|
804
|
+
return node.type === 'Identifier' ? (node.name as string) : stringValue(node)
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function jsonTypeAttribute(node: AstNode) {
|
|
808
|
+
return (
|
|
809
|
+
!node.computed &&
|
|
810
|
+
propertyName(child(node, 'key')) === 'type' &&
|
|
811
|
+
stringValue(child(node, 'value')) === 'json'
|
|
812
|
+
)
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
/** `{ with: { type: 'json' } }` (or `assert`) and nothing else. */
|
|
816
|
+
function jsonImportOptions(options: AstNode) {
|
|
817
|
+
const [property, ...rest] = (options.properties as AstNode[] | undefined) ?? []
|
|
818
|
+
const attributes = child(property, 'value')
|
|
819
|
+
const entries = attributes?.properties as AstNode[] | undefined
|
|
820
|
+
return (
|
|
821
|
+
options.type === 'ObjectExpression' &&
|
|
822
|
+
!!property &&
|
|
823
|
+
rest.length === 0 &&
|
|
824
|
+
property.type === 'Property' &&
|
|
825
|
+
!property.computed &&
|
|
826
|
+
['with', 'assert'].includes(propertyName(child(property, 'key')) ?? '') &&
|
|
827
|
+
attributes?.type === 'ObjectExpression' &&
|
|
828
|
+
!!entries?.length &&
|
|
829
|
+
entries.every(entry => entry.type === 'Property' && jsonTypeAttribute(entry))
|
|
830
|
+
)
|
|
831
|
+
}
|
|
832
|
+
|
|
584
833
|
function collectDynamicCalls(program: AstNode): DynamicCallFact[] {
|
|
585
834
|
const calls: DynamicCallFact[] = []
|
|
586
835
|
const visit = (value: unknown) => {
|
package/src/routing/href.ts
CHANGED
|
@@ -198,7 +198,16 @@ export function canonicalTrailingSlashPath(pathname: string, trailingSlash: bool
|
|
|
198
198
|
if (/\.[^/]+$/.test(pathname)) return pathname
|
|
199
199
|
const slashed = pathname.endsWith('/')
|
|
200
200
|
if (trailingSlash ? slashed : !slashed) return pathname
|
|
201
|
-
|
|
201
|
+
// Collapsed, so a rewritten path can never read as protocol-relative (`//host`).
|
|
202
|
+
const bare = pathname.replace(/\/{2,}/g, '/').replace(/\/+$/, '')
|
|
203
|
+
return trailingSlash ? `${bare}/` : bare || '/'
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** Next 308s a path with repeated slashes to its collapsed form, for every method, before anything else. */
|
|
207
|
+
export function repeatedSlashRedirect(url: URL): Response | null {
|
|
208
|
+
if (!url.pathname.includes('//')) return null
|
|
209
|
+
const target = `${url.pathname.replace(/\/{2,}/g, '/')}${url.search}`
|
|
210
|
+
return new Response(target, { status: 308, headers: { location: target } })
|
|
202
211
|
}
|
|
203
212
|
|
|
204
213
|
/**
|
|
@@ -210,13 +219,11 @@ export function canonicalTrailingSlashPath(pathname: string, trailingSlash: bool
|
|
|
210
219
|
export function trailingSlashRedirect(
|
|
211
220
|
config: { trailingSlash?: boolean; skipTrailingSlashRedirect?: boolean },
|
|
212
221
|
url: URL,
|
|
213
|
-
method: string,
|
|
214
222
|
): Response | null {
|
|
215
223
|
// skipTrailingSlashRedirect: the app serves both slashed and unslashed URLs
|
|
216
224
|
// as-is, so no canonical redirect is ever emitted (route matching already
|
|
217
225
|
// tolerates a trailing slash via normalizePathname).
|
|
218
226
|
if (config.skipTrailingSlashRedirect) return null
|
|
219
|
-
if (method !== 'GET' && method !== 'HEAD') return null
|
|
220
227
|
const pathname = url.pathname
|
|
221
228
|
if (pathname === '/' || pathname === '') return null
|
|
222
229
|
if (pathname.startsWith('/__pnext') || isBuildAssetPathname(pathname)) return null
|