@wular/pnext 0.0.19 → 0.0.21
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 +2 -2
- package/package.json +1 -1
- package/reference/compat.md +2 -2
- package/reference/getting-started.md +2 -2
- package/src/cli/adapters/vercel.ts +27 -0
- package/src/cli/analyze.ts +8 -7
- package/src/cli/boot/trace.ts +9 -3
- package/src/cli/build/cache.ts +324 -0
- package/src/cli/build.ts +42 -18
- package/src/cli/create.ts +4 -4
- package/src/cli/dev.ts +23 -0
- package/src/cli/migrate/package-json.ts +3 -2
- package/src/cli/migrate/report.ts +1 -1
- package/src/cli/migrate/run.ts +2 -2
- package/src/cli/migrate/scan.ts +128 -1
- package/src/cli/serve/pipeline.ts +42 -42
- package/src/cli/start.ts +20 -20
- package/src/client/build.ts +64 -35
- package/src/client/entry.ts +228 -16
- package/src/client/router/page-slot.ts +81 -0
- package/src/client/router/runtime.ts +107 -112
- package/src/compat/bundler/cjs-exports.ts +26 -20
- package/src/compat/client/css-order.ts +1 -1
- package/src/compat/client/navigation-scroll.ts +36 -40
- package/src/compat/css/modules.ts +6 -2
- package/src/compat/metadata.ts +0 -9
- package/src/compat/next/font/cache.ts +10 -22
- package/src/compat/next/font/runtime.ts +20 -0
- package/src/compat/next/legacy-request-apis.ts +41 -5
- package/src/compat/next/script.tsx +16 -24
- package/src/compat/react/client-lite.ts +1 -0
- package/src/compat/react/client.ts +11 -0
- package/src/compat/react/dom-client.ts +11 -2
- package/src/compat/react/dom-react-server.ts +4 -0
- package/src/compat/react/dom.ts +11 -2
- package/src/compat/react/internals.ts +65 -0
- package/src/compat/react/parity.ts +47 -0
- package/src/compat/react/preact.ts +31 -1
- package/src/compat/react/react-server.ts +7 -0
- package/src/compat/react/server.ts +16 -2
- package/src/compat/react/style-values.ts +68 -0
- package/src/compat/register/proxy.ts +3 -1
- package/src/compat/register/render.ts +14 -3
- package/src/compat/register/routing.ts +57 -7
- package/src/compat/register/typed-routes.ts +0 -2
- package/src/compat/tsconfig-defaults.ts +4 -0
- package/src/compat/typecheck/check.ts +26 -71
- package/src/compat/typecheck/worker.ts +3 -2
- package/src/compat/validation/validate.ts +2 -0
- package/src/compat-bootstrap.ts +4 -0
- package/src/css/build.ts +153 -14
- package/src/css/postcss.ts +32 -3
- package/src/dev/restart/client-key.ts +1 -1
- package/src/dev/restart/global-css.ts +12 -94
- package/src/dev/restart/record.ts +106 -0
- package/src/dev/restart/route-facts.ts +11 -90
- package/src/dev/server.ts +208 -41
- package/src/extensions.ts +69 -3
- package/src/render/hooks.ts +1 -1
- package/src/render/metadata.ts +89 -17
- package/src/render/renderer.ts +325 -138
- package/src/render/resource-hints.ts +62 -9
- package/src/render/slots.tsx +2 -1
- package/src/resolve/scan-facts.ts +13 -5
- package/src/routing/forwarded.ts +20 -7
- package/src/routing/handler.ts +6 -2
- package/src/routing/href.ts +11 -1
- package/src/routing/metadata-files.ts +16 -3
- package/src/routing/proxy.ts +6 -3
- package/src/routing/routes.ts +253 -34
- package/src/runtime/fetch-host.ts +42 -0
- package/src/runtime/fingerprint.ts +132 -0
- package/src/runtime/loader.ts +7 -5
- package/src/runtime/module-cache.ts +3 -36
- package/src/runtime/modules.ts +333 -77
- package/src/runtime/vendor-build.ts +34 -5
- package/src/runtime/vendor.ts +1 -18
- package/src/types.ts +6 -0
- package/src/utils/asset-hash.ts +22 -0
- package/src/utils/code.ts +41 -0
- package/src/utils/html.ts +4 -1
- package/src/utils/registry.ts +70 -0
- package/src/utils/serialize.ts +66 -7
package/src/extensions.ts
CHANGED
|
@@ -376,6 +376,17 @@ export interface RenderExtensions {
|
|
|
376
376
|
context: VaryParamsTrackingContext,
|
|
377
377
|
) => T
|
|
378
378
|
|
|
379
|
+
/**
|
|
380
|
+
* Give a `params`/`searchParams` promise Next 15's transitional SYNC surface (the resolved keys
|
|
381
|
+
* readable without `await`), behind compat.next.legacyRequestAPIs. The SAME promise comes back —
|
|
382
|
+
* callers hand a settled thenable to `use()`. Default: identity.
|
|
383
|
+
*/
|
|
384
|
+
legacySyncProps: <T extends object>(
|
|
385
|
+
promise: Promise<T>,
|
|
386
|
+
kind: 'params' | 'searchParams',
|
|
387
|
+
value: T,
|
|
388
|
+
) => Promise<T>
|
|
389
|
+
|
|
379
390
|
/** Optional opaque state lifecycle around partial prerenders. */
|
|
380
391
|
prerenderSidecar: PrerenderSidecarExtension
|
|
381
392
|
}
|
|
@@ -476,6 +487,7 @@ function createRenderExtensions(): RenderExtensions {
|
|
|
476
487
|
formatErrorLog: () => undefined,
|
|
477
488
|
globalNotFoundEnabled: () => false,
|
|
478
489
|
trackVaryParams: value => value,
|
|
490
|
+
legacySyncProps: promise => promise,
|
|
479
491
|
prerenderSidecar: {
|
|
480
492
|
begin: () => undefined,
|
|
481
493
|
collect: () => Promise.resolve(undefined),
|
|
@@ -704,8 +716,11 @@ export function registerRevalidateBypassToken(token: () => string | undefined):
|
|
|
704
716
|
* Run every registered response finalizer against `response` before it is
|
|
705
717
|
* flushed, then return the response to send. Finalizers see request info + the
|
|
706
718
|
* outgoing status/headers and mutate them in place; when a finalizer changes the
|
|
707
|
-
* status (immutable on a Response) the body is re-wrapped with the new status.
|
|
708
|
-
*
|
|
719
|
+
* status (immutable on a Response) the body is re-wrapped with the new status.
|
|
720
|
+
*
|
|
721
|
+
* The document cache-control default is applied LAST, and only when nothing
|
|
722
|
+
* upstream claimed the header: an ISR/`use cache` route's SWR value, a route
|
|
723
|
+
* handler's own header and compat's RSC `private, no-store` all win over it.
|
|
709
724
|
*/
|
|
710
725
|
export async function finalizeResponse(
|
|
711
726
|
response: Response,
|
|
@@ -714,10 +729,10 @@ export async function finalizeResponse(
|
|
|
714
729
|
routeKind: ResponseRouteKind
|
|
715
730
|
routeMode?: ResponseRouteMode
|
|
716
731
|
hints?: Record<string, unknown>
|
|
732
|
+
dev?: boolean
|
|
717
733
|
},
|
|
718
734
|
): Promise<Response> {
|
|
719
735
|
const finalizers = activeHost.request.responseFinalizers
|
|
720
|
-
if (finalizers.length === 0) return response
|
|
721
736
|
const ctx: ResponseFinalizerContext = {
|
|
722
737
|
request,
|
|
723
738
|
routeKind: info.routeKind,
|
|
@@ -727,6 +742,7 @@ export async function finalizeResponse(
|
|
|
727
742
|
headers: response.headers,
|
|
728
743
|
}
|
|
729
744
|
for (const finalizer of finalizers) await finalizer(ctx)
|
|
745
|
+
applyDocumentCacheControl(ctx, info.dev === true)
|
|
730
746
|
if (ctx.status === response.status) return response
|
|
731
747
|
return new Response(response.body, {
|
|
732
748
|
status: ctx.status,
|
|
@@ -735,6 +751,56 @@ export async function finalizeResponse(
|
|
|
735
751
|
})
|
|
736
752
|
}
|
|
737
753
|
|
|
754
|
+
/**
|
|
755
|
+
* What Next answers a DOCUMENT with, byte for byte (measured against stock
|
|
756
|
+
* next@16.2.12, `next start` and `next dev`; see tests/compat/conformance):
|
|
757
|
+
*
|
|
758
|
+
* dev, every document `no-cache, must-revalidate`
|
|
759
|
+
* prod, per-request document `private, no-cache, no-store, max-age=0, must-revalidate`
|
|
760
|
+
* prod, pure prerender (200) `s-maxage=31536000`
|
|
761
|
+
* prod, ISR / `use cache` `s-maxage=<revalidate>, stale-while-revalidate=<expire-revalidate>`
|
|
762
|
+
* route handler nothing, unless it sets `revalidate`
|
|
763
|
+
*
|
|
764
|
+
* Without one of these an intermediary is free to apply heuristic freshness to
|
|
765
|
+
* a personalised document. A non-200 is never a cacheable prerender in Next
|
|
766
|
+
* (its prerendered 404 still answers with the per-request value), so status
|
|
767
|
+
* gates the static case rather than the route's build-time disposition alone —
|
|
768
|
+
* which is also why no 404/500 render spells its own `no-store` any more: the
|
|
769
|
+
* prod value below already carries it, in Next's words.
|
|
770
|
+
*/
|
|
771
|
+
const DYNAMIC_DOCUMENT_CACHE_CONTROL = 'private, no-cache, no-store, max-age=0, must-revalidate'
|
|
772
|
+
const DEV_DOCUMENT_CACHE_CONTROL = 'no-cache, must-revalidate'
|
|
773
|
+
const DOCUMENT_EXPIRE_SECONDS = 31536000
|
|
774
|
+
|
|
775
|
+
function applyDocumentCacheControl(ctx: ResponseFinalizerContext, dev: boolean): void {
|
|
776
|
+
if (ctx.routeKind !== 'html') return
|
|
777
|
+
if (ctx.headers.has('cache-control')) return
|
|
778
|
+
if (!(ctx.headers.get('content-type') ?? '').startsWith('text/html')) return
|
|
779
|
+
if (dev) {
|
|
780
|
+
ctx.headers.set('cache-control', DEV_DOCUMENT_CACHE_CONTROL)
|
|
781
|
+
return
|
|
782
|
+
}
|
|
783
|
+
if (ctx.status !== 200) {
|
|
784
|
+
ctx.headers.set('cache-control', DYNAMIC_DOCUMENT_CACHE_CONTROL)
|
|
785
|
+
return
|
|
786
|
+
}
|
|
787
|
+
// Compat sets the SWR value itself (its expireTime is configurable and it also
|
|
788
|
+
// covers pages-router getStaticProps); this only catches a core ISR document,
|
|
789
|
+
// which would otherwise be told never to cache a page that is cacheable for N.
|
|
790
|
+
const revalidate = ctx.routeMode === 'isr' ? ctx.hints?.revalidateSeconds : undefined
|
|
791
|
+
if (typeof revalidate === 'number' && revalidate > 0) {
|
|
792
|
+
const swr = Math.max(0, DOCUMENT_EXPIRE_SECONDS - revalidate)
|
|
793
|
+
ctx.headers.set('cache-control', `s-maxage=${revalidate}, stale-while-revalidate=${swr}`)
|
|
794
|
+
return
|
|
795
|
+
}
|
|
796
|
+
ctx.headers.set(
|
|
797
|
+
'cache-control',
|
|
798
|
+
ctx.routeMode === 'static'
|
|
799
|
+
? `s-maxage=${DOCUMENT_EXPIRE_SECONDS}`
|
|
800
|
+
: DYNAMIC_DOCUMENT_CACHE_CONTROL,
|
|
801
|
+
)
|
|
802
|
+
}
|
|
803
|
+
|
|
738
804
|
export function setRequestExtensions(
|
|
739
805
|
overrides: Partial<Omit<RequestExtensions, 'interceptors' | 'responseFinalizers'>>,
|
|
740
806
|
): void {
|
package/src/render/hooks.ts
CHANGED
|
@@ -22,7 +22,7 @@ import { restoreWithExtensionHost } from '../extensions'
|
|
|
22
22
|
// ---------------------------------------------------------------------------
|
|
23
23
|
|
|
24
24
|
/** Next compat is on (server actions, next/* modules, authInterrupts). */
|
|
25
|
-
export function nextCompatEnabled(config: ResolvedConfig): boolean {
|
|
25
|
+
export function nextCompatEnabled(config: Pick<ResolvedConfig, 'compat'>): boolean {
|
|
26
26
|
return Boolean(config.compat?.next)
|
|
27
27
|
}
|
|
28
28
|
|
package/src/render/metadata.ts
CHANGED
|
@@ -60,12 +60,23 @@ export function metadataContext(page: MetadataRenderPage): MetadataContext {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
63
|
+
// Links describing the render's own bytes, not metadata - so they stay in the
|
|
64
|
+
// head even when metadata streams into the body.
|
|
65
|
+
export function assetLinks(page: MetadataRenderPage) {
|
|
66
|
+
return assetLinkGroups(page).filter(Boolean).join('\n')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function assetLinkGroups(page: MetadataRenderPage) {
|
|
70
|
+
const connects: ResourceHint[] = []
|
|
71
|
+
const preloads: ResourceHint[] = []
|
|
72
|
+
const lateHints: ResourceHint[] = []
|
|
73
|
+
for (const hint of page.resourceHints ?? []) {
|
|
74
|
+
if (hint.rel === 'preconnect' || hint.rel === 'dns-prefetch') connects.push(hint)
|
|
75
|
+
else if (hint.rel === 'preload' && (hint.as === 'font' || hint.as === 'image'))
|
|
76
|
+
preloads.push(hint)
|
|
77
|
+
else lateHints.push(hint)
|
|
78
|
+
}
|
|
79
|
+
const bulk: (MetadataLink | ResourceHint)[] = [
|
|
69
80
|
...(page.modulePreloads ?? []).map(url => ({ rel: 'modulepreload', url })),
|
|
70
81
|
...(page.dynamicScriptPreloads ?? []).map(url => ({
|
|
71
82
|
rel: 'preload',
|
|
@@ -76,6 +87,20 @@ export function headLinks(page: MetadataRenderPage, context: MetadataContext) {
|
|
|
76
87
|
crossOrigin: 'anonymous' as const,
|
|
77
88
|
fetchPriority: 'low' as const,
|
|
78
89
|
})),
|
|
90
|
+
]
|
|
91
|
+
return [
|
|
92
|
+
renderLinks(connects),
|
|
93
|
+
renderLinks([...page.fontPreloads, ...preloads]),
|
|
94
|
+
renderLinks(stylesheetLinks(page.stylesheets)),
|
|
95
|
+
renderLinks(bulk),
|
|
96
|
+
renderLinks(lateHints),
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Links derived from `metadata` - these travel with it into the body.
|
|
101
|
+
export function metadataOnlyLinks(page: MetadataRenderPage, context: MetadataContext) {
|
|
102
|
+
const canonical = coerceCanonical(page.metadata.alternates, context)
|
|
103
|
+
const links: (MetadataLink | ResourceHint)[] = [
|
|
79
104
|
...(canonical ? [{ rel: 'canonical', url: canonical }] : []),
|
|
80
105
|
...staticMetadataLinks(page),
|
|
81
106
|
...authorLinks(page.metadata.authors, context),
|
|
@@ -86,6 +111,14 @@ export function headLinks(page: MetadataRenderPage, context: MetadataContext) {
|
|
|
86
111
|
...coerceIcons(page.metadata.icons, context),
|
|
87
112
|
...(page.metadata.links ?? []),
|
|
88
113
|
]
|
|
114
|
+
return renderLinks(links)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function headLinks(page: MetadataRenderPage, context: MetadataContext) {
|
|
118
|
+
return [assetLinks(page), metadataOnlyLinks(page, context)].filter(Boolean).join('\n')
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function renderLinks(links: (MetadataLink | ResourceHint)[]) {
|
|
89
122
|
return links.map(link => ` <link ${linkAttrs(link)}/>`).join('\n')
|
|
90
123
|
}
|
|
91
124
|
|
|
@@ -122,11 +155,13 @@ function staticMetadataLinks(page: MetadataRenderPage): MetadataLink[] {
|
|
|
122
155
|
...(!hasExplicitManifest && metadata.manifest ? [staticMetadataLink(metadata.manifest)] : []),
|
|
123
156
|
...metadata.icons.map(staticMetadataLink),
|
|
124
157
|
...(metadata.dynamicIcons ?? []),
|
|
125
|
-
|
|
126
|
-
|
|
158
|
+
// Root icons share the icon bucket. Keeping them after apple icons made
|
|
159
|
+
// the link order differ from Next whenever both conventions were present.
|
|
127
160
|
...(!hasExplicitIcons && metadata.icons.length === 0
|
|
128
161
|
? metadata.rootIcons.map(staticMetadataLink)
|
|
129
162
|
: []),
|
|
163
|
+
...metadata.appleIcons.map(staticMetadataLink),
|
|
164
|
+
...(metadata.dynamicAppleIcons ?? []),
|
|
130
165
|
])
|
|
131
166
|
}
|
|
132
167
|
|
|
@@ -233,8 +268,9 @@ export function metadataTags(metadata: Metadata, context: MetadataContext): stri
|
|
|
233
268
|
}
|
|
234
269
|
for (const [property, value] of appLinkTags(meta.appLinks)) pushProperty(property, value)
|
|
235
270
|
|
|
236
|
-
|
|
237
|
-
tags.push(...
|
|
271
|
+
const social = resolveSocialMetadata(meta)
|
|
272
|
+
tags.push(...openGraphTags(social.openGraph, context))
|
|
273
|
+
tags.push(...twitterTags(social.twitter, context))
|
|
238
274
|
|
|
239
275
|
return tags.join('\n ')
|
|
240
276
|
}
|
|
@@ -831,17 +867,53 @@ function twitterCard(twitter: MetadataTwitter) {
|
|
|
831
867
|
return twitter.images === undefined ? 'summary' : 'summary_large_image'
|
|
832
868
|
}
|
|
833
869
|
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
870
|
+
// og/twitter resolve in one final pass over the merged chain: twitter auto-fills
|
|
871
|
+
// from openGraph, then whichever of the two exists inherits the page
|
|
872
|
+
// title/description it still lacks. Emission is that pass - nothing between the
|
|
873
|
+
// merge and here reads these fields, and the parent handed to generateMetadata
|
|
874
|
+
// is pre-merge by design.
|
|
875
|
+
function resolveSocialMetadata(meta: Metadata): {
|
|
876
|
+
openGraph?: MetadataOpenGraph
|
|
877
|
+
twitter?: MetadataTwitter
|
|
878
|
+
} {
|
|
879
|
+
const og = meta.openGraph
|
|
880
|
+
let twitter = meta.twitter
|
|
881
|
+
if (og) {
|
|
882
|
+
const ownImages = twitter && 'images' in twitter && twitter.images
|
|
883
|
+
twitter = {
|
|
884
|
+
...twitter,
|
|
885
|
+
// card follows twitter's OWN images: an og image inherited below never
|
|
886
|
+
// upgrades a declared twitter block to summary_large_image.
|
|
887
|
+
card: twitterCard(twitter ?? { images: og.images }),
|
|
888
|
+
title: titleText(twitter?.title) === undefined ? og.title : twitter?.title,
|
|
889
|
+
description: twitter?.description || og.description,
|
|
890
|
+
images: ownImages ? twitter?.images : og.images,
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return { openGraph: og && inherited(og, meta), twitter: twitter && inherited(twitter, meta) }
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// A block that renders no title/description of its own falls back to the page's.
|
|
897
|
+
function inherited<T extends { title?: Metadata['title']; description?: string }>(
|
|
898
|
+
block: T,
|
|
899
|
+
meta: Metadata,
|
|
900
|
+
): T {
|
|
901
|
+
const own = titleText(block.title)
|
|
902
|
+
if (own !== undefined && block.description) return block
|
|
838
903
|
return {
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
904
|
+
...block,
|
|
905
|
+
title: own === undefined ? titleText(meta.title) : block.title,
|
|
906
|
+
description: block.description || meta.description,
|
|
842
907
|
}
|
|
843
908
|
}
|
|
844
909
|
|
|
910
|
+
// The title a chain entry actually renders, or undefined when it renders none
|
|
911
|
+
// (a bare `template`, an empty string) and so counts as missing for inheritance.
|
|
912
|
+
function titleText(title: Metadata['title']): string | undefined {
|
|
913
|
+
if (title === undefined) return undefined
|
|
914
|
+
return resolveTitleChain([{ title }]) || undefined
|
|
915
|
+
}
|
|
916
|
+
|
|
845
917
|
export function mergeViewport(...items: Viewport[]) {
|
|
846
918
|
return items.reduce<Viewport>(
|
|
847
919
|
(viewport, item) => ({
|