@nuxt/nitro-server 4.3.0 → 4.4.2
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 +5 -5
- package/dist/THIRD-PARTY-LICENSES.md +11 -0
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +207 -81
- package/dist/runtime/h3-compat.d.mts +4 -0
- package/dist/runtime/h3-compat.mjs +4 -0
- package/dist/runtime/handlers/error.mjs +2 -2
- package/dist/runtime/handlers/island.d.mts +3 -2
- package/dist/runtime/handlers/island.mjs +21 -9
- package/dist/runtime/handlers/renderer.d.mts +3 -2
- package/dist/runtime/handlers/renderer.mjs +31 -32
- package/dist/runtime/middleware/no-ssr.d.mts +3 -2
- package/dist/runtime/middleware/no-ssr.mjs +2 -1
- package/dist/runtime/plugins/dev-server-logs.d.mts +2 -1
- package/dist/runtime/utils/cache.d.mts +11 -5
- package/dist/runtime/utils/cache.mjs +2 -2
- package/dist/runtime/utils/config.d.mts +1 -1
- package/dist/runtime/utils/dev.mjs +1 -1
- package/dist/runtime/utils/error.d.mts +1 -1
- package/dist/runtime/utils/error.mjs +1 -1
- package/dist/runtime/utils/renderer/app.mjs +1 -2
- package/dist/runtime/utils/renderer/build-files.d.mts +2 -4
- package/dist/runtime/utils/renderer/payload.mjs +23 -7
- package/package.json +38 -16
package/README.md
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
<a href="https://nuxt.com"><img width="830" height="213" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/banner.svg" alt="Nuxt banner"></a>
|
|
2
2
|
|
|
3
3
|
# Nuxt
|
|
4
4
|
|
|
5
5
|
<p>
|
|
6
|
-
<a href="https://
|
|
7
|
-
<a href="https://
|
|
6
|
+
<a href="https://npmx.dev/package/nuxt"><img src="https://npmx.dev/api/registry/badge/version/nuxt" alt="Version"></a>
|
|
7
|
+
<a href="https://npmx.dev/package/nuxt"><img src="https://npmx.dev/api/registry/badge/downloads/nuxt" alt="Downloads"></a>
|
|
8
8
|
<a href="https://github.com/nuxt/nuxt/blob/main/LICENSE"><img src="https://img.shields.io/github/license/nuxt/nuxt.svg?style=flat&colorA=18181B&colorB=28CF8D" alt="License"></a>
|
|
9
9
|
<a href="https://nuxt.com/modules"><img src="https://img.shields.io/badge/dynamic/json?url=https://nuxt.com/api/v1/modules&query=$.stats.modules&label=Modules&style=flat&colorA=18181B&colorB=28CF8D" alt="Modules"></a>
|
|
10
10
|
<a href="https://nuxt.com"><img src="https://img.shields.io/badge/Nuxt%20Docs-18181B?logo=nuxt" alt="Website"></a>
|
|
11
11
|
<a href="https://chat.nuxt.dev"><img src="https://img.shields.io/badge/Nuxt%20Discord-18181B?logo=discord" alt="Discord"></a>
|
|
12
|
-
<a href="https://securityscorecards.dev/"><img src="https://api.securityscorecards.dev/projects/github.com/nuxt/nuxt/badge" alt="Nuxt openssf scorecard score"></a>
|
|
12
|
+
<a href="https://securityscorecards.dev/viewer/?uri=github.com/nuxt/nuxt"><img src="https://api.securityscorecards.dev/projects/github.com/nuxt/nuxt/badge" alt="Nuxt openssf scorecard score"></a>
|
|
13
13
|
<a href="https://deepwiki.com/nuxt/nuxt"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
14
14
|
</p>
|
|
15
15
|
|
|
@@ -111,7 +111,7 @@ Follow the docs to [Set Up Your Local Development Environment](https://nuxt.com/
|
|
|
111
111
|
## <a name="follow-us">🔗 Follow Us</a>
|
|
112
112
|
|
|
113
113
|
<p valign="center">
|
|
114
|
-
<a href="https://go.nuxt.com/discord"><img width="
|
|
114
|
+
<a href="https://go.nuxt.com/discord"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/discord.svg" alt="Discord"></a> <a href="https://go.nuxt.com/x"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/twitter.svg" alt="Twitter"></a> <a href="https://go.nuxt.com/github"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/github.svg" alt="GitHub"></a> <a href="https://go.nuxt.com/bluesky"><img width="20" src="https://github.com/nuxt/nuxt/blob/main/.github/assets/bluesky.svg" alt="Bluesky"></a>
|
|
115
115
|
</p>
|
|
116
116
|
|
|
117
117
|
## <a name="license">⚖️ License</a>
|
package/dist/index.d.mts
CHANGED
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { performance } from "node:perf_hooks";
|
|
1
2
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
2
3
|
import { existsSync, promises, readFileSync } from "node:fs";
|
|
3
4
|
import { cpus } from "node:os";
|
|
@@ -15,14 +16,13 @@ import { addPlugin, addTemplate, addVitePlugin, createIsIgnored, findPath, getDi
|
|
|
15
16
|
import escapeRE from "escape-string-regexp";
|
|
16
17
|
import { defu } from "defu";
|
|
17
18
|
import { defineEventHandler, dynamicEventHandler, handleCors, setHeader } from "h3";
|
|
18
|
-
import {
|
|
19
|
+
import { addDependency } from "nypm";
|
|
20
|
+
import { hasTTY, isCI, isWindows } from "std-env";
|
|
19
21
|
import { ImpoundPlugin } from "impound";
|
|
20
22
|
import { resolveModulePath } from "exsolve";
|
|
21
23
|
import { runtimeDependencies } from "nitropack/runtime/meta";
|
|
22
|
-
|
|
23
24
|
//#region package.json
|
|
24
|
-
var version = "4.
|
|
25
|
-
|
|
25
|
+
var version = "4.4.2";
|
|
26
26
|
//#endregion
|
|
27
27
|
//#region src/utils.ts
|
|
28
28
|
function toArray(value) {
|
|
@@ -31,32 +31,63 @@ function toArray(value) {
|
|
|
31
31
|
let _distDir = dirname(fileURLToPath(import.meta.url));
|
|
32
32
|
if (/(?:chunks|shared)$/.test(_distDir)) _distDir = dirname(_distDir);
|
|
33
33
|
const distDir = _distDir;
|
|
34
|
-
|
|
35
34
|
//#endregion
|
|
36
35
|
//#region ../ui-templates/dist/templates/spa-loading-icon.ts
|
|
37
36
|
const template = () => {
|
|
38
37
|
return "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"80\" fill=\"none\" class=\"nuxt-spa-loading\" viewBox=\"0 0 37 25\"><path d=\"M24.236 22.006h10.742L25.563 5.822l-8.979 14.31a4 4 0 0 1-3.388 1.874H2.978l11.631-20 5.897 10.567\"/></svg><style>.nuxt-spa-loading{left:50%;position:fixed;top:50%;transform:translate(-50%,-50%)}.nuxt-spa-loading>path{animation:nuxt-spa-loading-move 3s linear infinite;fill:none;stroke:#00dc82;stroke-dasharray:128;stroke-dashoffset:128;stroke-linecap:round;stroke-linejoin:round;stroke-width:4px}@keyframes nuxt-spa-loading-move{to{stroke-dashoffset:-128}}</style>";
|
|
39
38
|
};
|
|
40
|
-
|
|
41
39
|
//#endregion
|
|
42
40
|
//#region ../nuxt/src/core/plugins/import-protection.ts
|
|
43
41
|
function createImportProtectionPatterns(nuxt, options) {
|
|
44
42
|
const patterns = [];
|
|
45
43
|
const context = contextFlags[options.context];
|
|
46
|
-
patterns.push([
|
|
47
|
-
|
|
44
|
+
patterns.push([
|
|
45
|
+
/^(nuxt|nuxt3|nuxt-nightly)$/,
|
|
46
|
+
`\`nuxt\` or \`nuxt-nightly\` cannot be imported directly in ${context}.`,
|
|
47
|
+
options.context === "nuxt-app" ? ["Import runtime Nuxt composables from `#app` or `#imports` instead."] : ["Use `#app` or `#imports` for runtime composables in your Vue app code."]
|
|
48
|
+
]);
|
|
49
|
+
patterns.push([
|
|
50
|
+
/^((~|~~|@|@@)?\/)?nuxt\.config(\.|$)/,
|
|
51
|
+
"Importing directly from a `nuxt.config` file is not allowed.",
|
|
52
|
+
[
|
|
53
|
+
"Use `useRuntimeConfig()` to access runtime config in your app.",
|
|
54
|
+
"Use `useAppConfig()` to access config that doesn't need to be changed at runtime.",
|
|
55
|
+
"Use a Nuxt module to access build-time configuration."
|
|
56
|
+
]
|
|
57
|
+
]);
|
|
48
58
|
patterns.push([/(^|node_modules\/)@vue\/composition-api/]);
|
|
49
|
-
for (const mod of nuxt.options._installedModules) if (mod.entryPath) patterns.push([
|
|
59
|
+
for (const mod of nuxt.options._installedModules) if (mod.entryPath) patterns.push([
|
|
60
|
+
new RegExp(`^${escapeRE(mod.entryPath)}$`),
|
|
61
|
+
"Importing directly from module entry-points is not allowed.",
|
|
62
|
+
["Import from the module's runtime directory instead (e.g. `my-module/runtime/...`)."]
|
|
63
|
+
]);
|
|
50
64
|
for (const i of [
|
|
51
65
|
/(^|node_modules\/)@nuxt\/(cli|kit|test-utils)/,
|
|
52
66
|
/(^|node_modules\/)nuxi/,
|
|
53
67
|
/(^|node_modules\/)nitro(?:pack)?(?:-nightly)?(?:$|\/)(?!(?:dist\/)?(?:node_modules|presets|runtime|types))/,
|
|
54
68
|
/(^|node_modules\/)nuxt\/(config|kit|schema)/
|
|
55
|
-
]) patterns.push([
|
|
56
|
-
|
|
69
|
+
]) patterns.push([
|
|
70
|
+
i,
|
|
71
|
+
`This module cannot be imported in ${context}.`,
|
|
72
|
+
["These are build-time only packages and cannot be used at runtime."]
|
|
73
|
+
]);
|
|
74
|
+
if (options.context === "nitro-app" || options.context === "shared") for (const i of ["#app", /^#build(\/|$)/]) patterns.push([
|
|
75
|
+
i,
|
|
76
|
+
`Vue app aliases are not allowed in ${context}.`,
|
|
77
|
+
["Move this code to your Vue app directory or use a shared utility."]
|
|
78
|
+
]);
|
|
57
79
|
if (options.context === "nuxt-app" || options.context === "shared") {
|
|
58
|
-
|
|
59
|
-
patterns.push([
|
|
80
|
+
const serverRelative = escapeRE(relative(nuxt.options.rootDir, resolve(nuxt.options.srcDir, nuxt.options.serverDir || "server")));
|
|
81
|
+
patterns.push([
|
|
82
|
+
new RegExp("^" + serverRelative + "\\/(api|routes|middleware|plugins)\\/"),
|
|
83
|
+
`Importing from server is not allowed in ${context}.`,
|
|
84
|
+
["Use `$fetch()` or `useFetch()` to fetch data from server routes.", "Move shared logic to the `shared/` directory."]
|
|
85
|
+
]);
|
|
86
|
+
patterns.push([
|
|
87
|
+
/^#server(\/|$)/,
|
|
88
|
+
`Server aliases are not allowed in ${context}.`,
|
|
89
|
+
["Use `$fetch()` or `useFetch()` to call server endpoints.", "Move shared logic to the `shared/` directory."]
|
|
90
|
+
]);
|
|
60
91
|
}
|
|
61
92
|
return patterns;
|
|
62
93
|
}
|
|
@@ -65,7 +96,6 @@ const contextFlags = {
|
|
|
65
96
|
"nuxt-app": "the Vue part of your app",
|
|
66
97
|
"shared": "the #shared directory"
|
|
67
98
|
};
|
|
68
|
-
|
|
69
99
|
//#endregion
|
|
70
100
|
//#region src/templates.ts
|
|
71
101
|
const nitroSchemaTemplate = {
|
|
@@ -77,12 +107,9 @@ const nitroSchemaTemplate = {
|
|
|
77
107
|
references,
|
|
78
108
|
declarations
|
|
79
109
|
});
|
|
80
|
-
const
|
|
110
|
+
const typesDir = join(nuxt.options.buildDir, "types");
|
|
81
111
|
return `
|
|
82
|
-
${[...references.map((ref) =>
|
|
83
|
-
if ("path" in ref && isAbsolute(ref.path)) ref.path = relative(sourceDir, ref.path);
|
|
84
|
-
return `/// <reference ${renderAttrs(ref)} />`;
|
|
85
|
-
}), ...declarations].join("\n")}
|
|
112
|
+
${[...references.map((ref) => renderReference(ref, typesDir)), ...declarations].join("\n")}
|
|
86
113
|
|
|
87
114
|
import type { RuntimeConfig } from 'nuxt/schema'
|
|
88
115
|
import type { H3Event } from 'h3'
|
|
@@ -144,15 +171,9 @@ declare module 'nitropack/types' {
|
|
|
144
171
|
`;
|
|
145
172
|
}
|
|
146
173
|
};
|
|
147
|
-
function
|
|
148
|
-
return
|
|
149
|
-
}
|
|
150
|
-
function renderAttrs(obj) {
|
|
151
|
-
const attrs = [];
|
|
152
|
-
for (const key in obj) attrs.push(renderAttr(key, obj[key]));
|
|
153
|
-
return attrs.join(" ");
|
|
174
|
+
function renderReference(ref, baseDir) {
|
|
175
|
+
return `/// <reference ${"path" in ref ? `path="${isAbsolute(ref.path) ? relative(baseDir, ref.path) : ref.path}"` : `types="${ref.types}"`} />`;
|
|
154
176
|
}
|
|
155
|
-
|
|
156
177
|
//#endregion
|
|
157
178
|
//#region src/index.ts
|
|
158
179
|
const logLevelMapReverse = {
|
|
@@ -171,7 +192,7 @@ async function bundle(nuxt) {
|
|
|
171
192
|
}
|
|
172
193
|
const layerPublicAssetsDirs = [];
|
|
173
194
|
for (const dirs of layerDirs) if (existsSync(dirs.public)) layerPublicAssetsDirs.push({ dir: dirs.public });
|
|
174
|
-
const excludePattern = excludePaths.length ? [
|
|
195
|
+
const excludePattern = excludePaths.length ? [new RegExp(`node_modules\\/(?!${excludePaths.join("|")})`)] : [/node_modules/];
|
|
175
196
|
const rootDirWithSlash = withTrailingSlash(nuxt.options.rootDir);
|
|
176
197
|
const moduleEntryPaths = [];
|
|
177
198
|
for (const m of nuxt.options._installedModules) {
|
|
@@ -294,7 +315,8 @@ async function bundle(nuxt) {
|
|
|
294
315
|
`export const NUXT_JSON_PAYLOADS = ${!!nuxt.options.experimental.renderJsonPayloads}`,
|
|
295
316
|
`export const NUXT_ASYNC_CONTEXT = ${!!nuxt.options.experimental.asyncContext}`,
|
|
296
317
|
`export const NUXT_SHARED_DATA = ${!!nuxt.options.experimental.sharedPrerenderData}`,
|
|
297
|
-
`export const NUXT_PAYLOAD_EXTRACTION = ${
|
|
318
|
+
`export const NUXT_PAYLOAD_EXTRACTION = ${nuxt.options.experimental.payloadExtraction !== false}`,
|
|
319
|
+
`export const NUXT_PAYLOAD_INLINE = ${nuxt.options.experimental.payloadExtraction !== true}`,
|
|
298
320
|
`export const NUXT_RUNTIME_PAYLOAD_EXTRACTION = ${hasCachedRoutes}`
|
|
299
321
|
].join("\n");
|
|
300
322
|
}
|
|
@@ -399,7 +421,8 @@ async function bundle(nuxt) {
|
|
|
399
421
|
"appLayout",
|
|
400
422
|
"cache",
|
|
401
423
|
"isr",
|
|
402
|
-
"swr"
|
|
424
|
+
"swr",
|
|
425
|
+
"ssr"
|
|
403
426
|
];
|
|
404
427
|
function getRouteRulesRouter() {
|
|
405
428
|
const routeRulesRouter = createRouter();
|
|
@@ -448,18 +471,19 @@ async function bundle(nuxt) {
|
|
|
448
471
|
}
|
|
449
472
|
});
|
|
450
473
|
if (nuxt.options.experimental.payloadExtraction) {
|
|
451
|
-
if (nuxt.options.dev) nuxt.hook("nitro:config", (nitroConfig
|
|
452
|
-
nitroConfig
|
|
453
|
-
nitroConfig
|
|
454
|
-
nitroConfig
|
|
455
|
-
for (const route of nitroConfig
|
|
474
|
+
if (nuxt.options.dev) nuxt.hook("nitro:config", (nitroConfig) => {
|
|
475
|
+
nitroConfig.prerender ||= {};
|
|
476
|
+
nitroConfig.prerender.routes ||= [];
|
|
477
|
+
nitroConfig.routeRules ||= {};
|
|
478
|
+
for (const route of nitroConfig.prerender.routes) {
|
|
456
479
|
if (!route) continue;
|
|
457
|
-
nitroConfig
|
|
480
|
+
nitroConfig.routeRules[route] = defu(nitroConfig.routeRules[route], { prerender: true });
|
|
458
481
|
}
|
|
459
482
|
});
|
|
460
|
-
nuxt.hook("nitro:init", (nitro
|
|
461
|
-
nitro
|
|
462
|
-
for (const [route, value] of Object.entries(nitro
|
|
483
|
+
nuxt.hook("nitro:init", (nitro) => {
|
|
484
|
+
nitro.hooks.hook("build:before", (nitro) => {
|
|
485
|
+
for (const [route, value] of Object.entries(nitro.options.routeRules)) if (!route.endsWith("*") && !route.endsWith("/_payload.json")) {
|
|
486
|
+
if (value.ssr === false) continue;
|
|
463
487
|
if (value.isr || value.cache || value.prerender && nuxt.options.dev) {
|
|
464
488
|
const payloadKey = route + "/_payload.json";
|
|
465
489
|
const defaults = {};
|
|
@@ -468,7 +492,7 @@ async function bundle(nuxt) {
|
|
|
468
492
|
"cache",
|
|
469
493
|
...nuxt.options.dev ? ["prerender"] : []
|
|
470
494
|
]) if (key in value) defaults[key] = value[key];
|
|
471
|
-
nitro
|
|
495
|
+
nitro.options.routeRules[payloadKey] = defu(nitro.options.routeRules[payloadKey], defaults);
|
|
472
496
|
}
|
|
473
497
|
}
|
|
474
498
|
});
|
|
@@ -500,13 +524,13 @@ async function bundle(nuxt) {
|
|
|
500
524
|
config.alias ||= {};
|
|
501
525
|
config.alias["#app-manifest"] = join(tempDir, `meta/${buildId}.json`);
|
|
502
526
|
});
|
|
503
|
-
nuxt.hook("nitro:init", (nitro
|
|
504
|
-
nitro
|
|
527
|
+
nuxt.hook("nitro:init", (nitro) => {
|
|
528
|
+
nitro.hooks.hook("rollup:before", async (nitro) => {
|
|
505
529
|
const prerenderedRoutes = /* @__PURE__ */ new Set();
|
|
506
530
|
const routeRulesMatcher = getRouteRulesRouter();
|
|
507
|
-
if (nitro
|
|
531
|
+
if (nitro._prerenderedRoutes?.length) {
|
|
508
532
|
const payloadSuffix = nuxt.options.experimental.renderJsonPayloads ? "/_payload.json" : "/_payload.js";
|
|
509
|
-
for (const route of nitro
|
|
533
|
+
for (const route of nitro._prerenderedRoutes) if (!route.error && route.route.endsWith(payloadSuffix)) {
|
|
510
534
|
const url = route.route.slice(0, -payloadSuffix.length) || "/";
|
|
511
535
|
if (!defu({}, ...findAllRoutes(routeRulesMatcher, void 0, url).reverse()).prerender) prerenderedRoutes.add(url);
|
|
512
536
|
}
|
|
@@ -535,21 +559,72 @@ async function bundle(nuxt) {
|
|
|
535
559
|
nitroConfig.virtual["#build/dist/server/styles.mjs"] = "export default {}";
|
|
536
560
|
if (process.platform === "win32") nitroConfig.virtual["#build/dist/server/styles.mjs".replace(FORWARD_SLASH_RE, "\\")] = "export default {}";
|
|
537
561
|
}
|
|
562
|
+
if (nuxt.options.experimental.decorators) {
|
|
563
|
+
const nitroDecoratorDeps = ["@rollup/plugin-babel", "@babel/plugin-proposal-decorators"];
|
|
564
|
+
let hasDeps = true;
|
|
565
|
+
for (const pkg of nitroDecoratorDeps) try {
|
|
566
|
+
await import(pkg);
|
|
567
|
+
} catch (_err) {
|
|
568
|
+
const err = _err;
|
|
569
|
+
if (err.code !== "ERR_MODULE_NOT_FOUND" && err.code !== "MODULE_NOT_FOUND") throw err;
|
|
570
|
+
if (!isCI && hasTTY) {
|
|
571
|
+
logger.info("Decorator support requires additional dependencies.");
|
|
572
|
+
if (await logger.prompt(`Install \`${nitroDecoratorDeps.join("` and `")}\`?`, {
|
|
573
|
+
type: "confirm",
|
|
574
|
+
initial: true
|
|
575
|
+
})) {
|
|
576
|
+
logger.start(`Installing ${nitroDecoratorDeps.map((d) => `\`${d}\``).join(" and ")}...`);
|
|
577
|
+
await addDependency(nitroDecoratorDeps, {
|
|
578
|
+
dev: true,
|
|
579
|
+
cwd: nuxt.options.rootDir,
|
|
580
|
+
silent: true
|
|
581
|
+
});
|
|
582
|
+
logger.info("Rerun Nuxt to enable decorator support.");
|
|
583
|
+
process.exit(1);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
logger.warn(`Cannot find \`${pkg}\`. Install \`${nitroDecoratorDeps.join("` and `")}\` to enable decorator support.`);
|
|
587
|
+
hasDeps = false;
|
|
588
|
+
break;
|
|
589
|
+
}
|
|
590
|
+
if (hasDeps) {
|
|
591
|
+
const { babel } = await import("@rollup/plugin-babel");
|
|
592
|
+
nitroConfig.rollupConfig.plugins = toArray(await nitroConfig.rollupConfig.plugins || []);
|
|
593
|
+
nitroConfig.rollupConfig.plugins.unshift(babel({
|
|
594
|
+
babelHelpers: "bundled",
|
|
595
|
+
configFile: false,
|
|
596
|
+
extensions: [
|
|
597
|
+
".ts",
|
|
598
|
+
".js",
|
|
599
|
+
".mjs",
|
|
600
|
+
".mts"
|
|
601
|
+
],
|
|
602
|
+
plugins: [["@babel/plugin-syntax-typescript", { isTSX: false }], ["@babel/plugin-proposal-decorators", { version: "2023-11" }]]
|
|
603
|
+
}), babel({
|
|
604
|
+
babelHelpers: "bundled",
|
|
605
|
+
configFile: false,
|
|
606
|
+
extensions: [".tsx", ".jsx"],
|
|
607
|
+
plugins: [["@babel/plugin-syntax-typescript", { isTSX: true }], ["@babel/plugin-proposal-decorators", { version: "2023-11" }]]
|
|
608
|
+
}));
|
|
609
|
+
}
|
|
610
|
+
}
|
|
538
611
|
nitroConfig.rollupConfig.plugins = await nitroConfig.rollupConfig.plugins || [];
|
|
539
612
|
nitroConfig.rollupConfig.plugins = toArray(nitroConfig.rollupConfig.plugins);
|
|
540
613
|
const sharedDir = withTrailingSlash(resolve(nuxt.options.rootDir, nuxt.options.dir.shared));
|
|
541
614
|
const relativeSharedDir = withTrailingSlash(relative(nuxt.options.rootDir, resolve(nuxt.options.rootDir, nuxt.options.dir.shared)));
|
|
542
615
|
const sharedPatterns = [
|
|
543
616
|
/^#shared\//,
|
|
544
|
-
|
|
545
|
-
|
|
617
|
+
new RegExp("^" + escapeRE(sharedDir)),
|
|
618
|
+
new RegExp("^" + escapeRE(relativeSharedDir))
|
|
546
619
|
];
|
|
547
620
|
nitroConfig.rollupConfig.plugins.push(ImpoundPlugin.rollup({
|
|
548
621
|
cwd: nuxt.options.rootDir,
|
|
622
|
+
trace: true,
|
|
549
623
|
include: sharedPatterns,
|
|
550
624
|
patterns: createImportProtectionPatterns(nuxt, { context: "shared" })
|
|
551
625
|
}), ImpoundPlugin.rollup({
|
|
552
626
|
cwd: nuxt.options.rootDir,
|
|
627
|
+
trace: true,
|
|
553
628
|
patterns: createImportProtectionPatterns(nuxt, { context: "nitro-app" }),
|
|
554
629
|
exclude: [/node_modules[\\/]nitro(?:pack)?(?:-nightly)?[\\/]|(packages|@nuxt)[\\/]nitro-server(?:-nightly)?[\\/](src|dist)[\\/]runtime[\\/]/, ...sharedPatterns]
|
|
555
630
|
}));
|
|
@@ -600,10 +675,12 @@ async function bundle(nuxt) {
|
|
|
600
675
|
tsConfig.compilerOptions.paths[alias] = [absolutePath];
|
|
601
676
|
if (isDirectory) tsConfig.compilerOptions.paths[`${alias}/*`] = [`${absolutePath}/*`];
|
|
602
677
|
}
|
|
678
|
+
nuxt._perf?.startPhase("nitro:createNitro");
|
|
603
679
|
const nitro = await createNitro(nitroConfig, {
|
|
604
680
|
compatibilityDate: nuxt.options.compatibilityDate,
|
|
605
681
|
dotenv: nuxt.options._loadOptions?.dotenv
|
|
606
682
|
});
|
|
683
|
+
nuxt._perf?.endPhase("nitro:createNitro");
|
|
607
684
|
if (nuxt.options.experimental.serverAppConfig === false && nitro.options.imports) {
|
|
608
685
|
nitro.options.imports.presets ||= [];
|
|
609
686
|
nitro.options.imports.presets = nitro.options.imports.presets.map((preset) => typeof preset === "string" || !("imports" in preset) ? preset : {
|
|
@@ -611,10 +688,7 @@ async function bundle(nuxt) {
|
|
|
611
688
|
imports: preset.imports.filter((i) => i !== "useAppConfig")
|
|
612
689
|
});
|
|
613
690
|
}
|
|
614
|
-
if (nitro.options.static && nuxt.options.experimental.payloadExtraction ===
|
|
615
|
-
logger.warn("Using experimental payload extraction for full-static output. You can opt-out by setting `experimental.payloadExtraction` to `false`.");
|
|
616
|
-
nuxt.options.experimental.payloadExtraction = true;
|
|
617
|
-
}
|
|
691
|
+
if (nitro.options.static && nuxt.options.experimental.payloadExtraction === false) logger.warn("Payload extraction is recommended for full-static output. You can enable it by setting `experimental.payloadExtraction` to `true` or `'client'`.");
|
|
618
692
|
const spaLoadingTemplateFilePath = await spaLoadingTemplatePath(nuxt);
|
|
619
693
|
nuxt.hook("builder:watch", async (_event, relativePath) => {
|
|
620
694
|
if (resolve(nuxt.options.srcDir, relativePath) === spaLoadingTemplateFilePath) await nitro.hooks.callHook("rollup:reload");
|
|
@@ -631,8 +705,52 @@ async function bundle(nuxt) {
|
|
|
631
705
|
} });
|
|
632
706
|
nuxt._nitro = nitro;
|
|
633
707
|
await nuxt.callHook("nitro:init", nitro);
|
|
708
|
+
if (nuxt._perf) nitro.hooks.hook("rollup:before", (_nitro, rollupConfig) => {
|
|
709
|
+
const plugins = rollupConfig.plugins || [];
|
|
710
|
+
for (const plugin of plugins) {
|
|
711
|
+
if (!plugin || !plugin.name) continue;
|
|
712
|
+
const pluginName = `nitro:${plugin.name}`;
|
|
713
|
+
for (const hookName of [
|
|
714
|
+
"transform",
|
|
715
|
+
"resolveId",
|
|
716
|
+
"load"
|
|
717
|
+
]) {
|
|
718
|
+
const original = plugin[hookName];
|
|
719
|
+
if (typeof original !== "function") continue;
|
|
720
|
+
plugin[hookName] = function(...args) {
|
|
721
|
+
const start = performance.now();
|
|
722
|
+
const record = () => nuxt._perf?.recordBundlerPluginHook(pluginName, hookName, performance.now() - start, start);
|
|
723
|
+
try {
|
|
724
|
+
const result = original.apply(this, args);
|
|
725
|
+
if (result && typeof result === "object" && "then" in result) return result.finally(record);
|
|
726
|
+
record();
|
|
727
|
+
return result;
|
|
728
|
+
} catch (err) {
|
|
729
|
+
record();
|
|
730
|
+
throw err;
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
});
|
|
634
736
|
nuxt["~runtimeDependencies"] ||= [];
|
|
635
737
|
nuxt["~runtimeDependencies"].push(...runtimeDependencies, "unhead", "@unhead/vue", "@nuxt/devalue", "unstorage", ...nitro.options.inlineDynamicImports ? ["vue", "@vue/server-renderer"] : []);
|
|
738
|
+
addVitePlugin({
|
|
739
|
+
name: "nuxt:nitro:ssr-conditions",
|
|
740
|
+
configEnvironment(name, config) {
|
|
741
|
+
if (name === "ssr") {
|
|
742
|
+
config.resolve ||= {};
|
|
743
|
+
config.resolve.conditions = [...nitro.options.exportConditions || [], "import"];
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
addVitePlugin({
|
|
748
|
+
name: "nuxt:nitro:vue-feature-flags",
|
|
749
|
+
applyToEnvironment: (environment) => environment.name === "ssr" && environment.config.isProduction,
|
|
750
|
+
configResolved(config) {
|
|
751
|
+
for (const key in config.define) if (key.startsWith("__VUE")) nitro.options.replace[key] = config.define[key];
|
|
752
|
+
}
|
|
753
|
+
});
|
|
636
754
|
nitro.vfs = nuxt.vfs = nitro.vfs || nuxt.vfs || {};
|
|
637
755
|
nuxt.hook("close", () => nitro.hooks.callHook("close"));
|
|
638
756
|
nitro.hooks.hook("prerender:routes", (routes) => {
|
|
@@ -666,12 +784,12 @@ async function bundle(nuxt) {
|
|
|
666
784
|
handler: resolve(distDir, "runtime/handlers/renderer")
|
|
667
785
|
});
|
|
668
786
|
if (nuxt.options.experimental.chromeDevtoolsProjectSettings) {
|
|
669
|
-
const cacheDir
|
|
670
|
-
let projectConfiguration = await readFile(join(cacheDir
|
|
787
|
+
const cacheDir = resolve(nuxt.options.rootDir, "node_modules/.cache/nuxt");
|
|
788
|
+
let projectConfiguration = await readFile(join(cacheDir, "chrome-workspace.json"), "utf-8").then((r) => JSON.parse(r)).catch(() => null);
|
|
671
789
|
if (!projectConfiguration) {
|
|
672
790
|
projectConfiguration = { uuid: randomUUID() };
|
|
673
|
-
await mkdir(cacheDir
|
|
674
|
-
await writeFile(join(cacheDir
|
|
791
|
+
await mkdir(cacheDir, { recursive: true });
|
|
792
|
+
await writeFile(join(cacheDir, "chrome-workspace.json"), JSON.stringify(projectConfiguration), "utf-8");
|
|
675
793
|
}
|
|
676
794
|
nitro.options.devHandlers.push({
|
|
677
795
|
route: "/.well-known/appspecific/com.chrome.devtools.json",
|
|
@@ -681,14 +799,14 @@ async function bundle(nuxt) {
|
|
|
681
799
|
} }))
|
|
682
800
|
});
|
|
683
801
|
}
|
|
684
|
-
if (!nuxt.options.dev && nuxt.options.experimental.noVueServer) nitro.hooks.hook("rollup:before", (nitro
|
|
685
|
-
if (nitro
|
|
686
|
-
nitro
|
|
802
|
+
if (!nuxt.options.dev && nuxt.options.experimental.noVueServer) nitro.hooks.hook("rollup:before", (nitro) => {
|
|
803
|
+
if (nitro.options.preset === "nitro-prerender") {
|
|
804
|
+
nitro.options.errorHandler = resolve(distDir, "runtime/handlers/error");
|
|
687
805
|
return;
|
|
688
806
|
}
|
|
689
|
-
const nuxtErrorHandler = nitro
|
|
690
|
-
if (nuxtErrorHandler >= 0) nitro
|
|
691
|
-
nitro
|
|
807
|
+
const nuxtErrorHandler = nitro.options.handlers.findIndex((h) => h.route === "/__nuxt_error");
|
|
808
|
+
if (nuxtErrorHandler >= 0) nitro.options.handlers.splice(nuxtErrorHandler, 1);
|
|
809
|
+
nitro.options.renderer = void 0;
|
|
692
810
|
});
|
|
693
811
|
nitro.hooks.hook("types:extend", (types) => {
|
|
694
812
|
types.tsConfig ||= {};
|
|
@@ -716,26 +834,17 @@ async function bundle(nuxt) {
|
|
|
716
834
|
for (const route of ["/200.html", "/404.html"]) routes.add(route);
|
|
717
835
|
if (!nuxt.options.ssr) routes.add("/index.html");
|
|
718
836
|
});
|
|
719
|
-
if (!nuxt.options.dev) nitro.hooks.hook("rollup:before", async (nitro
|
|
720
|
-
await copyPublicAssets(nitro
|
|
721
|
-
await nuxt.callHook("nitro:build:public-assets", nitro
|
|
837
|
+
if (!nuxt.options.dev) nitro.hooks.hook("rollup:before", async (nitro) => {
|
|
838
|
+
await copyPublicAssets(nitro);
|
|
839
|
+
await nuxt.callHook("nitro:build:public-assets", nitro);
|
|
722
840
|
});
|
|
723
841
|
async function symlinkDist() {
|
|
724
842
|
if (nitro.options.static) {
|
|
725
|
-
const distDir
|
|
726
|
-
if (!existsSync(distDir
|
|
843
|
+
const distDir = resolve(nuxt.options.rootDir, "dist");
|
|
844
|
+
if (!existsSync(distDir)) await promises.symlink(nitro.options.output.publicDir, distDir, "junction").catch(() => {});
|
|
727
845
|
}
|
|
728
846
|
}
|
|
729
|
-
|
|
730
|
-
await nuxt.callHook("nitro:build:before", nitro);
|
|
731
|
-
await prepare(nitro);
|
|
732
|
-
if (nuxt.options.dev) return build(nitro);
|
|
733
|
-
await prerender(nitro);
|
|
734
|
-
logger.restoreAll();
|
|
735
|
-
await build(nitro);
|
|
736
|
-
logger.wrapAll();
|
|
737
|
-
await symlinkDist();
|
|
738
|
-
});
|
|
847
|
+
let waitUntilCompile;
|
|
739
848
|
if (nuxt.options.dev) {
|
|
740
849
|
for (const builder of ["webpack", "rspack"]) {
|
|
741
850
|
nuxt.hook(`${builder}:compile`, ({ name, compiler }) => {
|
|
@@ -761,9 +870,27 @@ async function bundle(nuxt) {
|
|
|
761
870
|
}));
|
|
762
871
|
});
|
|
763
872
|
nuxt.server = createDevServer(nitro);
|
|
764
|
-
|
|
765
|
-
nuxt.hook("build:done", () => waitUntilCompile);
|
|
873
|
+
waitUntilCompile = new Promise((resolve) => nitro.hooks.hook("compiled", () => resolve()));
|
|
766
874
|
}
|
|
875
|
+
nuxt.hook("build:done", async () => {
|
|
876
|
+
nuxt._perf?.startPhase("nitro:build");
|
|
877
|
+
try {
|
|
878
|
+
await nuxt.callHook("nitro:build:before", nitro);
|
|
879
|
+
await prepare(nitro);
|
|
880
|
+
if (nuxt.options.dev) {
|
|
881
|
+
await build(nitro);
|
|
882
|
+
await waitUntilCompile;
|
|
883
|
+
return;
|
|
884
|
+
}
|
|
885
|
+
await prerender(nitro);
|
|
886
|
+
logger.restoreAll();
|
|
887
|
+
await build(nitro);
|
|
888
|
+
logger.wrapAll();
|
|
889
|
+
await symlinkDist();
|
|
890
|
+
} finally {
|
|
891
|
+
nuxt._perf?.endPhase("nitro:build");
|
|
892
|
+
}
|
|
893
|
+
});
|
|
767
894
|
}
|
|
768
895
|
const RELATIVE_RE = /^([^.])/;
|
|
769
896
|
function relativeWithDot(from, to) {
|
|
@@ -775,14 +902,13 @@ async function spaLoadingTemplatePath(nuxt) {
|
|
|
775
902
|
}
|
|
776
903
|
async function spaLoadingTemplate(nuxt) {
|
|
777
904
|
if (nuxt.options.spaLoadingTemplate === false) return "";
|
|
778
|
-
const spaLoadingTemplate
|
|
905
|
+
const spaLoadingTemplate = await spaLoadingTemplatePath(nuxt);
|
|
779
906
|
try {
|
|
780
|
-
if (existsSync(spaLoadingTemplate
|
|
907
|
+
if (existsSync(spaLoadingTemplate)) return readFileSync(spaLoadingTemplate, "utf-8").trim();
|
|
781
908
|
} catch {}
|
|
782
909
|
if (nuxt.options.spaLoadingTemplate === true) return template();
|
|
783
910
|
if (nuxt.options.spaLoadingTemplate) logger.warn(`Could not load custom \`spaLoadingTemplate\` path as it does not exist: \`${nuxt.options.spaLoadingTemplate}\`.`);
|
|
784
911
|
return "";
|
|
785
912
|
}
|
|
786
|
-
|
|
787
913
|
//#endregion
|
|
788
|
-
export { bundle };
|
|
914
|
+
export { bundle };
|
|
@@ -25,8 +25,8 @@ export default (async function errorhandler(error, event, { defaultHandler }) {
|
|
|
25
25
|
// remove proto/hostname/port from URL
|
|
26
26
|
const url = new URL(errorObject.url);
|
|
27
27
|
errorObject.url = withoutBase(url.pathname, useRuntimeConfig(event).app.baseURL) + url.search + url.hash;
|
|
28
|
-
// add default server message
|
|
29
|
-
errorObject.message
|
|
28
|
+
// add default server message (keep sanitized for unhandled errors)
|
|
29
|
+
errorObject.message = error.unhandled ? errorObject.message || "Server Error" : error.message || errorObject.message || "Server Error";
|
|
30
30
|
// we will be rendering this error internally so we can pass along the error.data safely
|
|
31
31
|
errorObject.data ||= error.data;
|
|
32
32
|
errorObject.statusText ||= error.statusText || error.statusMessage;
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { EventHandler } from "h3";
|
|
2
|
+
declare const handler: EventHandler;
|
|
3
|
+
export default handler;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useNitroApp } from "nitropack/runtime";
|
|
2
2
|
import { destr } from "destr";
|
|
3
|
-
import { defineEventHandler, getQuery, readBody, setResponseHeaders } from "h3";
|
|
3
|
+
import { createError, defineEventHandler, getQuery, readBody, setResponseHeaders } from "h3";
|
|
4
4
|
import { resolveUnrefHeadInput } from "@unhead/vue";
|
|
5
5
|
import { getRequestDependencies } from "vue-bundle-renderer/runtime";
|
|
6
6
|
import { getQuery as getURLQuery } from "ufo";
|
|
@@ -10,7 +10,7 @@ import { getSSRRenderer } from "../utils/renderer/build-files.mjs";
|
|
|
10
10
|
import { renderInlineStyles } from "../utils/renderer/inline-styles.mjs";
|
|
11
11
|
import { getClientIslandResponse, getServerComponentHTML, getSlotIslandResponse } from "../utils/renderer/islands.mjs";
|
|
12
12
|
const ISLAND_SUFFIX_RE = /\.json(?:\?.*)?$/;
|
|
13
|
-
|
|
13
|
+
const handler = defineEventHandler(async (event) => {
|
|
14
14
|
const nitroApp = useNitroApp();
|
|
15
15
|
setResponseHeaders(event, {
|
|
16
16
|
"content-type": "application/json;charset=utf-8",
|
|
@@ -95,26 +95,38 @@ export default defineEventHandler(async (event) => {
|
|
|
95
95
|
}
|
|
96
96
|
return islandResponse;
|
|
97
97
|
});
|
|
98
|
+
export default handler;
|
|
99
|
+
const ISLAND_PATH_PREFIX = "/__nuxt_island/";
|
|
100
|
+
const VALID_COMPONENT_NAME_RE = /^[a-z][\w.-]*$/i;
|
|
98
101
|
async function getIslandContext(event) {
|
|
99
|
-
// TODO: Strict validation for url
|
|
100
102
|
let url = event.path || "";
|
|
101
103
|
if (import.meta.prerender && event.path && await islandPropCache.hasItem(event.path)) {
|
|
102
104
|
// rehydrate props from cache so we can rerender island if cache does not have it any more
|
|
103
105
|
url = await islandPropCache.getItem(event.path);
|
|
104
106
|
}
|
|
105
|
-
|
|
107
|
+
if (!url.startsWith(ISLAND_PATH_PREFIX)) {
|
|
108
|
+
throw createError({
|
|
109
|
+
statusCode: 400,
|
|
110
|
+
statusMessage: "Invalid island request path"
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
const componentParts = url.substring(ISLAND_PATH_PREFIX.length).replace(ISLAND_SUFFIX_RE, "").split("_");
|
|
106
114
|
const hashId = componentParts.length > 1 ? componentParts.pop() : undefined;
|
|
107
115
|
const componentName = componentParts.join("_");
|
|
108
|
-
|
|
116
|
+
if (!componentName || !VALID_COMPONENT_NAME_RE.test(componentName)) {
|
|
117
|
+
throw createError({
|
|
118
|
+
statusCode: 400,
|
|
119
|
+
statusMessage: "Invalid island component name"
|
|
120
|
+
});
|
|
121
|
+
}
|
|
109
122
|
const context = event.method === "GET" ? getQuery(event) : await readBody(event);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
123
|
+
// Only extract known context fields to prevent arbitrary data injection
|
|
124
|
+
return {
|
|
125
|
+
url: typeof context?.url === "string" ? context.url : "/",
|
|
113
126
|
id: hashId,
|
|
114
127
|
name: componentName,
|
|
115
128
|
props: destr(context.props) || {},
|
|
116
129
|
slots: {},
|
|
117
130
|
components: {}
|
|
118
131
|
};
|
|
119
|
-
return ctx;
|
|
120
132
|
}
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { EventHandler } from "h3";
|
|
2
|
+
declare const handler: EventHandler;
|
|
3
|
+
export default handler;
|
|
@@ -14,9 +14,9 @@ import { replaceIslandTeleports } from "../utils/renderer/islands.mjs";
|
|
|
14
14
|
// @ts-expect-error virtual file
|
|
15
15
|
import { renderSSRHeadOptions } from "#internal/unhead.config.mjs";
|
|
16
16
|
// @ts-expect-error virtual file
|
|
17
|
-
import { NUXT_ASYNC_CONTEXT, NUXT_EARLY_HINTS, NUXT_INLINE_STYLES, NUXT_JSON_PAYLOADS, NUXT_NO_SCRIPTS, NUXT_PAYLOAD_EXTRACTION, NUXT_RUNTIME_PAYLOAD_EXTRACTION, PARSE_ERROR_DATA } from "#internal/nuxt/nitro-config.mjs";
|
|
17
|
+
import { NUXT_ASYNC_CONTEXT, NUXT_EARLY_HINTS, NUXT_INLINE_STYLES, NUXT_JSON_PAYLOADS, NUXT_NO_SCRIPTS, NUXT_PAYLOAD_EXTRACTION, NUXT_PAYLOAD_INLINE, NUXT_RUNTIME_PAYLOAD_EXTRACTION, PARSE_ERROR_DATA } from "#internal/nuxt/nitro-config.mjs";
|
|
18
18
|
// @ts-expect-error virtual file
|
|
19
|
-
import { appHead, appTeleportAttrs, appTeleportTag, componentIslands
|
|
19
|
+
import { appHead, appTeleportAttrs, appTeleportTag, componentIslands } from "#internal/nuxt.config.mjs";
|
|
20
20
|
// @ts-expect-error virtual file
|
|
21
21
|
import entryIds from "#internal/nuxt/entry-ids.mjs";
|
|
22
22
|
// @ts-expect-error virtual file
|
|
@@ -38,7 +38,7 @@ const APP_TELEPORT_CLOSE_TAG = HAS_APP_TELEPORTS ? `</${appTeleportTag}>` : "";
|
|
|
38
38
|
const PAYLOAD_URL_RE = NUXT_JSON_PAYLOADS ? /^[^?]*\/_payload.json(?:\?.*)?$/ : /^[^?]*\/_payload.js(?:\?.*)?$/;
|
|
39
39
|
const PAYLOAD_FILENAME = NUXT_JSON_PAYLOADS ? "_payload.json" : "_payload.js";
|
|
40
40
|
let entryPath;
|
|
41
|
-
|
|
41
|
+
const handler = defineRenderHandler(async (event) => {
|
|
42
42
|
const nitroApp = useNitroApp();
|
|
43
43
|
// Whether we're rendering an error page
|
|
44
44
|
const ssrError = event.path.startsWith("/__nuxt_error") ? getQuery(event) : null;
|
|
@@ -72,12 +72,16 @@ export default defineRenderHandler(async (event) => {
|
|
|
72
72
|
const routeOptions = getRouteRules(event);
|
|
73
73
|
// Whether we are prerendering route or using ISR/SWR caching
|
|
74
74
|
const _PAYLOAD_EXTRACTION = !ssrContext.noSSR && (import.meta.prerender && NUXT_PAYLOAD_EXTRACTION || NUXT_RUNTIME_PAYLOAD_EXTRACTION && (routeOptions.isr || routeOptions.cache));
|
|
75
|
+
// When NUXT_PAYLOAD_INLINE is true (payloadExtraction: 'client'), we inline the full payload
|
|
76
|
+
// in the HTML to avoid a separate _payload.json fetch on initial load (which would trigger a
|
|
77
|
+
// second render or lambda invocation). The _payload.json endpoint still works for client-side nav.
|
|
78
|
+
const _PAYLOAD_INLINE = !_PAYLOAD_EXTRACTION || NUXT_PAYLOAD_INLINE;
|
|
75
79
|
const isRenderingPayload = (_PAYLOAD_EXTRACTION || import.meta.dev && routeOptions.prerender) && PAYLOAD_URL_RE.test(ssrContext.url);
|
|
76
80
|
if (isRenderingPayload) {
|
|
77
81
|
const url = ssrContext.url.substring(0, ssrContext.url.lastIndexOf("/")) || "/";
|
|
78
82
|
ssrContext.url = url;
|
|
79
83
|
event._path = event.node.req.url = url;
|
|
80
|
-
if (
|
|
84
|
+
if (payloadCache && await payloadCache.hasItem(url)) {
|
|
81
85
|
return payloadCache.getItem(url);
|
|
82
86
|
}
|
|
83
87
|
}
|
|
@@ -128,16 +132,21 @@ export default defineRenderHandler(async (event) => {
|
|
|
128
132
|
// Directly render payload routes
|
|
129
133
|
if (isRenderingPayload) {
|
|
130
134
|
const response = renderPayloadResponse(ssrContext);
|
|
131
|
-
if (
|
|
135
|
+
if (payloadCache) {
|
|
132
136
|
await payloadCache.setItem(ssrContext.url, response);
|
|
133
137
|
}
|
|
134
138
|
return response;
|
|
135
139
|
}
|
|
136
|
-
if (_PAYLOAD_EXTRACTION
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
140
|
+
if (_PAYLOAD_EXTRACTION) {
|
|
141
|
+
if (import.meta.prerender) {
|
|
142
|
+
// Hint nitro to prerender payload for this route
|
|
143
|
+
appendResponseHeader(event, "x-nitro-prerender", joinURL(ssrContext.url.replace(/\?.*$/, ""), PAYLOAD_FILENAME));
|
|
144
|
+
}
|
|
145
|
+
// Cache payload from the current SSR context so _payload.json requests can be served
|
|
146
|
+
// without a full re-render (during prerender via LRU+FS, at runtime via in-memory TTL cache)
|
|
147
|
+
if (payloadCache) {
|
|
148
|
+
await payloadCache.setItem(ssrContext.url === "/" ? "/" : ssrContext.url.replace(/\/$/, ""), renderPayloadResponse(ssrContext));
|
|
149
|
+
}
|
|
141
150
|
}
|
|
142
151
|
const NO_SCRIPTS = NUXT_NO_SCRIPTS || routeOptions.noScripts;
|
|
143
152
|
// Setup head
|
|
@@ -167,7 +176,8 @@ export default defineRenderHandler(async (event) => {
|
|
|
167
176
|
}] }, headEntryOptions);
|
|
168
177
|
}
|
|
169
178
|
// 1. Preload payloads and app manifest
|
|
170
|
-
|
|
179
|
+
// Skip preload when inlining full payload in HTML (no separate fetch needed for initial load)
|
|
180
|
+
if (_PAYLOAD_EXTRACTION && !_PAYLOAD_INLINE && !NO_SCRIPTS) {
|
|
171
181
|
ssrContext.head.push({ link: [NUXT_JSON_PAYLOADS ? {
|
|
172
182
|
rel: "preload",
|
|
173
183
|
as: "fetch",
|
|
@@ -179,18 +189,6 @@ export default defineRenderHandler(async (event) => {
|
|
|
179
189
|
href: payloadURL
|
|
180
190
|
}] }, headEntryOptions);
|
|
181
191
|
}
|
|
182
|
-
if (isAppManifestEnabled && ssrContext["~preloadManifest"] && !NO_SCRIPTS) {
|
|
183
|
-
ssrContext.head.push({ link: [{
|
|
184
|
-
rel: "preload",
|
|
185
|
-
as: "fetch",
|
|
186
|
-
fetchpriority: "low",
|
|
187
|
-
crossorigin: "anonymous",
|
|
188
|
-
href: buildAssetsURL(`builds/meta/${ssrContext.runtimeConfig.app.buildId}.json`)
|
|
189
|
-
}] }, {
|
|
190
|
-
...headEntryOptions,
|
|
191
|
-
tagPriority: "low"
|
|
192
|
-
});
|
|
193
|
-
}
|
|
194
192
|
// 2. Styles
|
|
195
193
|
if (inlinedStyles.length) {
|
|
196
194
|
ssrContext.head.push({ style: inlinedStyles });
|
|
@@ -225,7 +223,14 @@ export default defineRenderHandler(async (event) => {
|
|
|
225
223
|
ssrContext.head.push({ link: getPreloadLinks(ssrContext, renderer.rendererContext) }, headEntryOptions);
|
|
226
224
|
ssrContext.head.push({ link: getPrefetchLinks(ssrContext, renderer.rendererContext) }, headEntryOptions);
|
|
227
225
|
// 5. Payloads
|
|
228
|
-
ssrContext.head.push({ script:
|
|
226
|
+
ssrContext.head.push({ script: _PAYLOAD_INLINE ? NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
|
|
227
|
+
ssrContext,
|
|
228
|
+
data: ssrContext.payload
|
|
229
|
+
}) : renderPayloadScript({
|
|
230
|
+
ssrContext,
|
|
231
|
+
data: ssrContext.payload,
|
|
232
|
+
routeOptions
|
|
233
|
+
}) : NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
|
|
229
234
|
ssrContext,
|
|
230
235
|
data: splitPayload(ssrContext).initial,
|
|
231
236
|
src: payloadURL
|
|
@@ -234,13 +239,6 @@ export default defineRenderHandler(async (event) => {
|
|
|
234
239
|
data: splitPayload(ssrContext).initial,
|
|
235
240
|
routeOptions,
|
|
236
241
|
src: payloadURL
|
|
237
|
-
}) : NUXT_JSON_PAYLOADS ? renderPayloadJsonScript({
|
|
238
|
-
ssrContext,
|
|
239
|
-
data: ssrContext.payload
|
|
240
|
-
}) : renderPayloadScript({
|
|
241
|
-
ssrContext,
|
|
242
|
-
data: ssrContext.payload,
|
|
243
|
-
routeOptions
|
|
244
242
|
}) }, {
|
|
245
243
|
...headEntryOptions,
|
|
246
244
|
tagPosition: "bodyClose",
|
|
@@ -249,7 +247,7 @@ export default defineRenderHandler(async (event) => {
|
|
|
249
247
|
}
|
|
250
248
|
// 6. Scripts
|
|
251
249
|
if (!routeOptions.noScripts) {
|
|
252
|
-
const tagPosition = _PAYLOAD_EXTRACTION && !NUXT_JSON_PAYLOADS ? "bodyClose" : "head";
|
|
250
|
+
const tagPosition = _PAYLOAD_EXTRACTION && !_PAYLOAD_INLINE && !NUXT_JSON_PAYLOADS ? "bodyClose" : "head";
|
|
253
251
|
ssrContext.head.push({ script: Object.values(scripts).map((resource) => ({
|
|
254
252
|
type: resource.module ? "module" : null,
|
|
255
253
|
src: renderer.rendererContext.buildAssetsURL(resource.file),
|
|
@@ -281,6 +279,7 @@ export default defineRenderHandler(async (event) => {
|
|
|
281
279
|
}
|
|
282
280
|
};
|
|
283
281
|
});
|
|
282
|
+
export default handler;
|
|
284
283
|
function normalizeChunks(chunks) {
|
|
285
284
|
const result = [];
|
|
286
285
|
for (const _chunk of chunks) {
|
|
@@ -1,2 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import type { EventHandler } from "h3";
|
|
2
|
+
declare const handler: EventHandler;
|
|
3
|
+
export default handler;
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { defineEventHandler, getRequestHeader } from "h3";
|
|
2
|
-
|
|
2
|
+
const handler = defineEventHandler((event) => {
|
|
3
3
|
if (getRequestHeader(event, "x-nuxt-no-ssr")) {
|
|
4
4
|
event.context.nuxt ||= {};
|
|
5
5
|
event.context.nuxt.noSSR = true;
|
|
6
6
|
}
|
|
7
7
|
});
|
|
8
|
+
export default handler;
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const
|
|
3
|
-
export declare const
|
|
4
|
-
export declare const
|
|
5
|
-
export declare const
|
|
1
|
+
import type { Storage } from "unstorage";
|
|
2
|
+
export declare const payloadCache: Storage | null;
|
|
3
|
+
export declare const islandCache: Storage | null;
|
|
4
|
+
export declare const islandPropCache: Storage | null;
|
|
5
|
+
export declare const sharedPrerenderPromises: Map<string, Promise<any>> | null;
|
|
6
|
+
interface SharedPrerenderCache {
|
|
7
|
+
get<T = unknown>(key: string): Promise<T> | undefined;
|
|
8
|
+
set<T>(key: string, value: Promise<T>): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export declare const sharedPrerenderCache: SharedPrerenderCache | null;
|
|
11
|
+
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useStorage } from "nitropack/runtime";
|
|
2
2
|
// @ts-expect-error virtual file
|
|
3
|
-
import { NUXT_SHARED_DATA } from "#internal/nuxt/nitro-config.mjs";
|
|
4
|
-
export const payloadCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:payload") : null;
|
|
3
|
+
import { NUXT_RUNTIME_PAYLOAD_EXTRACTION, NUXT_SHARED_DATA } from "#internal/nuxt/nitro-config.mjs";
|
|
4
|
+
export const payloadCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:payload") : NUXT_RUNTIME_PAYLOAD_EXTRACTION ? useStorage("cache:nuxt:payload") : null;
|
|
5
5
|
export const islandCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:island") : null;
|
|
6
6
|
export const islandPropCache = import.meta.prerender ? useStorage("internal:nuxt:prerender:island-props") : null;
|
|
7
7
|
export const sharedPrerenderPromises = import.meta.prerender && NUXT_SHARED_DATA ? new Map() : null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const defineAppConfig:
|
|
1
|
+
export declare const defineAppConfig: (config: any) => any;
|
|
@@ -357,7 +357,7 @@ function webComponentScript(base64HTML, startMinimized) {
|
|
|
357
357
|
iframe.id = 'frame';
|
|
358
358
|
iframe.src = 'data:text/html;base64,${base64HTML}';
|
|
359
359
|
iframe.title = 'Detailed error stack trace';
|
|
360
|
-
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin');
|
|
360
|
+
iframe.setAttribute('sandbox', 'allow-scripts allow-same-origin allow-top-navigation-by-user-activation');
|
|
361
361
|
|
|
362
362
|
const preview = el('div');
|
|
363
363
|
preview.id = 'preview';
|
|
@@ -3,4 +3,4 @@ import type { H3Event } from "h3";
|
|
|
3
3
|
* Nitro internal functions extracted from https://github.com/nitrojs/nitro/blob/v2/src/runtime/internal/utils.ts
|
|
4
4
|
*/
|
|
5
5
|
export declare function isJsonRequest(event: H3Event): boolean;
|
|
6
|
-
export declare function hasReqHeader(event: H3Event, name: string, includes: string);
|
|
6
|
+
export declare function hasReqHeader(event: H3Event, name: string, includes: string): boolean;
|
|
@@ -11,5 +11,5 @@ export function isJsonRequest(event) {
|
|
|
11
11
|
}
|
|
12
12
|
export function hasReqHeader(event, name, includes) {
|
|
13
13
|
const value = getRequestHeader(event, name);
|
|
14
|
-
return value && typeof value === "string" && value.toLowerCase().includes(includes);
|
|
14
|
+
return !!(value && typeof value === "string" && value.toLowerCase().includes(includes));
|
|
15
15
|
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { decodePath } from "ufo";
|
|
2
1
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
3
2
|
import { createHead } from "@unhead/vue/server";
|
|
4
3
|
import { sharedPrerenderCache } from "../cache.mjs";
|
|
@@ -13,7 +12,7 @@ const PRERENDER_NO_SSR_ROUTES = new Set([
|
|
|
13
12
|
]);
|
|
14
13
|
export function createSSRContext(event) {
|
|
15
14
|
const ssrContext = {
|
|
16
|
-
url:
|
|
15
|
+
url: event.path,
|
|
17
16
|
event,
|
|
18
17
|
runtimeConfig: useRuntimeConfig(event),
|
|
19
18
|
noSSR: !!NUXT_NO_SSR || event.context.nuxt?.noSSR || (import.meta.prerender ? PRERENDER_NO_SSR_ROUTES.has(event.path) : false),
|
|
@@ -10,9 +10,7 @@ interface Renderer {
|
|
|
10
10
|
renderScripts: () => string;
|
|
11
11
|
}>;
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
export declare const getSSRRenderer: unknown;
|
|
13
|
+
export declare const getSSRRenderer: () => Promise<Renderer>;
|
|
15
14
|
export declare function getRenderer(ssrContext: NuxtSSRContext): Promise<Renderer>;
|
|
16
|
-
|
|
17
|
-
export declare const getSSRStyles: unknown;
|
|
15
|
+
export declare const getSSRStyles: () => Promise<Record<string, () => Promise<string[]>>>;
|
|
18
16
|
export {};
|
|
@@ -4,10 +4,10 @@ import { stringify, uneval } from "devalue";
|
|
|
4
4
|
// @ts-expect-error virtual file
|
|
5
5
|
import { appId, multiApp } from "#internal/nuxt.config.mjs";
|
|
6
6
|
// @ts-expect-error virtual file
|
|
7
|
-
import { NUXT_JSON_PAYLOADS, NUXT_NO_SSR
|
|
7
|
+
import { NUXT_JSON_PAYLOADS, NUXT_NO_SSR } from "#internal/nuxt/nitro-config.mjs";
|
|
8
8
|
export function renderPayloadResponse(ssrContext) {
|
|
9
9
|
return {
|
|
10
|
-
body: NUXT_JSON_PAYLOADS ? stringify(splitPayload(ssrContext).payload, ssrContext["~payloadReducers"]) : `export default ${devalue(splitPayload(ssrContext).payload)}`,
|
|
10
|
+
body: NUXT_JSON_PAYLOADS ? encodeForwardSlashes(stringify(splitPayload(ssrContext).payload, ssrContext["~payloadReducers"])) : `export default ${devalue(splitPayload(ssrContext).payload)}`,
|
|
11
11
|
statusCode: getResponseStatus(ssrContext.event),
|
|
12
12
|
statusMessage: getResponseStatusText(ssrContext.event),
|
|
13
13
|
headers: {
|
|
@@ -17,7 +17,7 @@ export function renderPayloadResponse(ssrContext) {
|
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
export function renderPayloadJsonScript(opts) {
|
|
20
|
-
const contents = opts.data ? stringify(opts.data, opts.ssrContext["~payloadReducers"]) : "";
|
|
20
|
+
const contents = opts.data ? encodeForwardSlashes(stringify(opts.data, opts.ssrContext["~payloadReducers"])) : "";
|
|
21
21
|
const payload = {
|
|
22
22
|
"type": "application/json",
|
|
23
23
|
"innerHTML": contents,
|
|
@@ -33,13 +33,29 @@ export function renderPayloadJsonScript(opts) {
|
|
|
33
33
|
const config = uneval(opts.ssrContext.config);
|
|
34
34
|
return [payload, { innerHTML: multiApp ? `window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={config:${config}}` : `window.__NUXT__={};window.__NUXT__.config=${config}` }];
|
|
35
35
|
}
|
|
36
|
+
/**
|
|
37
|
+
* Encode forward slashes as unicode escape sequences to prevent
|
|
38
|
+
* Google from treating them as internal links and trying to crawl them.
|
|
39
|
+
* @see https://github.com/nuxt/nuxt/issues/24175
|
|
40
|
+
*/
|
|
41
|
+
function encodeForwardSlashes(str) {
|
|
42
|
+
return str.replaceAll("/", "\\u002F");
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Escape a string for safe interpolation inside a double-quoted JavaScript string literal.
|
|
46
|
+
* Prevents XSS when user-controlled URLs are embedded in inline `<script>` tags.
|
|
47
|
+
*/
|
|
48
|
+
function escapeJsString(str) {
|
|
49
|
+
return str.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n").replaceAll("\r", "\\r").replaceAll("/", "\\u002F").replaceAll("<", "\\u003C");
|
|
50
|
+
}
|
|
36
51
|
export function renderPayloadScript(opts) {
|
|
37
52
|
opts.data.config = opts.ssrContext.config;
|
|
38
|
-
const _PAYLOAD_EXTRACTION = !opts.ssrContext.noSSR && (import.meta.prerender && NUXT_PAYLOAD_EXTRACTION || NUXT_RUNTIME_PAYLOAD_EXTRACTION && (opts.routeOptions.isr || opts.routeOptions.cache));
|
|
39
53
|
const nuxtData = devalue(opts.data);
|
|
40
|
-
if (
|
|
41
|
-
|
|
42
|
-
const
|
|
54
|
+
if (opts.src) {
|
|
55
|
+
// Escape the URL to prevent XSS when interpolated into a JS string literal
|
|
56
|
+
const escapedSrc = escapeJsString(opts.src);
|
|
57
|
+
const singleAppPayload = `import p from "${escapedSrc}";window.__NUXT__={...p,...(${nuxtData})}`;
|
|
58
|
+
const multiAppPayload = `import p from "${escapedSrc}";window.__NUXT__=window.__NUXT__||{};window.__NUXT__[${JSON.stringify(appId)}]={...p,...(${nuxtData})}`;
|
|
43
59
|
return [{
|
|
44
60
|
type: "module",
|
|
45
61
|
innerHTML: multiApp ? multiAppPayload : singleAppPayload
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nuxt/nitro-server",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.4.2",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/nuxt/nuxt.git",
|
|
@@ -11,49 +11,71 @@
|
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"type": "module",
|
|
13
13
|
"types": "./dist/index.d.mts",
|
|
14
|
+
"typesVersions": {
|
|
15
|
+
"*": {
|
|
16
|
+
"h3": [
|
|
17
|
+
"./dist/runtime/h3-compat.d.mts"
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
14
21
|
"exports": {
|
|
15
|
-
".": "./dist/index.mjs"
|
|
22
|
+
".": "./dist/index.mjs",
|
|
23
|
+
"./h3": "./dist/runtime/h3-compat.mjs"
|
|
16
24
|
},
|
|
17
25
|
"files": [
|
|
18
26
|
"dist"
|
|
19
27
|
],
|
|
20
28
|
"dependencies": {
|
|
29
|
+
"@babel/plugin-syntax-typescript": "^7.28.6",
|
|
21
30
|
"@nuxt/devalue": "^2.0.2",
|
|
22
|
-
"@unhead/vue": "^2.1.
|
|
23
|
-
"@vue/shared": "^3.5.
|
|
31
|
+
"@unhead/vue": "^2.1.12",
|
|
32
|
+
"@vue/shared": "^3.5.30",
|
|
24
33
|
"consola": "^3.4.2",
|
|
25
34
|
"defu": "^6.1.4",
|
|
26
35
|
"destr": "^2.0.5",
|
|
27
|
-
"devalue": "^5.6.
|
|
36
|
+
"devalue": "^5.6.3",
|
|
28
37
|
"errx": "^0.1.0",
|
|
29
38
|
"escape-string-regexp": "^5.0.0",
|
|
30
39
|
"exsolve": "^1.0.8",
|
|
31
|
-
"h3": "^1.15.
|
|
32
|
-
"impound": "^1.
|
|
40
|
+
"h3": "^1.15.6",
|
|
41
|
+
"impound": "^1.1.5",
|
|
33
42
|
"klona": "^2.0.6",
|
|
34
43
|
"mocked-exports": "^0.1.1",
|
|
35
44
|
"nitropack": "^2.13.1",
|
|
45
|
+
"nypm": "^0.6.5",
|
|
36
46
|
"ohash": "^2.0.11",
|
|
37
47
|
"pathe": "^2.0.3",
|
|
38
48
|
"pkg-types": "^2.3.0",
|
|
39
|
-
"rou3": "^0.
|
|
40
|
-
"std-env": "^
|
|
49
|
+
"rou3": "^0.8.1",
|
|
50
|
+
"std-env": "^4.0.0",
|
|
41
51
|
"ufo": "^1.6.3",
|
|
42
52
|
"unctx": "^2.5.0",
|
|
43
53
|
"unstorage": "^1.17.4",
|
|
44
|
-
"vue": "^3.5.
|
|
54
|
+
"vue": "^3.5.30",
|
|
45
55
|
"vue-bundle-renderer": "^2.2.0",
|
|
46
56
|
"vue-devtools-stub": "^0.1.0",
|
|
47
|
-
"@nuxt/kit": "4.
|
|
57
|
+
"@nuxt/kit": "4.4.2"
|
|
48
58
|
},
|
|
49
59
|
"peerDependencies": {
|
|
50
|
-
"
|
|
60
|
+
"@babel/plugin-proposal-decorators": "^7.25.0",
|
|
61
|
+
"@rollup/plugin-babel": "^6.0.0 || ^7.0.0",
|
|
62
|
+
"nuxt": "^4.4.2"
|
|
63
|
+
},
|
|
64
|
+
"peerDependenciesMeta": {
|
|
65
|
+
"@babel/plugin-proposal-decorators": {
|
|
66
|
+
"optional": true
|
|
67
|
+
},
|
|
68
|
+
"@rollup/plugin-babel": {
|
|
69
|
+
"optional": true
|
|
70
|
+
}
|
|
51
71
|
},
|
|
52
72
|
"devDependencies": {
|
|
53
|
-
"
|
|
54
|
-
"
|
|
55
|
-
"
|
|
56
|
-
"
|
|
73
|
+
"@babel/plugin-proposal-decorators": "7.29.0",
|
|
74
|
+
"@rollup/plugin-babel": "7.0.0",
|
|
75
|
+
"obuild": "0.4.32",
|
|
76
|
+
"vitest": "4.0.18",
|
|
77
|
+
"nuxt": "4.4.2",
|
|
78
|
+
"@nuxt/schema": "4.4.2"
|
|
57
79
|
},
|
|
58
80
|
"engines": {
|
|
59
81
|
"node": "^20.19.0 || >=22.12.0"
|