@wular/pnext 0.0.24 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/pnext +1 -1
- package/package.json +2 -2
- package/reference/data/bench.json +398 -223
- package/reference/getting-started.md +1 -1
- package/reference/navigation.md +2 -2
- package/src/api/client-navigation.ts +7 -5
- package/src/cli/adapters/vercel-warm.ts +120 -28
- package/src/cli/build.ts +56 -15
- package/src/cli/create.ts +1 -1
- package/src/cli/dev.ts +2 -1
- package/src/cli/index.ts +146 -10
- package/src/cli/migrate/report.ts +1 -1
- package/src/cli/serve/pipeline.ts +23 -6
- package/src/cli/serve/ui.ts +1 -1
- package/src/cli/start.ts +2 -1
- package/src/cli/typegen.ts +5 -2
- package/src/client/build.ts +217 -58
- package/src/client/chunk-fold.ts +7 -1
- package/src/client/entry.ts +44 -7
- package/src/client/router/runtime.ts +1099 -236
- package/src/client/router/types.ts +15 -0
- package/src/compat/actions/client-plugin.ts +10 -0
- package/src/compat/actions/rewrite.ts +1 -0
- package/src/compat/bundler/optimize-package-imports.ts +375 -28
- package/src/compat/client/navigation-scroll.ts +29 -3
- package/src/compat/client/optimistic-routing.ts +2 -2
- package/src/compat/client/segment-cache-policy.ts +4 -18
- package/src/compat/client/segment-cache.ts +104 -0
- package/src/compat/client/segment-prefetch.ts +6 -6
- package/src/compat/next/config-loader.ts +6 -4
- package/src/compat/next/navigation.ts +16 -2
- package/src/compat/pages/router.ts +102 -9
- package/src/compat/protocol.ts +10 -1
- package/src/compat/register/actions.ts +2 -0
- package/src/compat/register/bundler.ts +6 -6
- package/src/compat/register/routing.ts +3 -4
- package/src/compat/tsconfig-defaults.ts +3 -2
- package/src/config.ts +3 -3
- package/src/css/postcss.ts +450 -17
- package/src/dev/client-actions.ts +2 -0
- package/src/dev/server.ts +87 -14
- package/src/render/island-context.ts +3 -0
- package/src/render/ppr.ts +14 -0
- package/src/render/renderer.ts +273 -43
- package/src/request/context.ts +32 -11
- package/src/resolve/scan-facts.ts +23 -4
- package/src/routing/proxy.ts +29 -25
- package/src/routing/routes.ts +1 -1
- package/src/runtime/loader.ts +47 -1
- package/src/runtime/modules.ts +546 -65
- package/src/runtime/vendor-build.ts +109 -22
- package/src/runtime/vendor.ts +7 -3
- package/src/utils/ansi.ts +3 -1
- package/src/utils/fs.ts +17 -3
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { builtinModules, createRequire } from 'node:module'
|
|
13
|
-
import { existsSync, readFileSync, statSync } from 'node:fs'
|
|
13
|
+
import { existsSync, readFileSync, realpathSync, statSync } from 'node:fs'
|
|
14
14
|
import { copyFile, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises'
|
|
15
15
|
import path from 'node:path'
|
|
16
16
|
import { fileURLToPath } from 'node:url'
|
|
@@ -104,6 +104,8 @@ export function clearServerRuntimeCaches() {
|
|
|
104
104
|
clearVendorPipeline()
|
|
105
105
|
clearVendorNativeCaches()
|
|
106
106
|
vendorEntryResolutions.clear()
|
|
107
|
+
canonicalVendorEntries.clear()
|
|
108
|
+
nearestManifestDirs.clear()
|
|
107
109
|
clearVendorPackageResolutions()
|
|
108
110
|
vendorLayerRecords.clear()
|
|
109
111
|
vendorSharedEntries.clear()
|
|
@@ -209,23 +211,40 @@ function resolveVendorEntry(
|
|
|
209
211
|
resolveDir: string,
|
|
210
212
|
conditionTarget: ServerBundleTarget,
|
|
211
213
|
) {
|
|
212
|
-
if (path.isAbsolute(specifier)) return path.resolve(specifier)
|
|
213
214
|
// Every demand builds a plan before dedup can see it, so this resolve runs
|
|
214
215
|
// once per DEMAND, not once per artifact — and the same specifier arrives
|
|
215
216
|
// from hundreds of importers. The answer is pure in its inputs.
|
|
216
217
|
const key = `${config.root}\0${conditionTarget}\0${resolveDir}\0${specifier}`
|
|
217
218
|
if (vendorEntryResolutions.has(key)) return vendorEntryResolutions.get(key)
|
|
218
|
-
const entry =
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
219
|
+
const entry = canonicalVendorEntry(
|
|
220
|
+
path.isAbsolute(specifier)
|
|
221
|
+
? path.resolve(specifier)
|
|
222
|
+
: resolveVendorPackageSpecifier(
|
|
223
|
+
config.root,
|
|
224
|
+
path.join(resolveDir, 'pnext-resolve.ts'),
|
|
225
|
+
specifier,
|
|
226
|
+
[...serverBundleConditions(conditionTarget), 'import'],
|
|
227
|
+
),
|
|
223
228
|
)
|
|
224
229
|
vendorEntryResolutions.set(key, entry)
|
|
225
230
|
return entry
|
|
226
231
|
}
|
|
227
232
|
|
|
233
|
+
function canonicalVendorEntry(entry: string | undefined) {
|
|
234
|
+
if (!entry) return entry
|
|
235
|
+
if (canonicalVendorEntries.has(entry)) return canonicalVendorEntries.get(entry)!
|
|
236
|
+
let canonical = entry
|
|
237
|
+
try {
|
|
238
|
+
canonical = realpathSync.native(entry)
|
|
239
|
+
} catch {
|
|
240
|
+
// Keep the unresolved path when the file disappeared between resolution and canonicalization.
|
|
241
|
+
}
|
|
242
|
+
canonicalVendorEntries.set(entry, canonical)
|
|
243
|
+
return canonical
|
|
244
|
+
}
|
|
245
|
+
|
|
228
246
|
const vendorEntryResolutions = new Map<string, string | undefined>()
|
|
247
|
+
const canonicalVendorEntries = new Map<string, string>()
|
|
229
248
|
|
|
230
249
|
function vendorBuildPlan(
|
|
231
250
|
config: ResolvedConfig,
|
|
@@ -940,10 +959,10 @@ async function preplanDependencyDecision(
|
|
|
940
959
|
resolveDir: string,
|
|
941
960
|
): Promise<OnResolveResult | undefined> {
|
|
942
961
|
const depName = packageNameFromSpecifier(specifier)
|
|
943
|
-
if (!
|
|
944
|
-
if (builtinModules.includes(depName) || isBunBuiltin(depName)) return undefined
|
|
945
|
-
if (getExternalPackagePolicy().external(depName)) return undefined
|
|
946
|
-
if (getExternalPackagePolicy().transpile(depName)) return undefined
|
|
962
|
+
if (!isVendorModuleSpecifier(specifier)) return undefined
|
|
963
|
+
if (depName && (builtinModules.includes(depName) || isBunBuiltin(depName))) return undefined
|
|
964
|
+
if (depName && getExternalPackagePolicy().external(depName)) return undefined
|
|
965
|
+
if (depName && getExternalPackagePolicy().transpile(depName)) return undefined
|
|
947
966
|
const entry = resolveVendorEntry(config, specifier, resolveDir, conditionTarget)
|
|
948
967
|
if (!entry || /\.[cm]?tsx?$/.test(entry) || !isEsmModuleEntry(entry)) return undefined
|
|
949
968
|
const plan = vendorBuildPlan(config, specifier, target, resolveDir, conditionTarget)
|
|
@@ -1251,7 +1270,16 @@ async function buildVendorGroupWithPlugins(
|
|
|
1251
1270
|
reactServerLayer: target === 'server',
|
|
1252
1271
|
}),
|
|
1253
1272
|
vendorGroupSiblingPlugin(siblings),
|
|
1254
|
-
externalPackageDependencyPlugin(
|
|
1273
|
+
externalPackageDependencyPlugin(
|
|
1274
|
+
config,
|
|
1275
|
+
packageName,
|
|
1276
|
+
target,
|
|
1277
|
+
conditionTarget,
|
|
1278
|
+
resolveDir,
|
|
1279
|
+
entryPoints.map(entry => entry.in),
|
|
1280
|
+
context,
|
|
1281
|
+
preplan,
|
|
1282
|
+
),
|
|
1255
1283
|
externalBuiltinBuildPlugin(),
|
|
1256
1284
|
])
|
|
1257
1285
|
if (trace) trace.plugins = groupPlugins.map(plugin => plugin.name)
|
|
@@ -1720,15 +1748,32 @@ function vendorPackageShape(request: VendorNativeRequest): VendorPackageShape {
|
|
|
1720
1748
|
}
|
|
1721
1749
|
|
|
1722
1750
|
function nearestManifestDir(from: string) {
|
|
1751
|
+
if (nearestManifestDirs.has(from)) return nearestManifestDirs.get(from)
|
|
1723
1752
|
let dir = from
|
|
1724
1753
|
while (true) {
|
|
1725
|
-
|
|
1754
|
+
const manifest = path.join(dir, 'package.json')
|
|
1755
|
+
if (existsSync(manifest)) {
|
|
1756
|
+
try {
|
|
1757
|
+
const parsed = JSON.parse(readFileSync(manifest, 'utf8')) as { name?: unknown }
|
|
1758
|
+
if (typeof parsed.name === 'string') {
|
|
1759
|
+
nearestManifestDirs.set(from, dir)
|
|
1760
|
+
return dir
|
|
1761
|
+
}
|
|
1762
|
+
} catch {
|
|
1763
|
+
// Malformed manifests cannot establish a package root; continue walking.
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1726
1766
|
const parent = path.dirname(dir)
|
|
1727
|
-
if (parent === dir)
|
|
1767
|
+
if (parent === dir) {
|
|
1768
|
+
nearestManifestDirs.set(from, undefined)
|
|
1769
|
+
return undefined
|
|
1770
|
+
}
|
|
1728
1771
|
dir = parent
|
|
1729
1772
|
}
|
|
1730
1773
|
}
|
|
1731
1774
|
|
|
1775
|
+
const nearestManifestDirs = new Map<string, string | undefined>()
|
|
1776
|
+
|
|
1732
1777
|
/**
|
|
1733
1778
|
* Decide what each external reference in the emitted code means - the work the resolve callbacks used
|
|
1734
1779
|
* to do, off the bundler's critical path. Undefined when a reference cannot be expressed as a rewrite
|
|
@@ -1899,8 +1944,11 @@ async function preplanVendorRefUncached(
|
|
|
1899
1944
|
)
|
|
1900
1945
|
return asset ? { kind: 'inline', file: asset } : undefined
|
|
1901
1946
|
}
|
|
1902
|
-
if (
|
|
1903
|
-
|
|
1947
|
+
if (sameVendorPackageFile(specifier, importerFile)) {
|
|
1948
|
+
return { kind: 'inline', file: canonicalVendorEntry(specifier)! }
|
|
1949
|
+
}
|
|
1950
|
+
if (!isVendorModuleSpecifier(specifier)) return undefined
|
|
1951
|
+
if (depName && getExternalPackagePolicy().external(depName)) {
|
|
1904
1952
|
const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
|
|
1905
1953
|
const fromRoot = resolveVendorPackageSpecifier(
|
|
1906
1954
|
config.root,
|
|
@@ -1990,11 +2038,14 @@ async function decideVendorRefUncached(
|
|
|
1990
2038
|
)
|
|
1991
2039
|
return asset ? { kind: 'inline', file: asset } : undefined
|
|
1992
2040
|
}
|
|
1993
|
-
if (
|
|
2041
|
+
if (sameVendorPackageFile(specifier, importerFile)) {
|
|
2042
|
+
return { kind: 'inline', file: canonicalVendorEntry(specifier)! }
|
|
2043
|
+
}
|
|
2044
|
+
if (!isVendorModuleSpecifier(specifier)) return undefined
|
|
1994
2045
|
|
|
1995
2046
|
// B7: a serverExternalPackage stays external, pinned to the importing
|
|
1996
2047
|
// package's own copy when that differs from the hoisted root version.
|
|
1997
|
-
if (getExternalPackagePolicy().external(depName)) {
|
|
2048
|
+
if (depName && getExternalPackagePolicy().external(depName)) {
|
|
1998
2049
|
const fromImporter = resolveNestedPackageFromImporter(importerFile, specifier, conditions)
|
|
1999
2050
|
const fromRoot = resolveVendorPackageSpecifier(
|
|
2000
2051
|
config.root,
|
|
@@ -2334,7 +2385,16 @@ async function bundleExternalPackageImpl(
|
|
|
2334
2385
|
// bundles are the SSR pass and keep the full-hooks shim).
|
|
2335
2386
|
reactServerLayer: target === 'server',
|
|
2336
2387
|
}),
|
|
2337
|
-
externalPackageDependencyPlugin(
|
|
2388
|
+
externalPackageDependencyPlugin(
|
|
2389
|
+
config,
|
|
2390
|
+
specifier,
|
|
2391
|
+
target,
|
|
2392
|
+
conditionTarget,
|
|
2393
|
+
resolveDir,
|
|
2394
|
+
nativeEntry ? [nativeEntry] : [],
|
|
2395
|
+
context,
|
|
2396
|
+
preplan,
|
|
2397
|
+
),
|
|
2338
2398
|
externalBuiltinBuildPlugin(),
|
|
2339
2399
|
])
|
|
2340
2400
|
if (trace) trace.plugins = vendorPlugins.map(plugin => plugin.name)
|
|
@@ -2410,16 +2470,32 @@ function externalPackageDependencyPlugin(
|
|
|
2410
2470
|
entrySpecifier: string,
|
|
2411
2471
|
target: CompatAliasTarget,
|
|
2412
2472
|
conditionTarget: ServerBundleTarget,
|
|
2473
|
+
resolveDir: string,
|
|
2474
|
+
ownEntries: string[],
|
|
2413
2475
|
context?: { nestedExternalFiles: string[] },
|
|
2414
2476
|
preplan = false,
|
|
2415
2477
|
): Plugin {
|
|
2416
2478
|
const entryPackageName = packageNameFromSpecifier(entrySpecifier)
|
|
2479
|
+
const ownPackageRoots = new Set(
|
|
2480
|
+
[...ownEntries, resolveVendorEntry(config, entrySpecifier, resolveDir, conditionTarget)]
|
|
2481
|
+
.filter((entry): entry is string => entry !== undefined)
|
|
2482
|
+
.map(entry => nearestManifestDir(path.dirname(canonicalVendorEntry(entry)!)))
|
|
2483
|
+
.filter((root): root is string => root !== undefined),
|
|
2484
|
+
)
|
|
2417
2485
|
return {
|
|
2418
2486
|
name: 'pnext-external-package-dependencies',
|
|
2419
2487
|
setup(build) {
|
|
2420
|
-
build.onResolve({ filter: /^[^./].*/ }, async args => {
|
|
2421
|
-
if (!
|
|
2422
|
-
if (
|
|
2488
|
+
build.onResolve({ filter: /^(?:\/|[^./]).*/ }, async args => {
|
|
2489
|
+
if (!isVendorModuleSpecifier(args.path)) return undefined
|
|
2490
|
+
if (args.kind === 'entry-point') return undefined
|
|
2491
|
+
const ownRoot = path.isAbsolute(args.path)
|
|
2492
|
+
? nearestManifestDir(path.dirname(canonicalVendorEntry(args.path)!))
|
|
2493
|
+
: undefined
|
|
2494
|
+
if (ownRoot && ownPackageRoots.has(ownRoot)) return undefined
|
|
2495
|
+
if (sameVendorPackageFile(args.path, args.importer)) return undefined
|
|
2496
|
+
if (entryPackageName && packageNameFromSpecifier(args.path) === entryPackageName) {
|
|
2497
|
+
return undefined
|
|
2498
|
+
}
|
|
2423
2499
|
if (preplan && args.kind !== 'require-call') {
|
|
2424
2500
|
const planned = await preplanDependencyDecision(
|
|
2425
2501
|
config,
|
|
@@ -2627,6 +2703,17 @@ function isPackageSpecifier(specifier: string) {
|
|
|
2627
2703
|
return Boolean(packageName && !builtinModules.includes(packageName))
|
|
2628
2704
|
}
|
|
2629
2705
|
|
|
2706
|
+
function isVendorModuleSpecifier(specifier: string) {
|
|
2707
|
+
return path.isAbsolute(specifier) || isPackageSpecifier(specifier)
|
|
2708
|
+
}
|
|
2709
|
+
|
|
2710
|
+
function sameVendorPackageFile(specifier: string, importer: string) {
|
|
2711
|
+
if (!path.isAbsolute(specifier) || !path.isAbsolute(importer)) return false
|
|
2712
|
+
const entryRoot = nearestManifestDir(path.dirname(canonicalVendorEntry(specifier)!))
|
|
2713
|
+
const importerRoot = nearestManifestDir(path.dirname(canonicalVendorEntry(importer)!))
|
|
2714
|
+
return entryRoot !== undefined && entryRoot === importerRoot
|
|
2715
|
+
}
|
|
2716
|
+
|
|
2630
2717
|
// Trace copies of nested transitive externals (a version distinct from the
|
|
2631
2718
|
// hoisted root copy) written alongside the vendor bundle as `.js`, so file
|
|
2632
2719
|
// tracing / server-bundle scans observe the bundled dependency the way
|
package/src/runtime/vendor.ts
CHANGED
|
@@ -1028,14 +1028,14 @@ export async function cjsEntryMayHaveNamedExports(entry: string) {
|
|
|
1028
1028
|
* full cache wipe recovers from. A new key sidesteps both: the old facade stays on disk for whoever still
|
|
1029
1029
|
* imports it, and every new compile names the new one.
|
|
1030
1030
|
*/
|
|
1031
|
-
const FACADE_GENERATOR = '
|
|
1031
|
+
const FACADE_GENERATOR = 'v3'
|
|
1032
1032
|
|
|
1033
1033
|
/** Marker written beside a bundle whose export set turned out to be default-only. */
|
|
1034
1034
|
const NO_FACADE_SUFFIX = `.no-facade-${FACADE_GENERATOR}`
|
|
1035
1035
|
|
|
1036
1036
|
/** The facade this generator writes for `bundleFile`. */
|
|
1037
1037
|
export function cjsFacadePath(bundleFile: string) {
|
|
1038
|
-
return `${bundleFile.replace(/\.mjs$/, '')}.facade-${FACADE_GENERATOR}.mjs
|
|
1038
|
+
return path.join(`${bundleFile.replace(/\.mjs$/, '')}.facade-${FACADE_GENERATOR}`, 'index.mjs')
|
|
1039
1039
|
}
|
|
1040
1040
|
|
|
1041
1041
|
/** Where this generator records "no named exports" for `bundleFile`. */
|
|
@@ -1120,11 +1120,15 @@ async function writeCjsNamedExportFacade(bundleFile: string, importHref: string)
|
|
|
1120
1120
|
// off it threw at facade eval. Its names live on the bundle's own exports
|
|
1121
1121
|
// instead — the same precedence `executedExportNames` derived them with.
|
|
1122
1122
|
const source =
|
|
1123
|
-
`import ${binding}, * as ${namespace} from ${JSON.stringify(
|
|
1123
|
+
`import ${binding}, * as ${namespace} from ${JSON.stringify(`../${path.basename(bundleFile)}`)};\n` +
|
|
1124
1124
|
`export default ${binding};\n` +
|
|
1125
1125
|
`const ${holder} = ${binding} ?? ${namespace};\n` +
|
|
1126
1126
|
names.map(name => `export const ${name} = ${holder}[${JSON.stringify(name)}];`).join('\n') +
|
|
1127
1127
|
'\n'
|
|
1128
|
+
// Bun 1.4 caches a resolved directory's module names. The bundle was just
|
|
1129
|
+
// imported above, so a new sibling facade is invisible to a later import;
|
|
1130
|
+
// a content-specific child directory has not been resolved yet.
|
|
1131
|
+
await mkdir(path.dirname(file), { recursive: true })
|
|
1128
1132
|
const ok = await facadeRebundles(file, source)
|
|
1129
1133
|
heavyProfRow({
|
|
1130
1134
|
k: 'facade',
|
package/src/utils/ansi.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
// NO_COLOR (https://no-color.org): any non-empty value disables all styling.
|
|
1
2
|
function color(open: string, close: string) {
|
|
2
|
-
return (value: string) =>
|
|
3
|
+
return (value: string) =>
|
|
4
|
+
process.stdout.isTTY && !process.env.NO_COLOR ? `${open}${value}${close}` : value
|
|
3
5
|
}
|
|
4
6
|
|
|
5
7
|
/** Mid-gray for subordinate text. Readable on both light and dark terminals. */
|
package/src/utils/fs.ts
CHANGED
|
@@ -9,9 +9,23 @@ import { fileURLToPath } from 'node:url'
|
|
|
9
9
|
// second rename hits ENOENT once the first moved it away. Losing that race is harmless - both wrote the same
|
|
10
10
|
// content-keyed bytes - so a rename whose temp is gone only fails if the destination is missing too.
|
|
11
11
|
let atomicWriteSequence = 0
|
|
12
|
-
export async function writeFileAtomic(file: string, contents: string) {
|
|
13
|
-
const
|
|
14
|
-
|
|
12
|
+
export async function writeFileAtomic(file: string, contents: string, tempDirectory?: string) {
|
|
13
|
+
const suffix = `${process.pid.toString(36)}.${(++atomicWriteSequence).toString(36)}.tmp`
|
|
14
|
+
let temp = tempDirectory
|
|
15
|
+
? path.join(
|
|
16
|
+
tempDirectory,
|
|
17
|
+
`.${path.basename(path.dirname(file))}-${path.basename(file)}.${suffix}`,
|
|
18
|
+
)
|
|
19
|
+
: `${file}.${suffix}`
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(temp, contents)
|
|
22
|
+
} catch (error) {
|
|
23
|
+
// An unwritable tempDirectory (read-only parent of a containerized app root)
|
|
24
|
+
// must not fail the write: fall back to staging beside the destination.
|
|
25
|
+
if (!tempDirectory) throw error
|
|
26
|
+
temp = `${file}.${suffix}`
|
|
27
|
+
await writeFile(temp, contents)
|
|
28
|
+
}
|
|
15
29
|
try {
|
|
16
30
|
await rename(temp, file)
|
|
17
31
|
} catch (error) {
|