nitro-nightly 3.0.1-20251023-220447-2d199369 → 3.1.0-20251024-111905-4718bf73

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.
@@ -66,6 +66,7 @@ import 'rendu';
66
66
  import 'vite';
67
67
  import 'get-port-please';
68
68
  import 'node:child_process';
69
+ import 'node:assert/strict';
69
70
 
70
71
  async function viteBuild(nitro$1) {
71
72
  if (nitro$1.options.dev) {
@@ -1,6 +1,6 @@
1
1
  import { createRequire } from 'node:module';
2
2
  import defu$1, { defu } from 'defu';
3
- import { E as Builder, M as MagicString, s as sanitizeFilePath, a as scanHandlers, f as formatCompatibilityDate } from './index.mjs';
3
+ import { F as Builder, M as MagicString, s as sanitizeFilePath, a as scanHandlers, f as formatCompatibilityDate } from './index.mjs';
4
4
  import { runtimeDir } from 'nitro/runtime/meta';
5
5
  import { a as alias, i as inject } from './index2.mjs';
6
6
  import sysPath__default, { resolve as resolve$2, relative, join, dirname, extname, basename, sep, normalize } from 'path';
@@ -22268,4 +22268,4 @@ function genRawValue(value, indent = "", options = {}) {
22268
22268
  return value.toString();
22269
22269
  }
22270
22270
 
22271
- export { loadOptions as A, runTask as B, C, listTasks as D, Builder as E, pm$1 as F, decode as G, encode as H, createUnimport as I, genSafeVariableName as J, genImport as K, genString as L, MagicString as M, N$1 as N, genObjectFromRaw as O, Parser as P, findNearestFile as Q, readGitConfig as R, readPackageJSON as S, findFile as T, fileURLToPath as U, resolveCompatibilityDatesFromEnv as V, index as W, scanHandlers as a, glob as b, snapshotStorage as c, copyPublicAssets as d, prettyPath as e, formatCompatibilityDate as f, getMagicString as g, h, createNitro as i, prepare as j, addRoute as k, scanUnprefixedPublicAssets as l, mime as m, compressPublicAssets as n, findAllRoutes as o, parse$1 as p, createRouter as q, resolveModulePath as r, sanitizeFilePath as s, genObjectKey as t, resolveNitroPath as u, toExports as v, writeFile as w, parseNodeModulePath as x, lookupNodeModuleSubpath as y, isDirectory as z };
22271
+ export { isDirectory as A, loadOptions as B, C, runTask as D, listTasks as E, Builder as F, pm$1 as G, decode as H, encode as I, createUnimport as J, genSafeVariableName as K, genImport as L, MagicString as M, N$1 as N, genString as O, Parser as P, genObjectFromRaw as Q, findNearestFile as R, readGitConfig as S, readPackageJSON as T, findFile as U, fileURLToPath as V, resolveCompatibilityDatesFromEnv as W, index as X, scanHandlers as a, glob as b, snapshotStorage as c, copyPublicAssets as d, prettyPath as e, formatCompatibilityDate as f, getMagicString as g, h, stripLiteral as i, prepare as j, createNitro as k, addRoute as l, mime as m, scanUnprefixedPublicAssets as n, compressPublicAssets as o, parse$1 as p, findAllRoutes as q, resolveModulePath as r, sanitizeFilePath as s, createRouter as t, genObjectKey as u, resolveNitroPath as v, writeFile as w, toExports as x, parseNodeModulePath as y, lookupNodeModuleSubpath as z };
@@ -1,4 +1,4 @@
1
- import { i as createNitro, k as addRoute, l as scanUnprefixedPublicAssets, n as compressPublicAssets, m as mime, w as writeFile, o as findAllRoutes, q as createRouter, t as genObjectKey, u as resolveNitroPath, v as toExports, r as resolveModulePath, x as parseNodeModulePath, y as lookupNodeModuleSubpath, z as isDirectory } from './index.mjs';
1
+ import { k as createNitro, l as addRoute, n as scanUnprefixedPublicAssets, o as compressPublicAssets, m as mime, w as writeFile, q as findAllRoutes, t as createRouter, u as genObjectKey, v as resolveNitroPath, x as toExports, r as resolveModulePath, y as parseNodeModulePath, z as lookupNodeModuleSubpath, A as isDirectory } from './index.mjs';
2
2
  import { pathToFileURL } from 'node:url';
3
3
  import { colors } from 'consola/utils';
4
4
  import { defu } from 'defu';
@@ -1,4 +1,4 @@
1
- import { F as pm, M as MagicString, P as Parser, G as decode, H as encode, I as createUnimport, J as genSafeVariableName, K as genImport, L as genString, O as genObjectFromRaw, b as glob, m as mime, r as resolveModulePath, w as writeFile } from './index.mjs';
1
+ import { G as pm, M as MagicString, P as Parser, H as decode, I as encode, J as createUnimport, K as genSafeVariableName, L as genImport, O as genString, Q as genObjectFromRaw, b as glob, m as mime, r as resolveModulePath, w as writeFile } from './index.mjs';
2
2
  import sysPath__default, { win32, posix, isAbsolute, resolve as resolve$1 } from 'path';
3
3
  import { defineEnv } from 'unenv';
4
4
  import { runtimeDir, runtimeDependencies } from 'nitro/runtime/meta';
@@ -9892,7 +9892,7 @@ async function getWasmImports(asset, _opts) {
9892
9892
  resolved: true
9893
9893
  };
9894
9894
  }
9895
- const { readPackageJSON } = await import('./index.mjs').then(function (n) { return n.W; });
9895
+ const { readPackageJSON } = await import('./index.mjs').then(function (n) { return n.X; });
9896
9896
  const pkgJSON = await readPackageJSON(asset.id);
9897
9897
  let resolved = true;
9898
9898
  const imports = [];
@@ -1,9 +1,9 @@
1
- import { s as sanitizeFilePath, f as formatCompatibilityDate, d as copyPublicAssets, a as scanHandlers, r as resolveModulePath, e as prettyPath, i as createNitro, j as prepare } from './index.mjs';
1
+ import { s as sanitizeFilePath, f as formatCompatibilityDate, d as copyPublicAssets, a as scanHandlers, r as resolveModulePath, e as prettyPath, M as MagicString, i as stripLiteral, j as prepare, k as createNitro } from './index.mjs';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { colors } from 'consola/utils';
4
4
  import { defu } from 'defu';
5
5
  import 'ufo';
6
- import { existsSync, watch as watch$1 } from 'node:fs';
6
+ import fs, { existsSync, watch as watch$1 } from 'node:fs';
7
7
  import { readFile, rm, mkdir, writeFile, readlink } from 'node:fs/promises';
8
8
  import 'node:zlib';
9
9
  import 'node:worker_threads';
@@ -25,14 +25,17 @@ import 'jiti';
25
25
  import 'klona';
26
26
  import 'unstorage';
27
27
  import 'ohash';
28
- import { resolve as resolve$1, join as join$1 } from 'node:path';
28
+ import path, { resolve as resolve$1, join as join$1 } from 'node:path';
29
29
  import 'youch-core';
30
30
  import 'youch';
31
31
  import 'source-map';
32
32
  import 'srvx';
33
- import { DevEnvironment } from 'vite';
33
+ import { DevEnvironment, normalizePath, isCSSRequest } from 'vite';
34
34
  import { getRandomPort } from 'get-port-please';
35
35
  import { spawn } from 'node:child_process';
36
+ import assert from 'node:assert/strict';
37
+ import 'node:assert';
38
+ import { createHash } from 'node:crypto';
36
39
 
37
40
  const getViteRollupConfig = (ctx) => {
38
41
  const nitro = ctx.nitro;
@@ -221,6 +224,7 @@ async function buildEnvironments(ctx, builder) {
221
224
  nitroOptions.renderer.template = tmp;
222
225
  }
223
226
  }
227
+ await builder.writeAssetsManifest?.();
224
228
  if (!isTest && !isCI) console.log();
225
229
  const buildInfo = [
226
230
  ["preset", nitro.options.preset],
@@ -635,234 +639,749 @@ function nitroPreviewPlugin(ctx) {
635
639
  };
636
640
  }
637
641
 
642
+ //#region src/plugins/shared.ts
643
+ function parseIdQuery(id) {
644
+ if (!id.includes("?")) return {
645
+ filename: id,
646
+ query: {}
647
+ };
648
+ const [filename, rawQuery] = id.split(`?`, 2);
649
+ const query = Object.fromEntries(new URLSearchParams(rawQuery));
650
+ return {
651
+ filename,
652
+ query
653
+ };
654
+ }
655
+ function toAssetsVirtual(options) {
656
+ return `virtual:fullstack/assets?${new URLSearchParams(options)}&lang.js`;
657
+ }
658
+ function parseAssetsVirtual(id) {
659
+ if (id.startsWith("\0virtual:fullstack/assets?")) return parseIdQuery(id).query;
660
+ }
661
+
662
+ //#endregion
663
+ //#region src/plugins/utils.ts
664
+ function createVirtualPlugin(name, load) {
665
+ name = "virtual:" + name;
666
+ return {
667
+ name: `rsc:virtual-${name}`,
668
+ resolveId: { handler(source, _importer, _options) {
669
+ return source === name ? "\0" + name : void 0;
670
+ } },
671
+ load: { handler(id, options) {
672
+ if (id === "\0" + name) return load.apply(this, [id, options]);
673
+ } }
674
+ };
675
+ }
676
+ function normalizeRelativePath(s) {
677
+ s = normalizePath(s);
678
+ return s[0] === "." ? s : "./" + s;
679
+ }
680
+ function hashString(v) {
681
+ return createHash("sha256").update(v).digest().toString("hex").slice(0, 12);
682
+ }
683
+
684
+ //#endregion
685
+ //#region src/plugins/vite-utils.ts
686
+ const VALID_ID_PREFIX = `/@id/`;
687
+ const NULL_BYTE_PLACEHOLDER = `__x00__`;
688
+ const FS_PREFIX = `/@fs/`;
689
+ function wrapId(id) {
690
+ return id.startsWith(VALID_ID_PREFIX) ? id : VALID_ID_PREFIX + id.replace("\0", NULL_BYTE_PLACEHOLDER);
691
+ }
692
+ function withTrailingSlash(path$1) {
693
+ if (path$1[path$1.length - 1] !== "/") return `${path$1}/`;
694
+ return path$1;
695
+ }
696
+ const postfixRE = /[?#].*$/;
697
+ function cleanUrl(url) {
698
+ return url.replace(postfixRE, "");
699
+ }
700
+ function splitFileAndPostfix(path$1) {
701
+ const file = cleanUrl(path$1);
702
+ return {
703
+ file,
704
+ postfix: path$1.slice(file.length)
705
+ };
706
+ }
707
+ const windowsSlashRE = /\\/g;
708
+ function slash(p) {
709
+ return p.replace(windowsSlashRE, "/");
710
+ }
711
+ const isWindows = typeof process !== "undefined" && process.platform === "win32";
712
+ function injectQuery(url, queryToInject) {
713
+ const { file, postfix } = splitFileAndPostfix(url);
714
+ const normalizedFile = isWindows ? slash(file) : file;
715
+ return `${normalizedFile}?${queryToInject}${postfix[0] === "?" ? `&${postfix.slice(1)}` : postfix}`;
716
+ }
717
+ function normalizeResolvedIdToUrl(environment, url, resolved) {
718
+ const root = environment.config.root;
719
+ const depsOptimizer = environment.depsOptimizer;
720
+ if (resolved.id.startsWith(withTrailingSlash(root))) url = resolved.id.slice(root.length);
721
+ else if (depsOptimizer?.isOptimizedDepFile(resolved.id) || resolved.id !== "/@react-refresh" && path.isAbsolute(resolved.id) && fs.existsSync(cleanUrl(resolved.id))) url = path.posix.join(FS_PREFIX, resolved.id);
722
+ else url = resolved.id;
723
+ if (url[0] !== "." && url[0] !== "/") url = wrapId(resolved.id);
724
+ return url;
725
+ }
726
+ function normalizeViteImportAnalysisUrl(environment, id) {
727
+ let url = normalizeResolvedIdToUrl(environment, id, { id });
728
+ if (environment.config.consumer === "client") {
729
+ const mod = environment.moduleGraph.getModuleById(id);
730
+ if (mod && mod.lastHMRTimestamp > 0) url = injectQuery(url, `t=${mod.lastHMRTimestamp}`);
731
+ }
732
+ return url;
733
+ }
734
+ function evalValue(rawValue) {
735
+ const fn = new Function(`
736
+ var console, exports, global, module, process, require
737
+ return (\n${rawValue}\n)
738
+ `);
739
+ return fn();
740
+ }
741
+ const directRequestRE = /(\?|&)direct=?(?:&|$)/;
742
+ function assetsPlugin(pluginOpts) {
743
+ let server;
744
+ let resolvedConfig;
745
+ const importAssetsMetaMap = {};
746
+ const bundleMap = {};
747
+ async function processAssetsImport(ctx, id, options) {
748
+ if (ctx.environment.mode === "dev") {
749
+ const result = {
750
+ entry: void 0,
751
+ js: [],
752
+ css: []
753
+ };
754
+ const environment = server.environments[options.environment];
755
+ assert(environment, `Unknown environment: ${options.environment}`);
756
+ if (options.environment === "client") result.entry = normalizeViteImportAnalysisUrl(environment, id);
757
+ if (environment.name !== "client") {
758
+ const collected = await collectCss(environment, id, { eager: pluginOpts?.experimental?.devEagerTransform ?? true });
759
+ result.css = collected.hrefs.map((href, i) => ({
760
+ href,
761
+ "data-vite-dev-id": collected.ids[i]
762
+ }));
763
+ }
764
+ return JSON.stringify(result);
765
+ } else {
766
+ const map = importAssetsMetaMap[options.environment] ??= {};
767
+ const meta = {
768
+ id,
769
+ key: path.relative(resolvedConfig.root, id),
770
+ importerEnvironment: ctx.environment.name,
771
+ isEntry: !!(map[id]?.isEntry || options.isEntry)
772
+ };
773
+ map[id] = meta;
774
+ return `__assets_manifest[${JSON.stringify(options.environment)}][${JSON.stringify(meta.key)}]`;
775
+ }
776
+ }
777
+ let writeAssetsManifestCalled = false;
778
+ async function writeAssetsManifest(builder) {
779
+ if (writeAssetsManifestCalled) return;
780
+ writeAssetsManifestCalled = true;
781
+ const manifest = {};
782
+ for (const [environmentName, metas] of Object.entries(importAssetsMetaMap)) {
783
+ const bundle = bundleMap[environmentName];
784
+ const assetDepsMap = collectAssetDeps(bundle);
785
+ for (const [id, meta] of Object.entries(metas)) {
786
+ const found = assetDepsMap[id];
787
+ if (!found) {
788
+ builder.config.logger.error(`[vite-plugin-fullstack] failed to find built chunk for ${meta.id} imported by ${meta.importerEnvironment} environment`);
789
+ return;
790
+ }
791
+ const result = {
792
+ js: [],
793
+ css: []
794
+ };
795
+ const { chunk, deps } = found;
796
+ if (environmentName === "client") {
797
+ result.entry = `/${chunk.fileName}`;
798
+ result.js = deps.js.map((fileName) => ({ href: `/${fileName}` }));
799
+ }
800
+ result.css = deps.css.map((fileName) => ({ href: `/${fileName}` }));
801
+ if (!builder.environments[environmentName].config.build.cssCodeSplit) {
802
+ const singleCss = Object.values(bundle).find((v) => v.type === "asset" && v.originalFileNames.includes("style.css"));
803
+ if (singleCss) result.css.push({ href: `/${singleCss.fileName}` });
804
+ }
805
+ (manifest[environmentName] ??= {})[meta.key] = result;
806
+ }
807
+ }
808
+ const importerEnvironments = new Set(Object.values(importAssetsMetaMap).flatMap((metas) => Object.values(metas)).flatMap((meta) => meta.importerEnvironment));
809
+ for (const environmentName of importerEnvironments) {
810
+ const outDir = builder.environments[environmentName].config.build.outDir;
811
+ fs.writeFileSync(path.join(outDir, BUILD_ASSETS_MANIFEST_NAME), `export default ${JSON.stringify(manifest, null, 2)};`);
812
+ const clientOutDir = builder.environments["client"].config.build.outDir;
813
+ for (const asset of Object.values(bundleMap[environmentName])) if (asset.type === "asset") {
814
+ const srcFile = path.join(outDir, asset.fileName);
815
+ const destFile = path.join(clientOutDir, asset.fileName);
816
+ fs.mkdirSync(path.dirname(destFile), { recursive: true });
817
+ fs.copyFileSync(srcFile, destFile);
818
+ }
819
+ }
820
+ }
821
+ return [
822
+ {
823
+ name: "fullstack:assets",
824
+ sharedDuringBuild: true,
825
+ configureServer(server_) {
826
+ server = server_;
827
+ },
828
+ configResolved(config) {
829
+ resolvedConfig = config;
830
+ },
831
+ configEnvironment(name) {
832
+ const serverEnvironments = pluginOpts?.serverEnvironments ?? ["ssr"];
833
+ if (serverEnvironments.includes(name)) return { build: { emitAssets: true } };
834
+ },
835
+ transform: { async handler(code, id, _options) {
836
+ if (!code.includes("import.meta.vite.assets")) return;
837
+ const output = new MagicString(code);
838
+ const strippedCode = stripLiteral(code);
839
+ const newImports = /* @__PURE__ */ new Set();
840
+ for (const match of code.matchAll(/import\.meta\.vite\.assets\(([\s\S]*?)\)/dg)) {
841
+ const [start, end] = match.indices[0];
842
+ if (!strippedCode.slice(start, end).includes("import.meta.vite.assets")) continue;
843
+ if (this.environment.name === "client") {
844
+ const replacement$1 = `(${JSON.stringify(EMPTY_ASSETS)})`;
845
+ output.update(start, end, replacement$1);
846
+ continue;
847
+ }
848
+ const argCode = match[1].trim();
849
+ const options = {
850
+ import: id,
851
+ environment: void 0,
852
+ asEntry: false
853
+ };
854
+ if (argCode) {
855
+ const argValue = evalValue(argCode);
856
+ Object.assign(options, argValue);
857
+ }
858
+ const environments = options.environment ? [options.environment] : ["client", this.environment.name];
859
+ const importedNames = [];
860
+ for (const environment of environments) {
861
+ const importSource = toAssetsVirtual({
862
+ import: options.import,
863
+ importer: id,
864
+ environment,
865
+ entry: options.asEntry ? "1" : ""
866
+ });
867
+ const hash = hashString(importSource);
868
+ const importedName = `__assets_${hash}`;
869
+ newImports.add(`;import ${importedName} from ${JSON.stringify(importSource)};\n`);
870
+ importedNames.push(importedName);
871
+ }
872
+ let replacement = importedNames[0];
873
+ if (importedNames.length > 1) {
874
+ newImports.add(`;import * as __assets_runtime from "@hiogawa/vite-plugin-fullstack/runtime";\n`);
875
+ replacement = `__assets_runtime.mergeAssets(${importedNames.join(", ")})`;
876
+ }
877
+ output.update(start, end, `(${replacement})`);
878
+ }
879
+ if (output.hasChanged()) {
880
+ for (const newImport of newImports) output.append(newImport);
881
+ return {
882
+ code: output.toString(),
883
+ map: output.generateMap({ hires: "boundary" })
884
+ };
885
+ }
886
+ } },
887
+ resolveId: { handler(source) {
888
+ if (source.startsWith("virtual:fullstack/assets?")) return "\0" + source;
889
+ if (source === "virtual:fullstack/assets-manifest") {
890
+ assert.notEqual(this.environment.name, "client");
891
+ assert.equal(this.environment.mode, "build");
892
+ return {
893
+ id: source,
894
+ external: true
895
+ };
896
+ }
897
+ } },
898
+ load: { async handler(id) {
899
+ const parsed = parseAssetsVirtual(id);
900
+ if (!parsed) return;
901
+ assert.notEqual(this.environment.name, "client");
902
+ const resolved = await this.resolve(parsed.import, parsed.importer);
903
+ assert(resolved, `Failed to resolve: ${parsed.import}`);
904
+ const s = new MagicString("");
905
+ const code = await processAssetsImport(this, resolved.id, {
906
+ environment: parsed.environment,
907
+ isEntry: !!parsed.entry
908
+ });
909
+ s.append(`export default ${code};\n`);
910
+ if (this.environment.mode === "build") s.prepend(`import __assets_manifest from "virtual:fullstack/assets-manifest";\n`);
911
+ return s.toString();
912
+ } },
913
+ renderChunk(code, chunk) {
914
+ if (code.includes("virtual:fullstack/assets-manifest")) {
915
+ const replacement = normalizeRelativePath(path.relative(path.join(chunk.fileName, ".."), BUILD_ASSETS_MANIFEST_NAME));
916
+ code = code.replaceAll("virtual:fullstack/assets-manifest", () => replacement);
917
+ return { code };
918
+ }
919
+ return;
920
+ },
921
+ writeBundle(_options, bundle) {
922
+ bundleMap[this.environment.name] = bundle;
923
+ },
924
+ buildStart() {
925
+ if (this.environment.mode == "build" && this.environment.name === "client") {
926
+ const metas = importAssetsMetaMap["client"];
927
+ if (metas) {
928
+ for (const meta of Object.values(importAssetsMetaMap["client"])) if (meta.isEntry) this.emitFile({
929
+ type: "chunk",
930
+ id: meta.id,
931
+ preserveSignature: "exports-only"
932
+ });
933
+ }
934
+ }
935
+ },
936
+ buildApp: {
937
+ order: "pre",
938
+ async handler(builder) {
939
+ builder.writeAssetsManifest = async () => {
940
+ await writeAssetsManifest(builder);
941
+ };
942
+ }
943
+ }
944
+ },
945
+ {
946
+ name: "fullstack:write-assets-manifest-post",
947
+ buildApp: {
948
+ order: "post",
949
+ async handler(builder) {
950
+ await builder.writeAssetsManifest();
951
+ }
952
+ }
953
+ },
954
+ {
955
+ name: "fullstack:assets-query",
956
+ sharedDuringBuild: true,
957
+ resolveId: {
958
+ order: "pre",
959
+ handler(source) {
960
+ const { query } = parseIdQuery(source);
961
+ const value = query["assets"];
962
+ if (typeof value !== "undefined") {
963
+ if (this.environment.name === "client") return `\0virtual:fullstack/empty-assets`;
964
+ }
965
+ if (source === "virtual:fullstack/runtime") return this.resolve("./runtime.js", import.meta.filename);
966
+ }
967
+ },
968
+ load: { async handler(id) {
969
+ if (id === "\0virtual:fullstack/empty-assets") return `export default ${JSON.stringify(EMPTY_ASSETS)}`;
970
+ const { filename, query } = parseIdQuery(id);
971
+ const value = query["assets"];
972
+ if (typeof value !== "undefined") {
973
+ const s = new MagicString("");
974
+ const codes = [];
975
+ if (value) {
976
+ const code = await processAssetsImport(this, filename, {
977
+ environment: value,
978
+ isEntry: value === "client"
979
+ });
980
+ codes.push(code);
981
+ } else {
982
+ const code1 = await processAssetsImport(this, filename, {
983
+ environment: "client",
984
+ isEntry: false
985
+ });
986
+ const code2 = await processAssetsImport(this, filename, {
987
+ environment: this.environment.name,
988
+ isEntry: false
989
+ });
990
+ codes.push(code1, code2);
991
+ }
992
+ s.append(`
993
+ import * as __assets_runtime from "virtual:fullstack/runtime";\n
994
+ export default __assets_runtime.mergeAssets(${codes.join(", ")});
995
+ `);
996
+ if (this.environment.mode === "build") s.prepend(`import __assets_manifest from "virtual:fullstack/assets-manifest";\n`);
997
+ return {
998
+ code: s.toString(),
999
+ moduleSideEffects: false
1000
+ };
1001
+ }
1002
+ } },
1003
+ hotUpdate(ctx) {
1004
+ if (this.environment.name === "rsc") {
1005
+ const mods = collectModuleDependents(ctx.modules);
1006
+ for (const mod of mods) if (mod.id) {
1007
+ const ids = [
1008
+ `${mod.id}?assets`,
1009
+ `${mod.id}?assets=client`,
1010
+ `${mod.id}?assets=${this.environment.name}`
1011
+ ];
1012
+ for (const id of ids) invalidteModuleById(this.environment, id);
1013
+ }
1014
+ }
1015
+ }
1016
+ },
1017
+ {
1018
+ ...createVirtualPlugin("fullstack/client-fallback", () => "export {}"),
1019
+ configEnvironment: {
1020
+ order: "post",
1021
+ handler(name, config, _env) {
1022
+ if (name === "client") {
1023
+ const clientBuildFallback = pluginOpts?.experimental?.clientBuildFallback ?? true;
1024
+ if (clientBuildFallback && !config.build?.rollupOptions?.input) return { build: { rollupOptions: { input: { __fallback: "virtual:fullstack/client-fallback" } } } };
1025
+ }
1026
+ }
1027
+ },
1028
+ generateBundle(_optoins, bundle) {
1029
+ if (this.environment.name !== "client") return;
1030
+ for (const [k, v] of Object.entries(bundle)) if (v.type === "chunk" && v.name === "__fallback") delete bundle[k];
1031
+ }
1032
+ },
1033
+ patchViteClientPlugin(),
1034
+ patchVueScopeCssHmr(),
1035
+ patchCssLinkSelfAccept()
1036
+ ];
1037
+ }
1038
+ const EMPTY_ASSETS = {
1039
+ js: [],
1040
+ css: []
1041
+ };
1042
+ const BUILD_ASSETS_MANIFEST_NAME = "__fullstack_assets_manifest.js";
1043
+ async function collectCss(environment, entryId, options) {
1044
+ const visited = /* @__PURE__ */ new Set();
1045
+ const cssIds = /* @__PURE__ */ new Set();
1046
+ async function recurse(id) {
1047
+ if (visited.has(id) || parseAssetsVirtual(id) || "assets" in parseIdQuery(id).query) return;
1048
+ visited.add(id);
1049
+ const mod = environment.moduleGraph.getModuleById(id);
1050
+ if (!mod) return;
1051
+ if (options.eager && !mod?.transformResult) try {
1052
+ await environment.transformRequest(id);
1053
+ } catch (e) {
1054
+ console.error(`[collectCss] Failed to transform '${id}'`, e);
1055
+ }
1056
+ for (const next of mod?.importedModules ?? []) if (next.id) if (isCSSRequest(next.id)) {
1057
+ if (hasSpecialCssQuery(next.id)) continue;
1058
+ cssIds.add(next.id);
1059
+ } else await recurse(next.id);
1060
+ }
1061
+ await recurse(entryId);
1062
+ const hrefs = [...cssIds].map((id) => normalizeViteImportAnalysisUrl(environment, id));
1063
+ return {
1064
+ ids: [...cssIds],
1065
+ hrefs
1066
+ };
1067
+ }
1068
+ function invalidteModuleById(environment, id) {
1069
+ const mod = environment.moduleGraph.getModuleById(id);
1070
+ if (mod) environment.moduleGraph.invalidateModule(mod);
1071
+ return mod;
1072
+ }
1073
+ function collectModuleDependents(mods) {
1074
+ const visited = /* @__PURE__ */ new Set();
1075
+ function recurse(mod) {
1076
+ if (visited.has(mod)) return;
1077
+ visited.add(mod);
1078
+ for (const importer of mod.importers) recurse(importer);
1079
+ }
1080
+ for (const mod of mods) recurse(mod);
1081
+ return [...visited];
1082
+ }
1083
+ function hasSpecialCssQuery(id) {
1084
+ return /[?&](url|inline|raw)(\b|=|&|$)/.test(id);
1085
+ }
1086
+ function collectAssetDeps(bundle) {
1087
+ const chunkToDeps = /* @__PURE__ */ new Map();
1088
+ for (const chunk of Object.values(bundle)) if (chunk.type === "chunk") chunkToDeps.set(chunk, collectAssetDepsInner(chunk.fileName, bundle));
1089
+ const idToDeps = {};
1090
+ for (const [chunk, deps] of chunkToDeps.entries()) for (const id of chunk.moduleIds) idToDeps[id] = {
1091
+ chunk,
1092
+ deps
1093
+ };
1094
+ return idToDeps;
1095
+ }
1096
+ function collectAssetDepsInner(fileName, bundle) {
1097
+ const visited = /* @__PURE__ */ new Set();
1098
+ const css = [];
1099
+ function recurse(k) {
1100
+ if (visited.has(k)) return;
1101
+ visited.add(k);
1102
+ const v = bundle[k];
1103
+ assert(v, `Not found '${k}' in the bundle`);
1104
+ if (v.type === "chunk") {
1105
+ css.push(...v.viteMetadata?.importedCss ?? []);
1106
+ for (const k2 of v.imports) if (k2 in bundle) recurse(k2);
1107
+ }
1108
+ }
1109
+ recurse(fileName);
1110
+ return {
1111
+ js: [...visited],
1112
+ css: [...new Set(css)]
1113
+ };
1114
+ }
1115
+ function patchViteClientPlugin() {
1116
+ const viteClientPath = normalizePath(fileURLToPath(import.meta.resolve("vite/dist/client/client.mjs")));
1117
+ function endIndexOf(code, searchValue) {
1118
+ const i = code.lastIndexOf(searchValue);
1119
+ return i === -1 ? i : i + searchValue.length;
1120
+ }
1121
+ return {
1122
+ name: "fullstack:patch-vite-client",
1123
+ transform: { handler(code, id) {
1124
+ if (id === viteClientPath) {
1125
+ if (code.includes("linkSheetsMap")) return;
1126
+ const s = new MagicString(code);
1127
+ s.prependLeft(code.indexOf("const sheetsMap"), `\
1128
+ const linkSheetsMap = new Map();
1129
+ document
1130
+ .querySelectorAll('link[rel="stylesheet"][data-vite-dev-id]')
1131
+ .forEach((el) => {
1132
+ linkSheetsMap.set(el.getAttribute('data-vite-dev-id'), el)
1133
+ });
1134
+ `);
1135
+ s.appendLeft(endIndexOf(code, `function updateStyle(id, content) {`), `if (linkSheetsMap.has(id)) { return }`);
1136
+ s.appendLeft(endIndexOf(code, `function removeStyle(id) {`), `
1137
+ const link = linkSheetsMap.get(id);
1138
+ if (link) {
1139
+ document
1140
+ .querySelectorAll(
1141
+ 'link[rel="stylesheet"][data-vite-dev-id]',
1142
+ )
1143
+ .forEach((el) => {
1144
+ if (el.getAttribute('data-vite-dev-id') === id) {
1145
+ el.remove()
1146
+ }
1147
+ })
1148
+ linkSheetsMap.delete(id)
1149
+ }
1150
+ `);
1151
+ return s.toString();
1152
+ }
1153
+ } }
1154
+ };
1155
+ }
1156
+ function patchVueScopeCssHmr() {
1157
+ return {
1158
+ name: "fullstack:patch-vue-scoped-css-hmr",
1159
+ configureServer(server) {
1160
+ server.middlewares.use((req, _res, next) => {
1161
+ if (req.headers.accept?.includes("text/css") && req.url?.includes("&lang.css=")) req.url = req.url.replace("&lang.css=", "?lang.css");
1162
+ next();
1163
+ });
1164
+ }
1165
+ };
1166
+ }
1167
+ function patchCssLinkSelfAccept() {
1168
+ return {
1169
+ name: "fullstack:patch-css-link-self-accept",
1170
+ apply: "serve",
1171
+ transform: {
1172
+ order: "post",
1173
+ handler(_code, id, _options) {
1174
+ if (this.environment.name === "client" && this.environment.mode === "dev" && isCSSRequest(id) && directRequestRE.test(id)) {
1175
+ const mod = this.environment.moduleGraph.getModuleById(id);
1176
+ if (mod && !mod.isSelfAccepting) mod.isSelfAccepting = true;
1177
+ }
1178
+ }
1179
+ }
1180
+ };
1181
+ }
1182
+
638
1183
  const DEFAULT_EXTENSIONS = [".ts", ".js", ".mts", ".mjs", ".tsx", ".jsx"];
1184
+ const debug = process.env.NITRO_DEBUG ? (...args) => console.log("[nitro]", ...args) : () => {
1185
+ };
639
1186
  function nitro(pluginConfig = {}) {
640
- const ctx = {
641
- pluginConfig,
642
- _entryPoints: {},
643
- _manifest: {},
644
- _serviceBundles: {}
645
- };
1187
+ const ctx = createContext(pluginConfig);
646
1188
  return [
647
- nitroPlugin(ctx),
648
- nitroServicePlugin(ctx),
1189
+ nitroInit(ctx),
1190
+ nitroEnv(ctx),
1191
+ nitroMain(ctx),
1192
+ nitroPrepare(ctx),
1193
+ nitroService(ctx),
649
1194
  nitroPreviewPlugin(),
650
- nitroRollupPlugins(ctx)
1195
+ pluginConfig.experimental?.assetsImport !== false && assetsPlugin({
1196
+ experimental: {
1197
+ // See https://github.com/hi-ogawa/vite-plugins/pull/1289
1198
+ clientBuildFallback: false
1199
+ }
1200
+ })
651
1201
  ];
652
1202
  }
653
- function nitroPlugin(ctx) {
654
- return [
655
- {
656
- name: "nitro:main",
657
- // Opt-in this plugin into the shared plugins pipeline
658
- sharedDuringBuild: true,
659
- // Only apply this plugin during build or dev
660
- apply: (config, configEnv) => !configEnv.isPreview,
661
- // Extend vite config before it's resolved
662
- async config(userConfig, configEnv) {
663
- ctx.nitro = ctx.pluginConfig._nitro || await createNitro({
664
- dev: configEnv.mode === "development",
665
- rootDir: userConfig.root,
666
- ...defu(ctx.pluginConfig.config, userConfig.nitro)
667
- });
668
- if (!ctx.pluginConfig.services?.ssr) {
669
- ctx.pluginConfig.services ??= {};
670
- if (userConfig.environments?.ssr === void 0) {
671
- const ssrEntry = resolveModulePath("./entry-server", {
672
- from: ["", "app", "src"].flatMap(
673
- (d) => ctx.nitro.options.scanDirs.map((s) => join(s, d) + "/")
674
- ),
675
- extensions: DEFAULT_EXTENSIONS,
676
- try: true
677
- });
678
- if (ssrEntry) {
679
- ctx.pluginConfig.services.ssr = { entry: ssrEntry };
680
- ctx.nitro.logger.info(
681
- `Using \`${prettyPath(ssrEntry)}\` as vite ssr entry.`
682
- );
683
- }
684
- } else {
685
- let ssrEntry = getEntry(
686
- userConfig.environments.ssr.build?.rollupOptions?.input
687
- );
688
- if (typeof ssrEntry === "string") {
689
- ssrEntry = resolveModulePath(ssrEntry, {
690
- from: ctx.nitro.options.scanDirs,
691
- extensions: DEFAULT_EXTENSIONS,
692
- suffixes: ["", "/index"],
693
- try: true
694
- }) || ssrEntry;
695
- ctx.pluginConfig.services.ssr = { entry: ssrEntry };
696
- } else {
697
- this.error(`Invalid input type for SSR entry point.`);
698
- }
1203
+ function nitroInit(ctx) {
1204
+ return {
1205
+ name: "nitro:init",
1206
+ sharedDuringBuild: true,
1207
+ apply: (_config, configEnv) => !configEnv.isPreview,
1208
+ async config(config, configEnv) {
1209
+ if (!ctx._initialized) {
1210
+ debug("[init] Initializing nitro");
1211
+ ctx._initialized = true;
1212
+ await setupNitroContext(ctx, configEnv, config);
1213
+ }
1214
+ },
1215
+ applyToEnvironment(env) {
1216
+ if (env.name === "nitro" && ctx.nitro?.options.dev) {
1217
+ debug("[init] Adding rollup plugins for dev");
1218
+ return [...ctx.rollupConfig?.config.plugins || []];
1219
+ }
1220
+ }
1221
+ };
1222
+ }
1223
+ function nitroEnv(ctx) {
1224
+ return {
1225
+ name: "nitro:env",
1226
+ sharedDuringBuild: true,
1227
+ apply: (_config, configEnv) => !configEnv.isPreview,
1228
+ async config(userConfig, _configEnv) {
1229
+ debug("[env] Extending config (environments)");
1230
+ const environments = {
1231
+ ...createServiceEnvironments(ctx),
1232
+ nitro: createNitroEnvironment(ctx)
1233
+ };
1234
+ environments.client = {
1235
+ consumer: userConfig.environments?.client?.consumer ?? "client",
1236
+ build: {
1237
+ rollupOptions: {
1238
+ input: userConfig.environments?.client?.build?.rollupOptions?.input ?? useNitro(ctx).options.renderer?.template
699
1239
  }
700
1240
  }
701
- if (!ctx.nitro.options.renderer?.entry && !ctx.nitro.options.renderer?.template && ctx.pluginConfig.services.ssr?.entry) {
702
- ctx.nitro.options.renderer ??= {};
703
- ctx.nitro.options.renderer.entry = resolve(
704
- runtimeDir,
705
- "internal/vite/ssr-renderer"
706
- );
707
- }
708
- const publicDistDir = ctx._publicDistDir = userConfig.build?.outDir || resolve(ctx.nitro.options.buildDir, "vite/public");
709
- ctx.nitro.options.publicAssets.push({
710
- dir: publicDistDir,
711
- maxAge: 0,
712
- baseURL: "/",
713
- fallthrough: true
714
- });
715
- if (!ctx.nitro.options.dev) {
716
- ctx.nitro.options.unenv.push({
717
- meta: { name: "nitro-vite" },
718
- polyfill: ["#nitro-vite-setup"]
719
- });
720
- }
721
- await ctx.nitro.hooks.callHook("build:before", ctx.nitro);
722
- ctx.rollupConfig = await getViteRollupConfig(ctx);
723
- if (ctx.nitro.options.dev) {
724
- await ctx.nitro.hooks.callHook(
725
- "rollup:before",
726
- ctx.nitro,
727
- ctx.rollupConfig.config
728
- );
729
- }
730
- if (ctx.nitro.options.dev && !ctx.devWorker) {
731
- ctx.devWorker = createDevWorker(ctx);
1241
+ };
1242
+ debug("[env] Environments:", Object.keys(environments).join(", "));
1243
+ return {
1244
+ environments
1245
+ };
1246
+ },
1247
+ configEnvironment(name, config) {
1248
+ if (config.consumer === "client") {
1249
+ debug(
1250
+ "[env] Configuring client environment",
1251
+ name === "client" ? "" : ` (${name})`
1252
+ );
1253
+ config.build.emptyOutDir = false;
1254
+ config.build.outDir = useNitro(ctx).options.output.publicDir;
1255
+ } else {
1256
+ if (ctx.pluginConfig.experimental?.virtualBundle && name in (ctx.pluginConfig.services || {})) {
1257
+ debug("[env] Configuring service environment for virtual:", name);
1258
+ config.build ??= {};
1259
+ config.build.write = config.build.write ?? false;
732
1260
  }
733
- if (ctx.nitro.options.dev && !ctx.devApp) {
734
- ctx.devApp = new NitroDevApp(ctx.nitro);
1261
+ }
1262
+ }
1263
+ };
1264
+ }
1265
+ function nitroMain(ctx) {
1266
+ return {
1267
+ name: "nitro:main",
1268
+ sharedDuringBuild: true,
1269
+ apply: (_config, configEnv) => !configEnv.isPreview,
1270
+ async config(userConfig, _configEnv) {
1271
+ debug("[main] Extending config (appType, resolve, server)");
1272
+ if (!ctx.rollupConfig) {
1273
+ throw new Error("Nitro rollup config is not initialized yet.");
1274
+ }
1275
+ return {
1276
+ appType: userConfig.appType || "custom",
1277
+ resolve: {
1278
+ // TODO: environment specific aliases not working
1279
+ // https://github.com/vitejs/vite/pull/17583 (seems not effective)
1280
+ alias: ctx.rollupConfig.base.aliases
1281
+ },
1282
+ builder: {
1283
+ sharedConfigBuild: true
1284
+ },
1285
+ server: {
1286
+ port: Number.parseInt(process.env.PORT || "") || userConfig.server?.port || useNitro(ctx).options.devServer?.port || 3e3
735
1287
  }
736
- return {
737
- // Don't include HTML middlewares
738
- appType: userConfig.appType || "custom",
739
- // Add Nitro as a Vite environment
740
- environments: {
741
- client: {
742
- consumer: userConfig.environments?.client?.consumer ?? "client",
743
- build: {
744
- rollupOptions: {
745
- input: userConfig.environments?.client?.build?.rollupOptions?.input ?? ctx.nitro.options.renderer?.template
746
- }
747
- }
748
- },
749
- ...createServiceEnvironments(ctx),
750
- nitro: createNitroEnvironment(ctx)
751
- },
752
- resolve: {
753
- // TODO: environment specific aliases not working
754
- // https://github.com/vitejs/vite/pull/17583 (seems not effective)
755
- alias: ctx.rollupConfig.base.aliases
756
- },
757
- build: {
758
- // TODO: Support server environment emitted assets
759
- assetsInlineLimit: 4096 * 4
760
- },
761
- builder: {
762
- /// Share the config instance among environments to align with the behavior of dev server
763
- sharedConfigBuild: true
764
- },
765
- server: {
766
- port: Number.parseInt(process.env.PORT || "") || userConfig.server?.port || ctx.nitro.options.devServer?.port || 3e3
767
- }
768
- };
769
- },
770
- configResolved(config) {
771
- if (config.command === "build") {
772
- for (const env of Object.values(config.environments)) {
773
- if (env.consumer === "client") {
774
- const { assetsDir } = env.build;
775
- const rule = ctx.nitro.options.routeRules[`/${assetsDir}/**`] ??= {};
776
- if (!rule.headers?.["cache-control"]) {
777
- rule.headers = {
778
- ...rule.headers,
779
- "cache-control": `public, max-age=31536000, immutable`
780
- };
781
- }
1288
+ };
1289
+ },
1290
+ configResolved(config) {
1291
+ if (config.command === "build") {
1292
+ debug("[main] Inferring caching routes");
1293
+ for (const env of Object.values(config.environments)) {
1294
+ if (env.consumer === "client") {
1295
+ const rule = ctx.nitro.options.routeRules[`/${env.build.assetsDir}/**`] ??= {};
1296
+ if (!rule.headers?.["cache-control"]) {
1297
+ rule.headers = {
1298
+ ...rule.headers,
1299
+ "cache-control": `public, max-age=31536000, immutable`
1300
+ };
782
1301
  }
783
1302
  }
784
1303
  }
785
- ctx.nitro.routing.sync();
786
- },
787
- buildApp: {
788
- order: "post",
789
- handler(builder) {
790
- return buildEnvironments(ctx, builder);
791
- }
792
- },
793
- generateBundle: {
794
- handler(_options, bundle) {
795
- const { root } = this.environment.config;
796
- const services = ctx.pluginConfig.services || {};
797
- const serviceNames = Object.keys(services);
798
- const isRegisteredService = serviceNames.includes(
799
- this.environment.name
800
- );
801
- let entryFile;
802
- for (const [_name, file] of Object.entries(bundle)) {
803
- if (file.type === "chunk") {
804
- if (isRegisteredService && file.isEntry) {
805
- if (entryFile !== void 0) {
806
- this.error(
807
- `Multiple entry points found for service "${this.environment.name}". Only one entry point is allowed.`
808
- );
809
- }
1304
+ }
1305
+ debug("[main] Syncing nitro routes");
1306
+ ctx.nitro.routing.sync();
1307
+ },
1308
+ buildApp: {
1309
+ order: "post",
1310
+ handler(builder) {
1311
+ debug("[main] Building environments");
1312
+ return buildEnvironments(ctx, builder);
1313
+ }
1314
+ },
1315
+ generateBundle: {
1316
+ handler(_options, bundle) {
1317
+ const environment = this.environment;
1318
+ debug(
1319
+ "[main] Generating manifest and entry points for environment:",
1320
+ environment.name
1321
+ );
1322
+ const { root } = environment.config;
1323
+ const services = ctx.pluginConfig.services || {};
1324
+ const serviceNames = Object.keys(services);
1325
+ const isRegisteredService = serviceNames.includes(environment.name);
1326
+ let entryFile;
1327
+ for (const [_name, file] of Object.entries(bundle)) {
1328
+ if (file.type === "chunk") {
1329
+ if (isRegisteredService && file.isEntry) {
1330
+ if (entryFile === void 0) {
810
1331
  entryFile = file.fileName;
811
- }
812
- const filteredModuleIds = file.moduleIds.filter(
813
- (id) => id.startsWith(root)
814
- );
815
- for (const id of filteredModuleIds) {
816
- const originalFile = relative(root, id);
817
- ctx._manifest[originalFile] = { file: file.fileName };
1332
+ } else {
1333
+ this.warn(
1334
+ `Multiple entry points found for service "${environment.name}"`
1335
+ );
818
1336
  }
819
1337
  }
820
- }
821
- if (isRegisteredService) {
822
- if (entryFile === void 0) {
823
- this.error(
824
- `No entry point found for service "${this.environment.name}".`
825
- );
1338
+ const filteredModuleIds = file.moduleIds.filter(
1339
+ (id) => id.startsWith(root)
1340
+ );
1341
+ for (const id of filteredModuleIds) {
1342
+ const originalFile = relative(root, id);
1343
+ ctx._manifest[originalFile] = { file: file.fileName };
826
1344
  }
827
- ctx._entryPoints[this.environment.name] = entryFile;
828
- ctx._serviceBundles[this.environment.name] = bundle;
829
1345
  }
830
1346
  }
831
- },
832
- // Modify environment configs before it's resolved.
833
- configEnvironment(name, config) {
834
- if (config.consumer === "client") {
835
- config.build.emptyOutDir = false;
836
- config.build.outDir = ctx.nitro.options.output.publicDir;
837
- }
838
- const services = ctx.pluginConfig.services || {};
839
- const serviceNames = Object.keys(services);
840
- if (serviceNames.includes(name) && ctx.pluginConfig.experimental?.virtualBundle) {
841
- config.build ??= {};
842
- config.build.write = config.build.write ?? false;
1347
+ if (isRegisteredService) {
1348
+ if (entryFile === void 0) {
1349
+ this.error(
1350
+ `No entry point found for service "${this.environment.name}".`
1351
+ );
1352
+ }
1353
+ ctx._entryPoints[this.environment.name] = entryFile;
1354
+ ctx._serviceBundles[this.environment.name] = bundle;
843
1355
  }
844
- },
845
- // Extend Vite dev server with Nitro middleware
846
- configureServer: (server) => configureViteDevServer(ctx, server)
1356
+ }
847
1357
  },
848
- {
849
- name: "nitro:prepare",
850
- buildApp: {
851
- // clean the output directory before any environment is built
852
- order: "pre",
853
- async handler() {
854
- const nitro2 = ctx.nitro;
855
- await prepare(nitro2);
856
- }
1358
+ configureServer: (server) => {
1359
+ debug("[main] Configuring dev server");
1360
+ return configureViteDevServer(ctx, server);
1361
+ }
1362
+ };
1363
+ }
1364
+ function nitroPrepare(ctx) {
1365
+ return {
1366
+ name: "nitro:prepare",
1367
+ sharedDuringBuild: true,
1368
+ applyToEnvironment: (env) => env.name === "nitro",
1369
+ buildApp: {
1370
+ // Clean the output directory before any environment is built
1371
+ order: "pre",
1372
+ async handler() {
1373
+ debug("[prepare] Preparing output directory");
1374
+ const nitro2 = ctx.nitro;
1375
+ await prepare(nitro2);
857
1376
  }
858
1377
  }
859
- ];
1378
+ };
860
1379
  }
861
- function nitroServicePlugin(ctx) {
1380
+ function nitroService(ctx) {
862
1381
  return {
863
1382
  name: "nitro:service",
864
1383
  enforce: "pre",
865
- // Only apply this plugin to the nitro environment
1384
+ sharedDuringBuild: true,
866
1385
  applyToEnvironment: (env) => env.name === "nitro",
867
1386
  resolveId: {
868
1387
  async handler(id, importer, options) {
@@ -928,43 +1447,95 @@ function nitroServicePlugin(ctx) {
928
1447
  }
929
1448
  };
930
1449
  }
931
- function nitroRollupPlugins(ctx) {
932
- const createHookCaller = (hook, order) => {
933
- const handler = async function(...args) {
934
- for (const plugin of ctx.rollupConfig.config.plugins) {
935
- if (typeof plugin[hook] !== "function") continue;
936
- let res;
937
- try {
938
- res = await plugin[hook].call(this, ...args);
939
- } catch (error) {
940
- throw new Error(
941
- `[nitro] Calling rollup plugin ${plugin.name || "unknown"}.${hook} failed`,
942
- { cause: error }
943
- );
944
- }
945
- if (res) {
946
- if (hook === "resolveId" && res.id?.startsWith?.("file://")) {
947
- res.id = fileURLToPath(res.id);
948
- }
949
- return res;
950
- }
951
- }
952
- };
953
- Object.defineProperty(handler, "name", { value: hook });
954
- return order ? { order, handler } : handler;
955
- };
1450
+ function createContext(pluginConfig) {
956
1451
  return {
957
- name: "nitro:rollup-hooks",
958
- applyToEnvironment: (env) => env.name === "nitro",
959
- buildStart: createHookCaller("buildStart", "pre"),
960
- resolveId: createHookCaller("resolveId", "pre"),
961
- load: createHookCaller("load", "pre"),
962
- transform: createHookCaller("transform", "post"),
963
- renderChunk: createHookCaller("renderChunk", "post"),
964
- generateBundle: createHookCaller("generateBundle", "post"),
965
- buildEnd: createHookCaller("buildEnd", "post")
1452
+ pluginConfig,
1453
+ _entryPoints: {},
1454
+ _manifest: {},
1455
+ _serviceBundles: {}
966
1456
  };
967
1457
  }
1458
+ function useNitro(ctx) {
1459
+ if (!ctx.nitro) {
1460
+ throw new Error("Nitro instance is not initialized yet.");
1461
+ }
1462
+ return ctx.nitro;
1463
+ }
1464
+ async function setupNitroContext(ctx, configEnv, userConfig) {
1465
+ ctx.nitro = ctx.pluginConfig._nitro || await createNitro({
1466
+ dev: configEnv.mode === "development",
1467
+ rootDir: userConfig.root,
1468
+ ...defu(ctx.pluginConfig.config, userConfig.nitro)
1469
+ });
1470
+ if (!ctx.pluginConfig.services?.ssr) {
1471
+ ctx.pluginConfig.services ??= {};
1472
+ if (userConfig.environments?.ssr === void 0) {
1473
+ const ssrEntry = resolveModulePath("./entry-server", {
1474
+ from: ["", "app", "src"].flatMap(
1475
+ (d) => ctx.nitro.options.scanDirs.map((s) => join(s, d) + "/")
1476
+ ),
1477
+ extensions: DEFAULT_EXTENSIONS,
1478
+ try: true
1479
+ });
1480
+ if (ssrEntry) {
1481
+ ctx.pluginConfig.services.ssr = { entry: ssrEntry };
1482
+ ctx.nitro.logger.info(
1483
+ `Using \`${prettyPath(ssrEntry)}\` as vite ssr entry.`
1484
+ );
1485
+ }
1486
+ } else {
1487
+ let ssrEntry = getEntry(
1488
+ userConfig.environments.ssr.build?.rollupOptions?.input
1489
+ );
1490
+ if (typeof ssrEntry === "string") {
1491
+ ssrEntry = resolveModulePath(ssrEntry, {
1492
+ from: ctx.nitro.options.scanDirs,
1493
+ extensions: DEFAULT_EXTENSIONS,
1494
+ suffixes: ["", "/index"],
1495
+ try: true
1496
+ }) || ssrEntry;
1497
+ ctx.pluginConfig.services.ssr = { entry: ssrEntry };
1498
+ } else {
1499
+ throw new TypeError(`Invalid input type for SSR entry point.`);
1500
+ }
1501
+ }
1502
+ }
1503
+ if (!ctx.nitro.options.renderer?.entry && !ctx.nitro.options.renderer?.template && ctx.pluginConfig.services.ssr?.entry) {
1504
+ ctx.nitro.options.renderer ??= {};
1505
+ ctx.nitro.options.renderer.entry = resolve(
1506
+ runtimeDir,
1507
+ "internal/vite/ssr-renderer"
1508
+ );
1509
+ }
1510
+ const publicDistDir = ctx._publicDistDir = userConfig.build?.outDir || resolve(ctx.nitro.options.buildDir, "vite/public");
1511
+ ctx.nitro.options.publicAssets.push({
1512
+ dir: publicDistDir,
1513
+ maxAge: 0,
1514
+ baseURL: "/",
1515
+ fallthrough: true
1516
+ });
1517
+ if (!ctx.nitro.options.dev) {
1518
+ ctx.nitro.options.unenv.push({
1519
+ meta: { name: "nitro-vite" },
1520
+ polyfill: ["#nitro-vite-setup"]
1521
+ });
1522
+ }
1523
+ await ctx.nitro.hooks.callHook("build:before", ctx.nitro);
1524
+ ctx.rollupConfig = await getViteRollupConfig(ctx);
1525
+ if (ctx.nitro.options.dev) {
1526
+ await ctx.nitro.hooks.callHook(
1527
+ "rollup:before",
1528
+ ctx.nitro,
1529
+ ctx.rollupConfig.config
1530
+ );
1531
+ }
1532
+ if (ctx.nitro.options.dev && !ctx.devWorker) {
1533
+ ctx.devWorker = createDevWorker(ctx);
1534
+ }
1535
+ if (ctx.nitro.options.dev && !ctx.devApp) {
1536
+ ctx.devApp = new NitroDevApp(ctx.nitro);
1537
+ }
1538
+ }
968
1539
  function getEntry(input) {
969
1540
  if (typeof input === "string") {
970
1541
  return input;
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- export { d as copyPublicAssets, i as createNitro, D as listTasks, A as loadOptions, j as prepare, B as runTask } from './_chunks/index.mjs';
1
+ export { d as copyPublicAssets, k as createNitro, E as listTasks, B as loadOptions, j as prepare, D as runTask } from './_chunks/index.mjs';
2
2
  export { b as build, p as prerender, w as writeTypes } from './_chunks/index3.mjs';
3
3
  export { c as createDevServer } from './_chunks/server.mjs';
4
4
  import 'consola';
package/dist/presets.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { b as glob, Q as findNearestFile, h, R as readGitConfig, S as readPackageJSON, r as resolveModulePath, T as findFile, U as fileURLToPath, V as resolveCompatibilityDatesFromEnv, f as formatCompatibilityDate } from './_chunks/index.mjs';
1
+ import { b as glob, R as findNearestFile, h, S as readGitConfig, T as readPackageJSON, r as resolveModulePath, U as findFile, V as fileURLToPath, W as resolveCompatibilityDatesFromEnv, f as formatCompatibilityDate } from './_chunks/index.mjs';
2
2
  import { kebabCase } from 'scule';
3
3
  import { provider, isTest } from 'std-env';
4
4
  import { presetsDir, runtimeDir } from 'nitro/runtime/meta';
package/dist/vite.d.mts CHANGED
@@ -28,6 +28,11 @@ interface NitroPluginConfig {
28
28
  * @note This is unsafe if plugins rely on temporary files on the filesystem.
29
29
  */
30
30
  virtualBundle?: boolean;
31
+ /**
32
+ * @experimental Enable `?assets` import proposed by https://github.com/vitejs/vite/discussions/20913
33
+ * @default true
34
+ */
35
+ assetsImport?: boolean;
31
36
  };
32
37
  }
33
38
  interface ServiceConfig {
package/dist/vite.mjs CHANGED
@@ -66,3 +66,4 @@ import 'rendu';
66
66
  import 'vite';
67
67
  import 'get-port-please';
68
68
  import 'node:child_process';
69
+ import 'node:assert/strict';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nitro-nightly",
3
- "version": "3.0.1-20251023-220447-2d199369",
3
+ "version": "3.1.0-20251024-111905-4718bf73",
4
4
  "description": "Build and Deploy Universal JavaScript Servers",
5
5
  "homepage": "https://nitro.build",
6
6
  "repository": "nitrojs/nitro",
@@ -75,6 +75,7 @@
75
75
  "@azure/static-web-apps-cli": "^2.0.7",
76
76
  "@cloudflare/workers-types": "^4.20251014.0",
77
77
  "@deno/types": "^0.0.1",
78
+ "@hiogawa/vite-plugin-fullstack": "0.0.5",
78
79
  "@netlify/edge-functions": "^3.0.1",
79
80
  "@netlify/functions": "^5.0.1",
80
81
  "@rollup/plugin-alias": "^5.1.1",