@lovable.dev/vite-tanstack-config 2.15.1 → 2.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +184 -1
- package/dist/index.d.cts +6 -2
- package/dist/index.d.ts +6 -2
- package/dist/index.js +184 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -189,6 +189,76 @@ async function nitroSupportsDefaultPreset() {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
const LOVABLE_NITRO_PRESETS = ["cloudflare-module", "lovable-fetch-bundle"];
|
|
192
|
+
const ROOT_PRERENDER_PINS = {
|
|
193
|
+
crawlLinks: false,
|
|
194
|
+
failOnError: false,
|
|
195
|
+
maxRedirects: 0,
|
|
196
|
+
autoSubfolderIndex: true
|
|
197
|
+
};
|
|
198
|
+
const PAGE_PRERENDER_PINS = {
|
|
199
|
+
crawlLinks: ROOT_PRERENDER_PINS.crawlLinks,
|
|
200
|
+
autoSubfolderIndex: ROOT_PRERENDER_PINS.autoSubfolderIndex
|
|
201
|
+
};
|
|
202
|
+
function pinPrerenderOptions(source, pins, label) {
|
|
203
|
+
const result = { ...source };
|
|
204
|
+
for (const [key, value] of Object.entries(pins)) {
|
|
205
|
+
if (key in result && result[key] !== value) logWarning(`Ignoring ${label}.${key} inside the Lovable build environment; forcing ${JSON.stringify(value)}.`);
|
|
206
|
+
result[key] = value;
|
|
207
|
+
}
|
|
208
|
+
if ("outputPath" in result) {
|
|
209
|
+
logWarning(`Ignoring ${label}.outputPath inside the Lovable build environment; prerendered routes are always written to <route>/index.html.`);
|
|
210
|
+
delete result.outputPath;
|
|
211
|
+
}
|
|
212
|
+
return result;
|
|
213
|
+
}
|
|
214
|
+
function pinPrerenderForLovableBuild(userOptions) {
|
|
215
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
216
|
+
const rendered = /* @__PURE__ */ new Set();
|
|
217
|
+
const userPrerender = userOptions.prerender;
|
|
218
|
+
const prerender = pinPrerenderOptions(userPrerender, ROOT_PRERENDER_PINS, "prerender");
|
|
219
|
+
const userFilter = userPrerender?.filter;
|
|
220
|
+
prerender.filter = (page) => {
|
|
221
|
+
const keep = typeof userFilter === "function" ? userFilter(page) : true;
|
|
222
|
+
if (keep) attempted.add(page.path);
|
|
223
|
+
return keep;
|
|
224
|
+
};
|
|
225
|
+
const withRenderTracking = (userOnSuccess) => (ctx) => {
|
|
226
|
+
rendered.add(ctx.page.path);
|
|
227
|
+
return typeof userOnSuccess === "function" ? userOnSuccess(ctx) : void 0;
|
|
228
|
+
};
|
|
229
|
+
prerender.onSuccess = withRenderTracking(userPrerender?.onSuccess);
|
|
230
|
+
const startOptions = {
|
|
231
|
+
...userOptions,
|
|
232
|
+
prerender
|
|
233
|
+
};
|
|
234
|
+
if (Array.isArray(userOptions.pages)) startOptions.pages = userOptions.pages.map((page) => {
|
|
235
|
+
if (!page || typeof page !== "object") return page;
|
|
236
|
+
const entry = page;
|
|
237
|
+
if (!entry.prerender) return entry;
|
|
238
|
+
const pinned = pinPrerenderOptions(entry.prerender, PAGE_PRERENDER_PINS, `pages["${entry.path}"].prerender`);
|
|
239
|
+
if ("onSuccess" in pinned) pinned.onSuccess = withRenderTracking(pinned.onSuccess);
|
|
240
|
+
return {
|
|
241
|
+
...entry,
|
|
242
|
+
prerender: pinned
|
|
243
|
+
};
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
startOptions,
|
|
247
|
+
skipReporter: {
|
|
248
|
+
name: "lovable:prerender-skip-report",
|
|
249
|
+
apply: "build",
|
|
250
|
+
enforce: "post",
|
|
251
|
+
buildApp: {
|
|
252
|
+
order: "post",
|
|
253
|
+
async handler() {
|
|
254
|
+
const skipped = [...attempted].filter((route) => !rendered.has(route));
|
|
255
|
+
if (skipped.length === 0) return;
|
|
256
|
+
logWarning(`Prerender skipped ${skipped.length} route(s) that failed to render; they fall back to SSR: ${skipped.join(", ")}`);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
}
|
|
192
262
|
function lovableFetchBundlePreset() {
|
|
193
263
|
return {
|
|
194
264
|
entry: (0, node_url.fileURLToPath)(new URL("../runtime/fetch-entry.mjs", require("url").pathToFileURL(__filename).href)),
|
|
@@ -481,6 +551,107 @@ function stripRedundantNodejsCompatFlag() {
|
|
|
481
551
|
}
|
|
482
552
|
};
|
|
483
553
|
}
|
|
554
|
+
const NITRO_SERVER_ENTRY = "index.mjs";
|
|
555
|
+
const NITRO_WRANGLER_CONFIG = "wrangler.json";
|
|
556
|
+
const DEFAULT_SERVER_OUTPUT_DIR = "dist/server";
|
|
557
|
+
const DEFAULT_SERVER_ENTRY_NAME = "server";
|
|
558
|
+
const PRERENDER_PREVIEW_HOST = "127.0.0.1";
|
|
559
|
+
const TSS_PRERENDERING_ENV = "TSS_PRERENDERING";
|
|
560
|
+
const SHIM_MARKER = "// Generated by @lovable.dev/vite-tanstack-config for the prerender preview server.";
|
|
561
|
+
function prerenderShimSource(vars) {
|
|
562
|
+
return `${SHIM_MARKER}
|
|
563
|
+
import server from "./${NITRO_SERVER_ENTRY}";
|
|
564
|
+
|
|
565
|
+
const env = ${JSON.stringify(vars)};
|
|
566
|
+
const ctx = { waitUntil() {}, passThroughOnException() {}, props: {} };
|
|
567
|
+
|
|
568
|
+
export default {
|
|
569
|
+
fetch(request) {
|
|
570
|
+
// srvx's NodeRequest exposes \`ip\` as a getter-only accessor and nitro's
|
|
571
|
+
// cloudflare module handler assigns to it; shadow it with a writable one.
|
|
572
|
+
Object.defineProperty(request, "ip", { value: undefined, writable: true, configurable: true });
|
|
573
|
+
return server.fetch(request, env, ctx);
|
|
574
|
+
},
|
|
575
|
+
};
|
|
576
|
+
`;
|
|
577
|
+
}
|
|
578
|
+
function prerenderPreviewShim(state) {
|
|
579
|
+
let root = process.cwd();
|
|
580
|
+
let serverOutDir = DEFAULT_SERVER_OUTPUT_DIR;
|
|
581
|
+
let serverEntryName = DEFAULT_SERVER_ENTRY_NAME;
|
|
582
|
+
return {
|
|
583
|
+
name: "lovable-prerender-preview-shim",
|
|
584
|
+
apply: "build",
|
|
585
|
+
config(userConfig) {
|
|
586
|
+
const ssrBuild = userConfig.environments?.ssr?.build;
|
|
587
|
+
if (ssrBuild?.outDir) serverOutDir = ssrBuild.outDir;
|
|
588
|
+
const input = ssrBuild?.rolldownOptions?.input ?? ssrBuild?.rollupOptions?.input;
|
|
589
|
+
if (typeof input === "string") serverEntryName = input;
|
|
590
|
+
},
|
|
591
|
+
configResolved(config) {
|
|
592
|
+
root = config.root;
|
|
593
|
+
},
|
|
594
|
+
buildApp: {
|
|
595
|
+
order: "post",
|
|
596
|
+
handler: async () => {
|
|
597
|
+
const { access, readFile, writeFile } = await import("node:fs/promises");
|
|
598
|
+
const nodePath = await import("node:path");
|
|
599
|
+
const exists = async (candidate) => {
|
|
600
|
+
try {
|
|
601
|
+
await access(candidate);
|
|
602
|
+
return true;
|
|
603
|
+
} catch {
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
};
|
|
607
|
+
const outDir = nodePath.resolve(root, serverOutDir);
|
|
608
|
+
if (!await exists(nodePath.join(outDir, NITRO_SERVER_ENTRY))) return;
|
|
609
|
+
const basename = nodePath.basename(serverEntryName, nodePath.extname(serverEntryName));
|
|
610
|
+
if (!basename || basename === "index") {
|
|
611
|
+
logWarning(`Cannot prerender: the server entry "${serverEntryName}" would need a shim at ${DEFAULT_SERVER_OUTPUT_DIR}/index.js, which outranks nitro's own entry when publishing. Rename the entry to anything but \`index\`.`);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const target = nodePath.join(outDir, `${basename}.js`);
|
|
615
|
+
const occupant = await readFile(target, "utf8").catch(() => void 0);
|
|
616
|
+
if (occupant !== void 0 && !occupant.startsWith(SHIM_MARKER)) {
|
|
617
|
+
logWarning(`Cannot prerender: ${basename}.js already exists in ${serverOutDir} and was not written by this plugin.`);
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
let vars = {};
|
|
621
|
+
try {
|
|
622
|
+
const candidate = JSON.parse(await readFile(nodePath.join(outDir, NITRO_WRANGLER_CONFIG), "utf8"))?.vars;
|
|
623
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) vars = candidate;
|
|
624
|
+
} catch {}
|
|
625
|
+
await writeFile(target, prerenderShimSource(vars));
|
|
626
|
+
state.file = target;
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
function prerenderPreviewShimCleanup(state) {
|
|
632
|
+
return {
|
|
633
|
+
name: "lovable-prerender-preview-shim-cleanup",
|
|
634
|
+
apply: "build",
|
|
635
|
+
enforce: "post",
|
|
636
|
+
buildApp: {
|
|
637
|
+
order: "post",
|
|
638
|
+
handler: async () => {
|
|
639
|
+
if (!state.file) return;
|
|
640
|
+
const { rm } = await import("node:fs/promises");
|
|
641
|
+
await rm(state.file, { force: true });
|
|
642
|
+
state.file = void 0;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
function prerenderIsEnabled(startOptions) {
|
|
648
|
+
const spa = startOptions.spa;
|
|
649
|
+
if (spa && (spa.enabled ?? true)) return true;
|
|
650
|
+
const prerender = startOptions.prerender;
|
|
651
|
+
if (prerender?.enabled === false) return false;
|
|
652
|
+
const pages = Array.isArray(startOptions.pages) ? startOptions.pages : [];
|
|
653
|
+
return !!(prerender?.enabled ?? pages.some((page) => !!page?.prerender?.enabled));
|
|
654
|
+
}
|
|
484
655
|
function devSsrErrorLogger() {
|
|
485
656
|
let lastCapture;
|
|
486
657
|
const CAPTURE_TTL_MS = 5e3;
|
|
@@ -660,7 +831,11 @@ function defineConfig(configOrOptions = {}) {
|
|
|
660
831
|
specifiers: ["server-only"]
|
|
661
832
|
}
|
|
662
833
|
} }, options.tanstackStart ?? {});
|
|
663
|
-
|
|
834
|
+
const prerenderEnabled = prerenderIsEnabled(tanstackStartOptions);
|
|
835
|
+
if (command === "build" && isSandbox) {
|
|
836
|
+
const { startOptions, skipReporter } = pinPrerenderForLovableBuild(tanstackStartOptions);
|
|
837
|
+
internalPlugins.push(tanstackStart(startOptions), skipReporter);
|
|
838
|
+
} else internalPlugins.push(tanstackStart(tanstackStartOptions));
|
|
664
839
|
const explicitNitro = options.nitro === true || typeof options.nitro === "object" && options.nitro !== null;
|
|
665
840
|
if (options.nitro !== false && command === "build") {
|
|
666
841
|
let nitroMod;
|
|
@@ -709,6 +884,10 @@ function defineConfig(configOrOptions = {}) {
|
|
|
709
884
|
}
|
|
710
885
|
} else if (!nitroOpts.preset && !await nitroSupportsDefaultPreset()) logWarning("The installed `nitro` predates the `defaultPreset` option (3.0.260603-beta), so the Cloudflare fallback is ignored and a zero-config build may target Node instead. Upgrade nitro to >=3.0.260603-beta, or set an explicit `nitro: { preset: \"cloudflare-module\" }`.");
|
|
711
886
|
internalPlugins.push(nitro(nitroOpts));
|
|
887
|
+
if (prerenderEnabled) {
|
|
888
|
+
const shimState = {};
|
|
889
|
+
internalPlugins.push(prerenderPreviewShim(shimState), prerenderPreviewShimCleanup(shimState));
|
|
890
|
+
}
|
|
712
891
|
if (isSandbox) internalPlugins.push(stripRedundantNodejsCompatFlag());
|
|
713
892
|
}
|
|
714
893
|
}
|
|
@@ -784,6 +963,10 @@ function defineConfig(configOrOptions = {}) {
|
|
|
784
963
|
host: "::",
|
|
785
964
|
port: 8080
|
|
786
965
|
} }, config);
|
|
966
|
+
if (env.isPreview && prerenderEnabled && process.env[TSS_PRERENDERING_ENV] === "true") config = (0, vite.mergeConfig)({ preview: {
|
|
967
|
+
host: PRERENDER_PREVIEW_HOST,
|
|
968
|
+
port: 0
|
|
969
|
+
} }, config);
|
|
787
970
|
if (isSandbox) return config;
|
|
788
971
|
return applyWatchDebounceDefaults(config);
|
|
789
972
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -12,6 +12,10 @@ import { ConfigEnv, Plugin, PluginOption, UserConfig, UserConfigFn, UserConfigFn
|
|
|
12
12
|
declare function lovableAssetsProxyPlugin(): Plugin;
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/index.d.ts
|
|
15
|
+
type PrerenderPage = {
|
|
16
|
+
path: string;
|
|
17
|
+
prerender?: Record<string, unknown>;
|
|
18
|
+
};
|
|
15
19
|
declare function stripRedundantNodejsCompatFlag(): Plugin;
|
|
16
20
|
interface LovableViteTanstackOptions {
|
|
17
21
|
plugins?: PluginOption[];
|
|
@@ -52,7 +56,7 @@ interface LovableViteTanstackOptions {
|
|
|
52
56
|
deployConfig?: boolean;
|
|
53
57
|
};
|
|
54
58
|
} | false;
|
|
55
|
-
/** Options forwarded to tanstackStart(). */
|
|
59
|
+
/** Options forwarded to tanstackStart(). Inside a Lovable build the prerender output shape is pinned. */
|
|
56
60
|
tanstackStart?: Record<string, unknown>;
|
|
57
61
|
/** Options forwarded to viteReact(). */
|
|
58
62
|
react?: Record<string, unknown>;
|
|
@@ -68,4 +72,4 @@ declare function defineConfig(config: UserConfigFnPromise): (env: ConfigEnv) =>
|
|
|
68
72
|
declare function defineConfig(config: UserConfigFn): (env: ConfigEnv) => Promise<UserConfig>;
|
|
69
73
|
declare function defineConfig(options?: LovableViteTanstackOptions): (env: ConfigEnv) => Promise<UserConfig>;
|
|
70
74
|
//#endregion
|
|
71
|
-
export { LovableViteTanstackOptions, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
|
|
75
|
+
export { LovableViteTanstackOptions, PrerenderPage, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ import { ConfigEnv, Plugin, PluginOption, UserConfig, UserConfigFn, UserConfigFn
|
|
|
12
12
|
declare function lovableAssetsProxyPlugin(): Plugin;
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/index.d.ts
|
|
15
|
+
type PrerenderPage = {
|
|
16
|
+
path: string;
|
|
17
|
+
prerender?: Record<string, unknown>;
|
|
18
|
+
};
|
|
15
19
|
declare function stripRedundantNodejsCompatFlag(): Plugin;
|
|
16
20
|
interface LovableViteTanstackOptions {
|
|
17
21
|
plugins?: PluginOption[];
|
|
@@ -52,7 +56,7 @@ interface LovableViteTanstackOptions {
|
|
|
52
56
|
deployConfig?: boolean;
|
|
53
57
|
};
|
|
54
58
|
} | false;
|
|
55
|
-
/** Options forwarded to tanstackStart(). */
|
|
59
|
+
/** Options forwarded to tanstackStart(). Inside a Lovable build the prerender output shape is pinned. */
|
|
56
60
|
tanstackStart?: Record<string, unknown>;
|
|
57
61
|
/** Options forwarded to viteReact(). */
|
|
58
62
|
react?: Record<string, unknown>;
|
|
@@ -68,4 +72,4 @@ declare function defineConfig(config: UserConfigFnPromise): (env: ConfigEnv) =>
|
|
|
68
72
|
declare function defineConfig(config: UserConfigFn): (env: ConfigEnv) => Promise<UserConfig>;
|
|
69
73
|
declare function defineConfig(options?: LovableViteTanstackOptions): (env: ConfigEnv) => Promise<UserConfig>;
|
|
70
74
|
//#endregion
|
|
71
|
-
export { LovableViteTanstackOptions, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
|
|
75
|
+
export { LovableViteTanstackOptions, PrerenderPage, defineConfig, lovableAssetsProxyPlugin, stripRedundantNodejsCompatFlag };
|
package/dist/index.js
CHANGED
|
@@ -165,6 +165,76 @@ async function nitroSupportsDefaultPreset() {
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
const LOVABLE_NITRO_PRESETS = ["cloudflare-module", "lovable-fetch-bundle"];
|
|
168
|
+
const ROOT_PRERENDER_PINS = {
|
|
169
|
+
crawlLinks: false,
|
|
170
|
+
failOnError: false,
|
|
171
|
+
maxRedirects: 0,
|
|
172
|
+
autoSubfolderIndex: true
|
|
173
|
+
};
|
|
174
|
+
const PAGE_PRERENDER_PINS = {
|
|
175
|
+
crawlLinks: ROOT_PRERENDER_PINS.crawlLinks,
|
|
176
|
+
autoSubfolderIndex: ROOT_PRERENDER_PINS.autoSubfolderIndex
|
|
177
|
+
};
|
|
178
|
+
function pinPrerenderOptions(source, pins, label) {
|
|
179
|
+
const result = { ...source };
|
|
180
|
+
for (const [key, value] of Object.entries(pins)) {
|
|
181
|
+
if (key in result && result[key] !== value) logWarning(`Ignoring ${label}.${key} inside the Lovable build environment; forcing ${JSON.stringify(value)}.`);
|
|
182
|
+
result[key] = value;
|
|
183
|
+
}
|
|
184
|
+
if ("outputPath" in result) {
|
|
185
|
+
logWarning(`Ignoring ${label}.outputPath inside the Lovable build environment; prerendered routes are always written to <route>/index.html.`);
|
|
186
|
+
delete result.outputPath;
|
|
187
|
+
}
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
function pinPrerenderForLovableBuild(userOptions) {
|
|
191
|
+
const attempted = /* @__PURE__ */ new Set();
|
|
192
|
+
const rendered = /* @__PURE__ */ new Set();
|
|
193
|
+
const userPrerender = userOptions.prerender;
|
|
194
|
+
const prerender = pinPrerenderOptions(userPrerender, ROOT_PRERENDER_PINS, "prerender");
|
|
195
|
+
const userFilter = userPrerender?.filter;
|
|
196
|
+
prerender.filter = (page) => {
|
|
197
|
+
const keep = typeof userFilter === "function" ? userFilter(page) : true;
|
|
198
|
+
if (keep) attempted.add(page.path);
|
|
199
|
+
return keep;
|
|
200
|
+
};
|
|
201
|
+
const withRenderTracking = (userOnSuccess) => (ctx) => {
|
|
202
|
+
rendered.add(ctx.page.path);
|
|
203
|
+
return typeof userOnSuccess === "function" ? userOnSuccess(ctx) : void 0;
|
|
204
|
+
};
|
|
205
|
+
prerender.onSuccess = withRenderTracking(userPrerender?.onSuccess);
|
|
206
|
+
const startOptions = {
|
|
207
|
+
...userOptions,
|
|
208
|
+
prerender
|
|
209
|
+
};
|
|
210
|
+
if (Array.isArray(userOptions.pages)) startOptions.pages = userOptions.pages.map((page) => {
|
|
211
|
+
if (!page || typeof page !== "object") return page;
|
|
212
|
+
const entry = page;
|
|
213
|
+
if (!entry.prerender) return entry;
|
|
214
|
+
const pinned = pinPrerenderOptions(entry.prerender, PAGE_PRERENDER_PINS, `pages["${entry.path}"].prerender`);
|
|
215
|
+
if ("onSuccess" in pinned) pinned.onSuccess = withRenderTracking(pinned.onSuccess);
|
|
216
|
+
return {
|
|
217
|
+
...entry,
|
|
218
|
+
prerender: pinned
|
|
219
|
+
};
|
|
220
|
+
});
|
|
221
|
+
return {
|
|
222
|
+
startOptions,
|
|
223
|
+
skipReporter: {
|
|
224
|
+
name: "lovable:prerender-skip-report",
|
|
225
|
+
apply: "build",
|
|
226
|
+
enforce: "post",
|
|
227
|
+
buildApp: {
|
|
228
|
+
order: "post",
|
|
229
|
+
async handler() {
|
|
230
|
+
const skipped = [...attempted].filter((route) => !rendered.has(route));
|
|
231
|
+
if (skipped.length === 0) return;
|
|
232
|
+
logWarning(`Prerender skipped ${skipped.length} route(s) that failed to render; they fall back to SSR: ${skipped.join(", ")}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
};
|
|
237
|
+
}
|
|
168
238
|
function lovableFetchBundlePreset() {
|
|
169
239
|
return {
|
|
170
240
|
entry: fileURLToPath(new URL("../runtime/fetch-entry.mjs", import.meta.url)),
|
|
@@ -457,6 +527,107 @@ function stripRedundantNodejsCompatFlag() {
|
|
|
457
527
|
}
|
|
458
528
|
};
|
|
459
529
|
}
|
|
530
|
+
const NITRO_SERVER_ENTRY = "index.mjs";
|
|
531
|
+
const NITRO_WRANGLER_CONFIG = "wrangler.json";
|
|
532
|
+
const DEFAULT_SERVER_OUTPUT_DIR = "dist/server";
|
|
533
|
+
const DEFAULT_SERVER_ENTRY_NAME = "server";
|
|
534
|
+
const PRERENDER_PREVIEW_HOST = "127.0.0.1";
|
|
535
|
+
const TSS_PRERENDERING_ENV = "TSS_PRERENDERING";
|
|
536
|
+
const SHIM_MARKER = "// Generated by @lovable.dev/vite-tanstack-config for the prerender preview server.";
|
|
537
|
+
function prerenderShimSource(vars) {
|
|
538
|
+
return `${SHIM_MARKER}
|
|
539
|
+
import server from "./${NITRO_SERVER_ENTRY}";
|
|
540
|
+
|
|
541
|
+
const env = ${JSON.stringify(vars)};
|
|
542
|
+
const ctx = { waitUntil() {}, passThroughOnException() {}, props: {} };
|
|
543
|
+
|
|
544
|
+
export default {
|
|
545
|
+
fetch(request) {
|
|
546
|
+
// srvx's NodeRequest exposes \`ip\` as a getter-only accessor and nitro's
|
|
547
|
+
// cloudflare module handler assigns to it; shadow it with a writable one.
|
|
548
|
+
Object.defineProperty(request, "ip", { value: undefined, writable: true, configurable: true });
|
|
549
|
+
return server.fetch(request, env, ctx);
|
|
550
|
+
},
|
|
551
|
+
};
|
|
552
|
+
`;
|
|
553
|
+
}
|
|
554
|
+
function prerenderPreviewShim(state) {
|
|
555
|
+
let root = process.cwd();
|
|
556
|
+
let serverOutDir = DEFAULT_SERVER_OUTPUT_DIR;
|
|
557
|
+
let serverEntryName = DEFAULT_SERVER_ENTRY_NAME;
|
|
558
|
+
return {
|
|
559
|
+
name: "lovable-prerender-preview-shim",
|
|
560
|
+
apply: "build",
|
|
561
|
+
config(userConfig) {
|
|
562
|
+
const ssrBuild = userConfig.environments?.ssr?.build;
|
|
563
|
+
if (ssrBuild?.outDir) serverOutDir = ssrBuild.outDir;
|
|
564
|
+
const input = ssrBuild?.rolldownOptions?.input ?? ssrBuild?.rollupOptions?.input;
|
|
565
|
+
if (typeof input === "string") serverEntryName = input;
|
|
566
|
+
},
|
|
567
|
+
configResolved(config) {
|
|
568
|
+
root = config.root;
|
|
569
|
+
},
|
|
570
|
+
buildApp: {
|
|
571
|
+
order: "post",
|
|
572
|
+
handler: async () => {
|
|
573
|
+
const { access, readFile, writeFile } = await import("node:fs/promises");
|
|
574
|
+
const nodePath = await import("node:path");
|
|
575
|
+
const exists = async (candidate) => {
|
|
576
|
+
try {
|
|
577
|
+
await access(candidate);
|
|
578
|
+
return true;
|
|
579
|
+
} catch {
|
|
580
|
+
return false;
|
|
581
|
+
}
|
|
582
|
+
};
|
|
583
|
+
const outDir = nodePath.resolve(root, serverOutDir);
|
|
584
|
+
if (!await exists(nodePath.join(outDir, NITRO_SERVER_ENTRY))) return;
|
|
585
|
+
const basename = nodePath.basename(serverEntryName, nodePath.extname(serverEntryName));
|
|
586
|
+
if (!basename || basename === "index") {
|
|
587
|
+
logWarning(`Cannot prerender: the server entry "${serverEntryName}" would need a shim at ${DEFAULT_SERVER_OUTPUT_DIR}/index.js, which outranks nitro's own entry when publishing. Rename the entry to anything but \`index\`.`);
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
const target = nodePath.join(outDir, `${basename}.js`);
|
|
591
|
+
const occupant = await readFile(target, "utf8").catch(() => void 0);
|
|
592
|
+
if (occupant !== void 0 && !occupant.startsWith(SHIM_MARKER)) {
|
|
593
|
+
logWarning(`Cannot prerender: ${basename}.js already exists in ${serverOutDir} and was not written by this plugin.`);
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
let vars = {};
|
|
597
|
+
try {
|
|
598
|
+
const candidate = JSON.parse(await readFile(nodePath.join(outDir, NITRO_WRANGLER_CONFIG), "utf8"))?.vars;
|
|
599
|
+
if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) vars = candidate;
|
|
600
|
+
} catch {}
|
|
601
|
+
await writeFile(target, prerenderShimSource(vars));
|
|
602
|
+
state.file = target;
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function prerenderPreviewShimCleanup(state) {
|
|
608
|
+
return {
|
|
609
|
+
name: "lovable-prerender-preview-shim-cleanup",
|
|
610
|
+
apply: "build",
|
|
611
|
+
enforce: "post",
|
|
612
|
+
buildApp: {
|
|
613
|
+
order: "post",
|
|
614
|
+
handler: async () => {
|
|
615
|
+
if (!state.file) return;
|
|
616
|
+
const { rm } = await import("node:fs/promises");
|
|
617
|
+
await rm(state.file, { force: true });
|
|
618
|
+
state.file = void 0;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
};
|
|
622
|
+
}
|
|
623
|
+
function prerenderIsEnabled(startOptions) {
|
|
624
|
+
const spa = startOptions.spa;
|
|
625
|
+
if (spa && (spa.enabled ?? true)) return true;
|
|
626
|
+
const prerender = startOptions.prerender;
|
|
627
|
+
if (prerender?.enabled === false) return false;
|
|
628
|
+
const pages = Array.isArray(startOptions.pages) ? startOptions.pages : [];
|
|
629
|
+
return !!(prerender?.enabled ?? pages.some((page) => !!page?.prerender?.enabled));
|
|
630
|
+
}
|
|
460
631
|
function devSsrErrorLogger() {
|
|
461
632
|
let lastCapture;
|
|
462
633
|
const CAPTURE_TTL_MS = 5e3;
|
|
@@ -636,7 +807,11 @@ function defineConfig(configOrOptions = {}) {
|
|
|
636
807
|
specifiers: ["server-only"]
|
|
637
808
|
}
|
|
638
809
|
} }, options.tanstackStart ?? {});
|
|
639
|
-
|
|
810
|
+
const prerenderEnabled = prerenderIsEnabled(tanstackStartOptions);
|
|
811
|
+
if (command === "build" && isSandbox) {
|
|
812
|
+
const { startOptions, skipReporter } = pinPrerenderForLovableBuild(tanstackStartOptions);
|
|
813
|
+
internalPlugins.push(tanstackStart(startOptions), skipReporter);
|
|
814
|
+
} else internalPlugins.push(tanstackStart(tanstackStartOptions));
|
|
640
815
|
const explicitNitro = options.nitro === true || typeof options.nitro === "object" && options.nitro !== null;
|
|
641
816
|
if (options.nitro !== false && command === "build") {
|
|
642
817
|
let nitroMod;
|
|
@@ -685,6 +860,10 @@ function defineConfig(configOrOptions = {}) {
|
|
|
685
860
|
}
|
|
686
861
|
} else if (!nitroOpts.preset && !await nitroSupportsDefaultPreset()) logWarning("The installed `nitro` predates the `defaultPreset` option (3.0.260603-beta), so the Cloudflare fallback is ignored and a zero-config build may target Node instead. Upgrade nitro to >=3.0.260603-beta, or set an explicit `nitro: { preset: \"cloudflare-module\" }`.");
|
|
687
862
|
internalPlugins.push(nitro(nitroOpts));
|
|
863
|
+
if (prerenderEnabled) {
|
|
864
|
+
const shimState = {};
|
|
865
|
+
internalPlugins.push(prerenderPreviewShim(shimState), prerenderPreviewShimCleanup(shimState));
|
|
866
|
+
}
|
|
688
867
|
if (isSandbox) internalPlugins.push(stripRedundantNodejsCompatFlag());
|
|
689
868
|
}
|
|
690
869
|
}
|
|
@@ -760,6 +939,10 @@ function defineConfig(configOrOptions = {}) {
|
|
|
760
939
|
host: "::",
|
|
761
940
|
port: 8080
|
|
762
941
|
} }, config);
|
|
942
|
+
if (env.isPreview && prerenderEnabled && process.env[TSS_PRERENDERING_ENV] === "true") config = mergeConfig({ preview: {
|
|
943
|
+
host: PRERENDER_PREVIEW_HOST,
|
|
944
|
+
port: 0
|
|
945
|
+
} }, config);
|
|
763
946
|
if (isSandbox) return config;
|
|
764
947
|
return applyWatchDebounceDefaults(config);
|
|
765
948
|
};
|