@nifrajs/web 5.0.0 → 5.2.0
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/CHANGELOG.md +2970 -0
- package/README.md +8 -0
- package/dist/build-plan.d.ts +7 -0
- package/dist/build-plan.d.ts.map +1 -1
- package/dist/build-plan.js +19 -0
- package/dist/build-plan.js.map +1 -1
- package/dist/build-vite.d.ts +1 -0
- package/dist/build-vite.d.ts.map +1 -1
- package/dist/build-vite.js +51 -3
- package/dist/build-vite.js.map +1 -1
- package/dist/build.d.ts +12 -1
- package/dist/build.d.ts.map +1 -1
- package/dist/build.js +43 -6
- package/dist/build.js.map +1 -1
- package/dist/client.d.ts +7 -6
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +39 -22
- package/dist/client.js.map +1 -1
- package/dist/dev-port.d.ts +3 -2
- package/dist/dev-port.d.ts.map +1 -1
- package/dist/dev-port.js +2 -6
- package/dist/dev-port.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/internal/codegen.d.ts +6 -0
- package/dist/internal/codegen.d.ts.map +1 -1
- package/dist/internal/codegen.js +23 -5
- package/dist/internal/codegen.js.map +1 -1
- package/dist/internal/copy-split.d.ts +27 -0
- package/dist/internal/copy-split.d.ts.map +1 -0
- package/dist/internal/copy-split.js +173 -0
- package/dist/internal/copy-split.js.map +1 -0
- package/dist/internal/package-entries.d.ts +34 -0
- package/dist/internal/package-entries.d.ts.map +1 -0
- package/dist/internal/package-entries.js +324 -0
- package/dist/internal/package-entries.js.map +1 -0
- package/dist/internal/page-execution.d.ts +19 -1
- package/dist/internal/page-execution.d.ts.map +1 -1
- package/dist/internal/page-execution.js +123 -14
- package/dist/internal/page-execution.js.map +1 -1
- package/dist/internal/parity.d.ts.map +1 -1
- package/dist/internal/parity.js +15 -2
- package/dist/internal/parity.js.map +1 -1
- package/dist/internal/private-env.d.ts +8 -0
- package/dist/internal/private-env.d.ts.map +1 -1
- package/dist/internal/private-env.js +25 -10
- package/dist/internal/private-env.js.map +1 -1
- package/dist/internal/render-document.d.ts +15 -46
- package/dist/internal/render-document.d.ts.map +1 -1
- package/dist/internal/render-document.js +59 -62
- package/dist/internal/render-document.js.map +1 -1
- package/dist/internal/route-path.d.ts +6 -0
- package/dist/internal/route-path.d.ts.map +1 -1
- package/dist/internal/route-path.js +11 -0
- package/dist/internal/route-path.js.map +1 -1
- package/dist/internal/secret-scan.d.ts.map +1 -1
- package/dist/internal/secret-scan.js +103 -1
- package/dist/internal/secret-scan.js.map +1 -1
- package/dist/internal/typescript-api.d.ts +4 -0
- package/dist/internal/typescript-api.d.ts.map +1 -0
- package/dist/internal/typescript-api.js +8 -0
- package/dist/internal/typescript-api.js.map +1 -0
- package/dist/internal/vite-plugin-compat.d.ts +28 -0
- package/dist/internal/vite-plugin-compat.d.ts.map +1 -0
- package/dist/internal/vite-plugin-compat.js +82 -0
- package/dist/internal/vite-plugin-compat.js.map +1 -0
- package/dist/internal/web-app.d.ts +38 -2
- package/dist/internal/web-app.d.ts.map +1 -1
- package/dist/internal/web-app.js +30 -3
- package/dist/internal/web-app.js.map +1 -1
- package/dist/internal/without-query.d.ts +4 -0
- package/dist/internal/without-query.d.ts.map +1 -0
- package/dist/internal/without-query.js +9 -0
- package/dist/internal/without-query.js.map +1 -0
- package/dist/internal/zone-graph.d.ts.map +1 -1
- package/dist/internal/zone-graph.js +17 -6
- package/dist/internal/zone-graph.js.map +1 -1
- package/dist/isr.d.ts +1 -1
- package/dist/isr.d.ts.map +1 -1
- package/dist/isr.js +15 -2
- package/dist/isr.js.map +1 -1
- package/dist/manifest.d.ts +9 -0
- package/dist/manifest.d.ts.map +1 -1
- package/dist/manifest.js +1 -0
- package/dist/manifest.js.map +1 -1
- package/dist/plugins/import-glob.d.ts +20 -0
- package/dist/plugins/import-glob.d.ts.map +1 -0
- package/dist/plugins/import-glob.js +401 -0
- package/dist/plugins/import-glob.js.map +1 -0
- package/dist/plugins/kit.d.ts +11 -0
- package/dist/plugins/kit.d.ts.map +1 -1
- package/dist/plugins/kit.js +32 -0
- package/dist/plugins/kit.js.map +1 -1
- package/dist/plugins/stylex.d.ts.map +1 -1
- package/dist/plugins/stylex.js +2 -2
- package/dist/plugins/stylex.js.map +1 -1
- package/dist/plugins/vite-leak-guard.d.ts +6 -0
- package/dist/plugins/vite-leak-guard.d.ts.map +1 -1
- package/dist/plugins/vite-leak-guard.js +80 -7
- package/dist/plugins/vite-leak-guard.js.map +1 -1
- package/dist/plugins/vite-zone-guard.d.ts.map +1 -1
- package/dist/plugins/vite-zone-guard.js +17 -21
- package/dist/plugins/vite-zone-guard.js.map +1 -1
- package/dist/plugins/zone-guard.d.ts.map +1 -1
- package/dist/plugins/zone-guard.js +44 -3
- package/dist/plugins/zone-guard.js.map +1 -1
- package/dist/prerender.d.ts +5 -1
- package/dist/prerender.d.ts.map +1 -1
- package/dist/prerender.js +41 -19
- package/dist/prerender.js.map +1 -1
- package/dist/render-seam.d.ts +60 -2
- package/dist/render-seam.d.ts.map +1 -1
- package/dist/render-seam.js +33 -0
- package/dist/render-seam.js.map +1 -1
- package/dist/router.d.ts +3 -2
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +13 -9
- package/dist/router.js.map +1 -1
- package/dist/seo.d.ts +49 -0
- package/dist/seo.d.ts.map +1 -0
- package/dist/seo.js +48 -0
- package/dist/seo.js.map +1 -0
- package/dist/server-components/typescript.d.ts.map +1 -1
- package/dist/server-components/typescript.js +2 -7
- package/dist/server-components/typescript.js.map +1 -1
- package/dist/vite.d.ts +1 -28
- package/dist/vite.d.ts.map +1 -1
- package/dist/vite.js +3 -69
- package/dist/vite.js.map +1 -1
- package/dist/zones.d.ts +30 -8
- package/dist/zones.d.ts.map +1 -1
- package/dist/zones.js +185 -46
- package/dist/zones.js.map +1 -1
- package/package.json +10 -4
- package/src/build-plan.ts +21 -0
- package/src/build-vite.ts +60 -3
- package/src/build.ts +57 -4
- package/src/client.ts +42 -22
- package/src/dev-port.ts +4 -10
- package/src/index.ts +8 -4
- package/src/internal/codegen.ts +32 -3
- package/src/internal/copy-split.ts +220 -0
- package/src/internal/package-entries.ts +331 -0
- package/src/internal/page-execution.ts +150 -13
- package/src/internal/parity.ts +15 -2
- package/src/internal/private-env.ts +27 -13
- package/src/internal/render-document.ts +100 -81
- package/src/internal/route-path.ts +16 -0
- package/src/internal/secret-scan.ts +101 -1
- package/src/internal/typescript-api.ts +11 -0
- package/src/internal/vite-plugin-compat.ts +81 -0
- package/src/internal/web-app.ts +71 -6
- package/src/internal/without-query.ts +8 -0
- package/src/internal/zone-graph.ts +19 -6
- package/src/isr.ts +13 -2
- package/src/manifest.ts +10 -0
- package/src/plugins/import-glob.ts +392 -0
- package/src/plugins/kit.ts +36 -0
- package/src/plugins/stylex.ts +2 -1
- package/src/plugins/vite-leak-guard.ts +99 -7
- package/src/plugins/vite-zone-guard.ts +21 -19
- package/src/plugins/zone-guard.ts +41 -2
- package/src/prerender.ts +39 -18
- package/src/render-seam.ts +88 -2
- package/src/router.ts +16 -11
- package/src/seo.ts +68 -0
- package/src/server-components/typescript.ts +2 -10
- package/src/vite.ts +3 -81
- package/src/zones.ts +237 -52
package/src/router.ts
CHANGED
|
@@ -10,7 +10,7 @@ import type { StandardSchemaV1 } from "@nifrajs/core/server"
|
|
|
10
10
|
import type { BoundaryDescriptor, BoundaryStates } from "./boundary.ts"
|
|
11
11
|
import { parseNdjsonData } from "./deferred.ts"
|
|
12
12
|
import { parsePageData } from "./internal/revive-dates.ts"
|
|
13
|
-
import { requestPattern } from "./internal/route-path.ts"
|
|
13
|
+
import { appUrl, requestPattern } from "./internal/route-path.ts"
|
|
14
14
|
import type { ClientActionResult, ClientRequestBody, ClientRouteHooks } from "./manifest.ts"
|
|
15
15
|
import type { MatchChain } from "./render-seam.ts"
|
|
16
16
|
import { isClientOnlySearchChange } from "./search.ts"
|
|
@@ -310,8 +310,9 @@ export interface ClientRouterOptions {
|
|
|
310
310
|
/** Override the loader-data fetch (tests inject a stub; defaults to a same-origin JSON GET). */
|
|
311
311
|
readonly fetchData?: FetchRouteData
|
|
312
312
|
/** Ensure a route's code chunk is loaded before rendering (code-splitting). Awaited in parallel
|
|
313
|
-
* with the loader data, so `pending` covers both. Omit when the bundle isn't split.
|
|
314
|
-
|
|
313
|
+
* with the loader data, so `pending` covers both. Omit when the bundle isn't split. `path` is the URL
|
|
314
|
+
* the route is about to render, for code loaded per URL (a production build's copy chunks). */
|
|
315
|
+
readonly loadModule?: (routeId: string, path?: string) => Promise<void>
|
|
315
316
|
/** Terminal status → client route id. Generated entries populate this from `_404` and
|
|
316
317
|
* `_<status>` files so soft navigation renders the same boundary as a hard request. */
|
|
317
318
|
readonly statusRoutes?: Readonly<Record<number, string>>
|
|
@@ -429,7 +430,7 @@ const defaultFetchData: FetchRouteData = async (path, _match, signal, navigation
|
|
|
429
430
|
const cut = query === -1 ? hash : hash === -1 ? query : Math.min(query, hash)
|
|
430
431
|
const pathname = cut === -1 ? path : path.slice(0, cut)
|
|
431
432
|
if (prerenderedSetOf(prerendered).has(pathname)) {
|
|
432
|
-
const staticRes = await fetch(dataUrlFor(pathname), { signal: signal ?? null })
|
|
433
|
+
const staticRes = await fetch(appUrl(dataUrlFor(pathname)), { signal: signal ?? null })
|
|
433
434
|
if (staticRes.ok) return readResponseData(staticRes, signal)
|
|
434
435
|
}
|
|
435
436
|
}
|
|
@@ -438,7 +439,7 @@ const defaultFetchData: FetchRouteData = async (path, _match, signal, navigation
|
|
|
438
439
|
headers.set(RETAIN_HEADER, navigation.retain.join(","))
|
|
439
440
|
headers.set(NAV_FROM_HEADER, navigation.from)
|
|
440
441
|
}
|
|
441
|
-
const res = await fetch(path, { headers, signal: signal ?? null })
|
|
442
|
+
const res = await fetch(appUrl(path), { headers, signal: signal ?? null })
|
|
442
443
|
const redirectTo = res.headers.get(REDIRECT_HEADER)
|
|
443
444
|
if (redirectTo !== null) throw redirected(redirectTo)
|
|
444
445
|
const status = Number(res.headers.get(STATUS_HEADER))
|
|
@@ -667,7 +668,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
667
668
|
)
|
|
668
669
|
if (mine !== generation) return
|
|
669
670
|
if (loaded.terminalRouteId !== undefined) {
|
|
670
|
-
await loadModule?.(loaded.terminalRouteId)
|
|
671
|
+
await loadModule?.(loaded.terminalRouteId, state.path)
|
|
671
672
|
state = { ...state, routeId: loaded.terminalRouteId, params: {}, data: null }
|
|
672
673
|
} else {
|
|
673
674
|
state = {
|
|
@@ -788,7 +789,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
788
789
|
state = { ...state, pendingPath: at }
|
|
789
790
|
emit()
|
|
790
791
|
}
|
|
791
|
-
await loadModule?.(route.routeId)
|
|
792
|
+
await loadModule?.(route.routeId, at)
|
|
792
793
|
// Prefetched server data is one-shot; a clientLoader still runs over it without another request.
|
|
793
794
|
const prefetchedData = usePrefetch ? freshPrefetch(at) : undefined
|
|
794
795
|
if (usePrefetch) prefetched.delete(at)
|
|
@@ -801,7 +802,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
801
802
|
prefetchedData,
|
|
802
803
|
)
|
|
803
804
|
if (loaded.terminalRouteId !== undefined) {
|
|
804
|
-
await loadModule?.(loaded.terminalRouteId)
|
|
805
|
+
await loadModule?.(loaded.terminalRouteId, at)
|
|
805
806
|
if (options.layoutStatusRoutes?.[loaded.terminalRouteId] === true) {
|
|
806
807
|
throw new Error(
|
|
807
808
|
`[nifra/web] ${loaded.terminalRouteId} renders inside layouts; loading ${at} as a document`,
|
|
@@ -943,7 +944,11 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
943
944
|
}
|
|
944
945
|
fEmit()
|
|
945
946
|
try {
|
|
946
|
-
const res = await fetch(action, {
|
|
947
|
+
const res = await fetch(appUrl(action), {
|
|
948
|
+
method: "POST",
|
|
949
|
+
body,
|
|
950
|
+
headers: { [DATA_HEADER]: "1" },
|
|
951
|
+
})
|
|
947
952
|
if (!res.ok)
|
|
948
953
|
throw new Error(`[nifra/web] fetcher action failed (${res.status}): ${action}`)
|
|
949
954
|
const actionData = res.status === 204 ? undefined : await readResponseData(res, ac.signal)
|
|
@@ -1094,7 +1099,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1094
1099
|
state = { ...state, actionData: prepared.optimisticData }
|
|
1095
1100
|
emit()
|
|
1096
1101
|
}
|
|
1097
|
-
const res = await fetch(action, {
|
|
1102
|
+
const res = await fetch(appUrl(action), {
|
|
1098
1103
|
method: "POST",
|
|
1099
1104
|
body: prepared.body,
|
|
1100
1105
|
headers: { [DATA_HEADER]: "1" },
|
|
@@ -1211,7 +1216,7 @@ export function createClientRouter(options: ClientRouterOptions): ClientRouter {
|
|
|
1211
1216
|
// Unwrapped here, so a prefetched entry is the same shape `navigate` builds state from -
|
|
1212
1217
|
// otherwise a prefetch hit would publish an envelope where a miss publishes loader data.
|
|
1213
1218
|
const [, loaded] = await Promise.all([
|
|
1214
|
-
loadModule?.(matched.routeId),
|
|
1219
|
+
loadModule?.(matched.routeId, path),
|
|
1215
1220
|
loadRouteData(path, matched),
|
|
1216
1221
|
])
|
|
1217
1222
|
prefetched.delete(path) // a stale entry re-enters as the newest
|
package/src/seo.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Head helpers for a route's `meta`: `canonical()`, `openGraph()` and `jsonLd()`. `meta()` runs on the
|
|
3
|
+
* server and in the browser, and this entry imports nothing at runtime, so browser code can take the
|
|
4
|
+
* helpers from here without bundling the server modules the package root also exports.
|
|
5
|
+
*/
|
|
6
|
+
import type { LinkDescriptor, MetaDescriptor, ScriptDescriptor } from "./manifest.ts"
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A `<link rel="canonical">` descriptor for a route's `meta.link`. The canonical URL tells search
|
|
10
|
+
* engines which URL is authoritative for a page (deduping query-string / tracking variants).
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* export const meta = (a) => ({ link: [canonical(`https://site.com/posts/${a.params.slug}`)] })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function canonical(href: string): LinkDescriptor {
|
|
17
|
+
return { rel: "canonical", href }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Inputs for {@link openGraph} - the common Open Graph properties. All optional; only the provided
|
|
21
|
+
* ones become tags. `type` defaults to `"website"`. */
|
|
22
|
+
export interface OpenGraphInput {
|
|
23
|
+
readonly title?: string
|
|
24
|
+
readonly description?: string
|
|
25
|
+
/** Absolute URL of the share image (`og:image`). */
|
|
26
|
+
readonly image?: string
|
|
27
|
+
/** Canonical URL of the page (`og:url`). */
|
|
28
|
+
readonly url?: string
|
|
29
|
+
/** Object type (`og:type`) - `"website"`, `"article"`, … Default `"website"`. */
|
|
30
|
+
readonly type?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build the Open Graph `<meta property="og:*">` entries for a route's `meta.meta`. Returns only the
|
|
35
|
+
* properties you supplied (plus `og:type`, defaulting to `"website"`), so it composes with other meta.
|
|
36
|
+
*
|
|
37
|
+
* ```ts
|
|
38
|
+
* export const meta = { meta: [...openGraph({ title: "Nifra", image: "https://site.com/og.png" })] }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function openGraph(input: OpenGraphInput): MetaDescriptor[] {
|
|
42
|
+
const tags: MetaDescriptor[] = []
|
|
43
|
+
const add = (property: string, content: string | undefined): void => {
|
|
44
|
+
if (content !== undefined) tags.push({ property, content })
|
|
45
|
+
}
|
|
46
|
+
add("og:title", input.title)
|
|
47
|
+
add("og:description", input.description)
|
|
48
|
+
add("og:image", input.image)
|
|
49
|
+
add("og:url", input.url)
|
|
50
|
+
// `og:type` always present (the spec's required property) - default "website" when unset.
|
|
51
|
+
tags.push({ property: "og:type", content: input.type ?? "website" })
|
|
52
|
+
return tags
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Build a JSON-LD `<script type="application/ld+json">` entry for a route's `meta.script` from a plain
|
|
57
|
+
* object. `JSON.stringify` produces the body; the head renderer breakout-escapes it (see
|
|
58
|
+
* `escapeScriptContent`), so a string field containing `</script>` is embedded safely.
|
|
59
|
+
*
|
|
60
|
+
* ```ts
|
|
61
|
+
* export const meta = {
|
|
62
|
+
* script: [jsonLd({ "@context": "https://schema.org", "@type": "Article", headline: "Hi" })],
|
|
63
|
+
* }
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export function jsonLd(data: Record<string, unknown>): ScriptDescriptor {
|
|
67
|
+
return { type: "application/ld+json", content: JSON.stringify(data) }
|
|
68
|
+
}
|
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
import { createRequire } from "node:module"
|
|
2
2
|
import type * as TS from "typescript"
|
|
3
|
+
import { isTypeScriptCompiler } from "../internal/typescript-api.ts"
|
|
3
4
|
|
|
4
5
|
/** The code of the error a TypeScript without the in-process compiler API raises here. */
|
|
5
6
|
export const UNSUPPORTED_TYPESCRIPT = "NIFRA_TS_UNSUPPORTED"
|
|
6
7
|
|
|
7
|
-
function isCompiler(value: unknown): value is typeof TS {
|
|
8
|
-
return (
|
|
9
|
-
typeof value === "object" &&
|
|
10
|
-
value !== null &&
|
|
11
|
-
"createSourceFile" in value &&
|
|
12
|
-
typeof value.createSourceFile === "function"
|
|
13
|
-
)
|
|
14
|
-
}
|
|
15
|
-
|
|
16
8
|
/**
|
|
17
9
|
* The TypeScript compiler API, resolved from `from` the way `import "typescript"` there would be.
|
|
18
10
|
* TypeScript 7's package ships no in-process compiler API (its root export is only `version`), so
|
|
@@ -20,7 +12,7 @@ function isCompiler(value: unknown): value is typeof TS {
|
|
|
20
12
|
*/
|
|
21
13
|
export function loadTypeScript(from: string = import.meta.url): typeof TS {
|
|
22
14
|
const loaded: unknown = createRequire(from)("typescript")
|
|
23
|
-
if (
|
|
15
|
+
if (isTypeScriptCompiler(loaded)) return loaded
|
|
24
16
|
const version =
|
|
25
17
|
typeof loaded === "object" &&
|
|
26
18
|
loaded !== null &&
|
package/src/vite.ts
CHANGED
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
} from "./internal/parity.ts"
|
|
36
36
|
import { vitePublicEnvPrefix } from "./internal/server-boundary.ts"
|
|
37
37
|
import { importVite } from "./internal/vite-import.ts"
|
|
38
|
+
import { normalizeRolldownPlugins } from "./internal/vite-plugin-compat.ts"
|
|
38
39
|
import { scopedName } from "./plugins/css-modules.ts"
|
|
39
40
|
import {
|
|
40
41
|
DEV_ROOT_ENV,
|
|
@@ -168,6 +169,7 @@ export interface ViteDevServer {
|
|
|
168
169
|
|
|
169
170
|
export type { DevAppHooks } from "./dev-session.ts"
|
|
170
171
|
export { LAST_ERROR_PATH } from "./diagnostic.ts"
|
|
172
|
+
export { normalizeRolldownPlugins } from "./internal/vite-plugin-compat.ts"
|
|
171
173
|
|
|
172
174
|
// The codegen'd client entry is written here (at the Vite root) so Vite serves + HMRs it.
|
|
173
175
|
const DEV_ENTRY = ".nifra-vite-entry.tsx"
|
|
@@ -247,19 +249,6 @@ function toWebRequest(req: IncomingMessage, body: Buffer | undefined): Request {
|
|
|
247
249
|
return new Request(url, init as unknown as ConstructorParameters<typeof Request>[1])
|
|
248
250
|
}
|
|
249
251
|
|
|
250
|
-
// A Vite `config` hook: a plain function, OR the object form `{ handler, order }` Vite accepts for hook
|
|
251
|
-
// ordering (`order: "pre" | "post"`). We wrap the handler in either shape - see normalizeRolldownPlugins.
|
|
252
|
-
type ConfigFn = (config: unknown, env: unknown) => unknown
|
|
253
|
-
type ConfigHook = ConfigFn | { readonly handler: ConfigFn; readonly order?: unknown }
|
|
254
|
-
|
|
255
|
-
// A Vite plugin - the only hook we wrap is `config`. Typed structurally (no `vite` type dep). Everything
|
|
256
|
-
// else on the plugin object is preserved by spread, so wrapping is transparent to Vite.
|
|
257
|
-
interface VitePluginLike {
|
|
258
|
-
readonly name?: string
|
|
259
|
-
config?: ConfigHook
|
|
260
|
-
readonly [key: string]: unknown
|
|
261
|
-
}
|
|
262
|
-
|
|
263
252
|
/** The bits of a Node ServerResponse `pipeWebBodyToNode` touches - structural, to avoid a node:http dep here. */
|
|
264
253
|
interface NodeResLike {
|
|
265
254
|
flushHeaders?(): void
|
|
@@ -317,72 +306,6 @@ export async function pipeWebBodyToNode(
|
|
|
317
306
|
res.end()
|
|
318
307
|
}
|
|
319
308
|
|
|
320
|
-
/**
|
|
321
|
-
* Strip `optimizeDeps.rollupOptions.jsx` from a plugin's `config` hook output when running under
|
|
322
|
-
* rolldown-vite - the source of the scary, harmless `Warning: Invalid input options … "jsx" Invalid
|
|
323
|
-
* key: Expected never but received "jsx"` on `nifra dev`.
|
|
324
|
-
*
|
|
325
|
-
* Why it happens: `@vitejs/plugin-react@4.x` (and peers) target an *older* rolldown-vite optimizeDeps
|
|
326
|
-
* API - they inject `optimizeDeps.rollupOptions.jsx` to tell the dep pre-bundler to transform JSX. Vite
|
|
327
|
-
* 8's rolldown dep-optimizer renamed that surface to `optimizeDeps.rolldownOptions` (and moved jsx under
|
|
328
|
-
* `transform.jsx`), so the stale `rollupOptions.jsx` is an unrecognized input option → the warning. It's
|
|
329
|
-
* a version-skew artifact, not a real misconfig: the route source JSX transform runs through the
|
|
330
|
-
* plugin's own `transform` hook (untouched here), and node_modules deps that get pre-bundled almost
|
|
331
|
-
* never contain raw JSX - so dropping the dead key changes no behavior and keeps HMR/Fast Refresh
|
|
332
|
-
* intact. We *strip* (rather than translate to `rolldownOptions`) so the fix is version-agnostic: a
|
|
333
|
-
* plugin already emitting the correct `rolldownOptions` is left untouched, and a future plugin bump that
|
|
334
|
-
* stops emitting `rollupOptions.jsx` makes this a no-op.
|
|
335
|
-
*
|
|
336
|
-
* Scoped narrowly: only the `optimizeDeps.rollupOptions.jsx` key is removed, only under rolldown-vite,
|
|
337
|
-
* and only from the value a plugin's `config` hook returns. Non-rolldown Vite is passed through verbatim.
|
|
338
|
-
*
|
|
339
|
-
* FLATTEN FIRST: a Vite plugin factory may return an ARRAY of plugins - `@vitejs/plugin-react`'s `react()`
|
|
340
|
-
* returns `[vite:react-babel, vite:react-refresh]`, and it's `react:react-babel`'s `config` hook that emits
|
|
341
|
-
* the offending `optimizeDeps.rollupOptions.jsx`. `nifra.config.ts` writes `vitePlugins = [react()]`, so the
|
|
342
|
-
* plugin list arrives NESTED (`[[babel, refresh]]`). Without flattening, `.map` sees the inner array (which
|
|
343
|
-
* has no `config`), leaves it untouched, and Vite - which flattens plugin arrays itself before running them
|
|
344
|
-
* - then executes the un-stripped babel hook, so the warning survives. Flattening here (Vite accepts a flat
|
|
345
|
-
* list identically) is what lets the strip reach every real plugin.
|
|
346
|
-
*/
|
|
347
|
-
export function normalizeRolldownPlugins(
|
|
348
|
-
plugins: readonly unknown[],
|
|
349
|
-
isRolldown: boolean,
|
|
350
|
-
): readonly unknown[] {
|
|
351
|
-
if (!isRolldown) return plugins
|
|
352
|
-
const stripJsxKey = (returned: unknown): unknown => {
|
|
353
|
-
// Only touch a plain-object config carrying optimizeDeps.rollupOptions.jsx; leave anything else as-is.
|
|
354
|
-
if (returned === null || typeof returned !== "object") return returned
|
|
355
|
-
const cfg = returned as { optimizeDeps?: { rollupOptions?: Record<string, unknown> } }
|
|
356
|
-
const rollupOptions = cfg.optimizeDeps?.rollupOptions
|
|
357
|
-
if (rollupOptions === undefined || !("jsx" in rollupOptions)) return returned
|
|
358
|
-
// Clone the affected branch (never mutate the plugin's own return value) and drop the dead key.
|
|
359
|
-
const { jsx: _dropped, ...restRollup } = rollupOptions
|
|
360
|
-
return {
|
|
361
|
-
...cfg,
|
|
362
|
-
optimizeDeps: { ...cfg.optimizeDeps, rollupOptions: restRollup },
|
|
363
|
-
}
|
|
364
|
-
}
|
|
365
|
-
return plugins.flat(Number.POSITIVE_INFINITY).map((plugin) => {
|
|
366
|
-
if (plugin === null || typeof plugin !== "object") return plugin
|
|
367
|
-
const p = plugin as VitePluginLike
|
|
368
|
-
// `config` may be a plain function or the object form `{ handler, order }`. Wrap the handler either way.
|
|
369
|
-
const hook = p.config
|
|
370
|
-
const handler = typeof hook === "function" ? hook : hook?.handler
|
|
371
|
-
if (typeof handler !== "function") return plugin
|
|
372
|
-
const wrappedHandler: ConfigFn = (config, env) => {
|
|
373
|
-
const returned = handler(config, env)
|
|
374
|
-
// The hook may return a promise - normalize both shapes.
|
|
375
|
-
return returned instanceof Promise ? returned.then(stripJsxKey) : stripJsxKey(returned)
|
|
376
|
-
}
|
|
377
|
-
// Preserve the ORIGINAL shape: a function stays a function; the object form keeps its `order`
|
|
378
|
-
// (collapsing `{ handler, order }` to a bare function would silently drop the hook ordering).
|
|
379
|
-
return {
|
|
380
|
-
...p,
|
|
381
|
-
config: typeof hook === "function" ? wrappedHandler : { ...hook, handler: wrappedHandler },
|
|
382
|
-
}
|
|
383
|
-
})
|
|
384
|
-
}
|
|
385
|
-
|
|
386
309
|
/**
|
|
387
310
|
* The npm package a bare module specifier belongs to (`@nifrajs/web-react/client` →
|
|
388
311
|
* `@nifrajs/web-react`, `some-adapter/client` → `some-adapter`), or `undefined` for a path-shaped
|
|
@@ -614,8 +537,7 @@ export async function createViteDevServer(options: ViteDevServerOptions): Promis
|
|
|
614
537
|
// server importing vite unguarded is precisely what poisoned the module for the whole process.
|
|
615
538
|
const viteModule = await importVite<ViteModule>()
|
|
616
539
|
const { createServer } = viteModule
|
|
617
|
-
// Under rolldown-vite (Vite 8+),
|
|
618
|
-
// plugins still emit - it triggers a noisy "Invalid key … jsx" warning but does nothing useful here.
|
|
540
|
+
// Under rolldown-vite (Vite 8+), framework plugins' stale `optimizeDeps.rollupOptions` warns on every start.
|
|
619
541
|
const plugins = normalizeRolldownPlugins(
|
|
620
542
|
options.plugins ?? [],
|
|
621
543
|
viteModule.rolldownVersion !== undefined,
|