@wular/pnext 0.0.6 → 0.0.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/README.md +12 -6
- package/bin/pnext +5 -0
- package/package.json +1 -1
- package/reference/compat.md +86 -21
- package/reference/config.md +40 -93
- package/reference/css.md +40 -35
- package/reference/dev.md +25 -32
- package/reference/env.md +18 -20
- package/reference/getting-started.md +132 -0
- package/reference/metadata.md +26 -41
- package/reference/navigation.md +58 -105
- package/reference/performance.md +21 -142
- package/reference/rendering.md +73 -70
- package/reference/routing.md +87 -97
- package/reference/typegen.md +21 -27
- package/src/api/client-navigation.ts +11 -8
- package/src/api/link.tsx +5 -3
- package/src/cli/create.ts +1 -1
- package/src/cli/index.ts +6 -0
- package/src/client/build.ts +10 -2
- package/src/client/router/events.ts +15 -0
- package/src/client/router/runtime.ts +40 -9
- package/src/client/router/types.ts +5 -0
- package/src/config.ts +18 -1
- package/src/dev/server.ts +2 -2
- package/src/render/renderer.ts +11 -0
- package/src/routing/href.ts +19 -1
- package/src/runtime/loader.ts +3 -1
- package/src/runtime/vendor.ts +5 -4
- package/src/types.ts +5 -1
- package/reference/overview.md +0 -35
package/src/config.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { loadEnv } from './env'
|
|
5
5
|
import { findWorkspaceRoot } from './resolve/imports'
|
|
6
|
-
import type
|
|
6
|
+
import { PREFETCH_MODES, type PNextConfig } from './types'
|
|
7
7
|
import { setCacheComponents } from './render/ppr'
|
|
8
8
|
|
|
9
9
|
export type ResolvedConfig = Required<Pick<PNextConfig, 'outDir' | 'basePath'>> &
|
|
@@ -86,6 +86,7 @@ export async function loadConfig(
|
|
|
86
86
|
? { compat: { ...(loadedConfig.default?.compat ?? {}), next: true } }
|
|
87
87
|
: {}),
|
|
88
88
|
}
|
|
89
|
+
validateConfig(config)
|
|
89
90
|
// cacheComponents is a per-app flag on a process-global cell: clear it before
|
|
90
91
|
// the next.config source re-derives it, so a pure-core (or flag-off) app
|
|
91
92
|
// loaded after a cacheComponents app never inherits the previous build's flag.
|
|
@@ -128,6 +129,22 @@ export async function loadConfig(
|
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
132
|
+
// One row per enum-valued config field; validateConfig checks them all.
|
|
133
|
+
const enumFields: readonly [keyof PNextConfig, readonly unknown[]][] = [
|
|
134
|
+
['prefetch', PREFETCH_MODES],
|
|
135
|
+
]
|
|
136
|
+
|
|
137
|
+
function validateConfig(config: PNextConfig) {
|
|
138
|
+
for (const [field, allowed] of enumFields) {
|
|
139
|
+
const value = config[field]
|
|
140
|
+
if (value === undefined || allowed.includes(value)) continue
|
|
141
|
+
const list = allowed.map(v => (typeof v === 'string' ? `'${v}'` : String(v))).join(', ')
|
|
142
|
+
throw new Error(
|
|
143
|
+
`Invalid pnext config: '${field}' must be one of ${list} (received ${JSON.stringify(value)}).`,
|
|
144
|
+
)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
131
148
|
/** Dev's private subtree under the out root. Build never touches it. */
|
|
132
149
|
export const devOutSegment = 'dev'
|
|
133
150
|
|
package/src/dev/server.ts
CHANGED
|
@@ -964,10 +964,10 @@ async function handleRoute(
|
|
|
964
964
|
}
|
|
965
965
|
|
|
966
966
|
function routeClientCacheKey(config: ResolvedConfig, route: RouteManifestEntry) {
|
|
967
|
-
const existing = routeCacheKeys.get(route.id)
|
|
967
|
+
const existing = routeCacheKeys.get(appKey(config.outPath, route.id))
|
|
968
968
|
if (existing) return existing
|
|
969
969
|
const key = devClientCacheKey(config, route, Boolean(config.compat?.next))
|
|
970
|
-
routeCacheKeys.set(route.id, key)
|
|
970
|
+
routeCacheKeys.set(appKey(config.outPath, route.id), key)
|
|
971
971
|
return key
|
|
972
972
|
}
|
|
973
973
|
|
package/src/render/renderer.ts
CHANGED
|
@@ -3655,6 +3655,7 @@ async function renderTree(
|
|
|
3655
3655
|
renderCollectedHeadScripts(),
|
|
3656
3656
|
bootstrapScripts,
|
|
3657
3657
|
compatBuildManifestScript,
|
|
3658
|
+
prefetchModeScript(options.config),
|
|
3658
3659
|
]
|
|
3659
3660
|
.filter(Boolean)
|
|
3660
3661
|
.join('\n'),
|
|
@@ -3791,6 +3792,16 @@ function shouldRenderRuntimeMetadataInBody(options: RenderOptions, runtimeMetada
|
|
|
3791
3792
|
)
|
|
3792
3793
|
}
|
|
3793
3794
|
|
|
3795
|
+
/**
|
|
3796
|
+
* Inline script exposing the configured default prefetch mode to the client router, which applies it
|
|
3797
|
+
* to links carrying no `data-prefetch` of their own. Emitted only when `prefetch` is configured - an
|
|
3798
|
+
* app that leaves it unset keeps the global undefined and the built-in 'visible' default.
|
|
3799
|
+
*/
|
|
3800
|
+
function prefetchModeScript(config: ResolvedConfig): string {
|
|
3801
|
+
if (config.prefetch === undefined) return ''
|
|
3802
|
+
return `<script>window.__PNEXT_PREFETCH__=${JSON.stringify(config.prefetch)};</script>`
|
|
3803
|
+
}
|
|
3804
|
+
|
|
3794
3805
|
/**
|
|
3795
3806
|
* Embeds the render's navigation state (children source path + each slot's
|
|
3796
3807
|
* resolved source path). The client echoes it on soft-navigation fetches so
|
package/src/routing/href.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { RouteParamValue } from '../types'
|
|
1
|
+
import type { PrefetchMode, RouteParamValue } from '../types'
|
|
2
2
|
|
|
3
3
|
export type SearchValue = string | number | boolean | null | undefined
|
|
4
4
|
export type SearchInput = URLSearchParams | Record<string, SearchValue | SearchValue[]>
|
|
@@ -19,8 +19,10 @@ export function routeHref(route: string, options: HrefParts = {}) {
|
|
|
19
19
|
declare global {
|
|
20
20
|
interface Window {
|
|
21
21
|
__PNEXT_TRAILING_SLASH__?: boolean
|
|
22
|
+
__PNEXT_PREFETCH__?: PrefetchMode
|
|
22
23
|
}
|
|
23
24
|
var __PNEXT_TRAILING_SLASH__: boolean | undefined
|
|
25
|
+
var __PNEXT_PREFETCH__: PrefetchMode | undefined
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
// With `trailingSlash: true` the server 308s bare page URLs to their slashed
|
|
@@ -44,6 +46,22 @@ export function getTrailingSlashUrls() {
|
|
|
44
46
|
return isTrailingSlashEnabled()
|
|
45
47
|
}
|
|
46
48
|
|
|
49
|
+
// The app-wide default prefetch mode (config `prefetch`), for links that set
|
|
50
|
+
// none of their own. Isomorphic like the trailing-slash seam: the server sets it
|
|
51
|
+
// from the resolved config, the client reads the injected window global
|
|
52
|
+
// (renderer's prefetchModeScript).
|
|
53
|
+
export function setDefaultPrefetchMode(mode: PrefetchMode | undefined) {
|
|
54
|
+
globalThis.__PNEXT_PREFETCH__ = mode
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getDefaultPrefetchMode(): PrefetchMode {
|
|
58
|
+
const mode =
|
|
59
|
+
process.browser || typeof window !== 'undefined'
|
|
60
|
+
? window.__PNEXT_PREFETCH__
|
|
61
|
+
: globalThis.__PNEXT_PREFETCH__
|
|
62
|
+
return mode === undefined ? 'visible' : mode
|
|
63
|
+
}
|
|
64
|
+
|
|
47
65
|
// The configured basePath ('' when unset). Set once per process by the server
|
|
48
66
|
// runtime from the resolved config (registerServerRuntime), mirroring the
|
|
49
67
|
// trailing-slash seam. Core render code (metadata) reads it through
|
package/src/runtime/loader.ts
CHANGED
|
@@ -41,7 +41,7 @@ import {
|
|
|
41
41
|
} from '../resolve/dynamic'
|
|
42
42
|
import { cacheRoot } from './module-cache'
|
|
43
43
|
import { readNodeModuleBundle, writeNodeModuleBundle } from '../dev/restart/node-modules'
|
|
44
|
-
import { setBasePathPrefix, setTrailingSlashUrls } from '../routing/href'
|
|
44
|
+
import { setBasePathPrefix, setDefaultPrefetchMode, setTrailingSlashUrls } from '../routing/href'
|
|
45
45
|
import { resolveExternalLoadTarget, resolveImport, workspacePackageRoots } from '../resolve/imports'
|
|
46
46
|
import { escapeRegex } from '../utils/code'
|
|
47
47
|
import { writeFileAtomic } from '../utils/fs'
|
|
@@ -169,6 +169,8 @@ export function registerServerRuntime(config: ResolvedConfig, sourceFiles: strin
|
|
|
169
169
|
// File-convention metadata asset hrefs (og-image, manifest) carry the
|
|
170
170
|
// basePath prefix; core render reads it through the href seam.
|
|
171
171
|
setBasePathPrefix(typeof config.basePath === 'string' ? config.basePath : '')
|
|
172
|
+
// Server-rendered Links bake the configured default into `data-prefetch`.
|
|
173
|
+
setDefaultPrefetchMode(config.prefetch)
|
|
172
174
|
if (typeof Bun === 'undefined') return
|
|
173
175
|
const sourceRoots = [...new Set(sourceFiles.map(file => sourceRootForFile(config, file)))].sort()
|
|
174
176
|
const signature = sourceRoots.join('\0')
|
package/src/runtime/vendor.ts
CHANGED
|
@@ -422,13 +422,14 @@ async function runVendorDemand(plan: VendorBuildPlan, nested: boolean, ticket: V
|
|
|
422
422
|
*
|
|
423
423
|
* Every demand pays it before it can even learn it has no siblings, and it is a timer on the one JS thread
|
|
424
424
|
* the concurrent bundlers already saturate with plugin IPC, so the real wait is bounded below by this rather
|
|
425
|
-
* than by it. `PNEXT_VENDOR_GROUP_WINDOW_MS` is the bisect seam
|
|
425
|
+
* than by it. `PNEXT_VENDOR_GROUP_WINDOW_MS` is the bisect seam, read per round so
|
|
426
|
+
* tests can widen it after module load.
|
|
426
427
|
*/
|
|
427
|
-
|
|
428
|
+
function vendorGroupWindowMs() {
|
|
428
429
|
// eslint-disable-next-line turbo/no-undeclared-env-vars
|
|
429
430
|
const override = Number(process.env.PNEXT_VENDOR_GROUP_WINDOW_MS)
|
|
430
431
|
return Number.isFinite(override) && override >= 0 ? override : 5
|
|
431
|
-
}
|
|
432
|
+
}
|
|
432
433
|
|
|
433
434
|
/**
|
|
434
435
|
* A group re-parses the package graph once per round, so an unbounded trickle
|
|
@@ -515,7 +516,7 @@ function runVendorGroupRound(
|
|
|
515
516
|
}
|
|
516
517
|
|
|
517
518
|
async function vendorGroupRound(group: VendorGroupPlan, state: VendorGroupState, nested: boolean) {
|
|
518
|
-
await new Promise(resolve => setTimeout(resolve,
|
|
519
|
+
await new Promise(resolve => setTimeout(resolve, vendorGroupWindowMs()))
|
|
519
520
|
const members = [...state.pending.values()]
|
|
520
521
|
if (members.length === 0) return
|
|
521
522
|
// A lone subpath with no sibling to share a graph with is the single-bundle
|
package/src/types.ts
CHANGED
|
@@ -2,7 +2,8 @@ import type { ComponentChildren } from 'preact'
|
|
|
2
2
|
|
|
3
3
|
export type RouteMode = 'static' | 'dynamic'
|
|
4
4
|
|
|
5
|
-
export
|
|
5
|
+
export const PREFETCH_MODES = ['visible', 'intent', 'load', false] as const
|
|
6
|
+
export type PrefetchMode = (typeof PREFETCH_MODES)[number]
|
|
6
7
|
|
|
7
8
|
export interface PNextRoutes {
|
|
8
9
|
readonly __pnext_internal_route_brand?: never
|
|
@@ -30,6 +31,9 @@ export interface PNextConfig {
|
|
|
30
31
|
// Link hrefs / client navigation (the raw path is preserved end to end).
|
|
31
32
|
skipTrailingSlashRedirect?: boolean
|
|
32
33
|
htmlLimitedBots?: RegExp
|
|
34
|
+
// App-wide default prefetch mode for client-router links that set none of
|
|
35
|
+
// their own. `false` means links never prefetch unless they opt in.
|
|
36
|
+
prefetch?: PrefetchMode
|
|
33
37
|
// Emit browser sourcemaps from the production client build. Off by default,
|
|
34
38
|
// matching Next: shipping maps publishes your source to every visitor, and
|
|
35
39
|
// generating them costs real build time. Dev never emits them (the
|
package/reference/overview.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# pnext
|
|
2
|
-
|
|
3
|
-
pnext is a Preact framework for file-routed applications. It keeps familiar Next-style routing and rendering semantics where they help, and diverges where pnext needs a simpler or smaller model.
|
|
4
|
-
|
|
5
|
-
It renders on the server by default, passes request data through explicit props, provides strong generated route type safety, and ships client JavaScript only for components that opt into running in the browser.
|
|
6
|
-
|
|
7
|
-
## Core Model
|
|
8
|
-
|
|
9
|
-
- Routes live in an `app/` directory.
|
|
10
|
-
- `page.tsx` files define UI routes.
|
|
11
|
-
- `layout.tsx` files export metadata, viewport, and can wrap descendant pages.
|
|
12
|
-
- `route.ts` files define HTTP handlers.
|
|
13
|
-
- `proxy.ts` or `middleware.ts` can run before route matching.
|
|
14
|
-
- `loading.tsx`, `error.tsx`, and `not-found.tsx` define route fallbacks.
|
|
15
|
-
- Pages and layouts are pnext Server Components by default.
|
|
16
|
-
- Server components can be async.
|
|
17
|
-
- Pages without client components emit HTML without pnext client JavaScript.
|
|
18
|
-
- Client Components can be loaded with `dynamic({ load: 'visible' })` and deferred until visible.
|
|
19
|
-
- React compatibility mode supported via `compat.react`.
|
|
20
|
-
- Next compatibility mode supported via `compat.next`.
|
|
21
|
-
- Experimental React Compiler support for React-style Client Components via `compat.reactCompiler`.
|
|
22
|
-
|
|
23
|
-
## References
|
|
24
|
-
|
|
25
|
-
- [Config](./config.md)
|
|
26
|
-
- [Routing](./routing.md)
|
|
27
|
-
- [Metadata](./metadata.md)
|
|
28
|
-
- [Navigation](./navigation.md)
|
|
29
|
-
- [Rendering](./rendering.md)
|
|
30
|
-
- [CSS](./css.md)
|
|
31
|
-
- [Environment Variables](./env.md)
|
|
32
|
-
- [Compatibility](./compat.md)
|
|
33
|
-
- [Typegen](./typegen.md)
|
|
34
|
-
- [Dev Server](./dev.md)
|
|
35
|
-
- [Performance](./performance.md)
|