@decocms/tanstack 7.71.3 → 7.71.5
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/package.json +4 -4
- package/src/vite/plugin.js +39 -7
- package/src/vite/plugin.test.js +114 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@decocms/tanstack",
|
|
3
|
-
"version": "7.71.
|
|
3
|
+
"version": "7.71.5",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Deco framework binding for TanStack Start + Cloudflare Workers",
|
|
6
6
|
"repository": {
|
|
@@ -27,9 +27,9 @@
|
|
|
27
27
|
"lint:unused": "knip"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@decocms/blocks": "7.71.
|
|
31
|
-
"@decocms/blocks-admin": "7.71.
|
|
32
|
-
"@decocms/blocks-cli": "7.71.
|
|
30
|
+
"@decocms/blocks": "7.71.5",
|
|
31
|
+
"@decocms/blocks-admin": "7.71.5",
|
|
32
|
+
"@decocms/blocks-cli": "7.71.5",
|
|
33
33
|
"@deco-cx/warp-node": "^0.3.16",
|
|
34
34
|
"fast-json-patch": "^3.1.0",
|
|
35
35
|
"ws": "^8.18.0"
|
package/src/vite/plugin.js
CHANGED
|
@@ -84,6 +84,12 @@ function resolveBuildHash() {
|
|
|
84
84
|
return Date.now().toString(36);
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
// Virtual id for meta.gen.json on the server: the real path wrapped so the id
|
|
88
|
+
// does NOT end in `.json` (Vite's `vite:json` would re-parse our JS output).
|
|
89
|
+
const META_GEN_PREFIX = "\0deco-meta:";
|
|
90
|
+
const META_GEN_SUFFIX = ".deco-meta.js";
|
|
91
|
+
const metaGenSsrId = (file) => META_GEN_PREFIX + file + META_GEN_SUFFIX;
|
|
92
|
+
|
|
87
93
|
// Bare-specifier stubs resolved by ID before Vite touches them.
|
|
88
94
|
/** @type {Record<string, string>} */
|
|
89
95
|
const CLIENT_STUBS = {
|
|
@@ -235,6 +241,32 @@ export function decoVitePlugin({ fastDeploy = "auto" } = {}) {
|
|
|
235
241
|
// SSR-only stubs — must be checked first since the client guard below
|
|
236
242
|
// returns undefined for everything that hasn't matched yet on SSR.
|
|
237
243
|
if (options?.ssr && SSR_STUBS[id]) return SSR_STUBS[id];
|
|
244
|
+
// meta.gen.json on the SERVER → a virtual id that does NOT end in
|
|
245
|
+
// `.json`. `load` below emits JS (`JSON.parse("...")`), and Vite's
|
|
246
|
+
// `vite:json` transform runs on every id matching `\.json$` regardless
|
|
247
|
+
// of who loaded it — it would try to JSON.parse that JS and fail the
|
|
248
|
+
// build ("Failed to parse JSON file").
|
|
249
|
+
if (options?.ssr && importer && id.endsWith("meta.gen.json")) {
|
|
250
|
+
if (id.startsWith(".") || path.isAbsolute(id)) {
|
|
251
|
+
const file = path.resolve(path.dirname(importer), id);
|
|
252
|
+
if (existsSync(file)) return metaGenSsrId(file);
|
|
253
|
+
// Absent (pre-generate-schema) — let Vite report it normally.
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
// Alias / bare specifier (`~/.deco/meta.gen.json`, tsconfig paths…):
|
|
257
|
+
// let the rest of the pipeline resolve it, then wrap — otherwise it
|
|
258
|
+
// falls through to vite:json and loses the JSON.parse optimization.
|
|
259
|
+
if (typeof this?.resolve === "function") {
|
|
260
|
+
return this.resolve(id, importer, { ...options, skipSelf: true }).then(
|
|
261
|
+
(r) =>
|
|
262
|
+
r && !r.external && r.id.endsWith("meta.gen.json") && existsSync(r.id)
|
|
263
|
+
? metaGenSsrId(r.id)
|
|
264
|
+
: undefined,
|
|
265
|
+
// A resolver in the chain threw — let Vite's normal resolution report it.
|
|
266
|
+
() => undefined,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
238
270
|
// Server builds keep the real modules.
|
|
239
271
|
if (options?.ssr) return undefined;
|
|
240
272
|
// Bare-specifier exact-match stubs (react-dom/server, node:stream, etc.).
|
|
@@ -294,12 +326,12 @@ export function decoVitePlugin({ fastDeploy = "auto" } = {}) {
|
|
|
294
326
|
// materializes the object graph at all.
|
|
295
327
|
//
|
|
296
328
|
// The client is stubbed in `resolveId` above and never reaches here.
|
|
297
|
-
if (id.
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
329
|
+
if (id.startsWith(META_GEN_PREFIX) && id.endsWith(META_GEN_SUFFIX)) {
|
|
330
|
+
const file = id.slice(META_GEN_PREFIX.length, -META_GEN_SUFFIX.length);
|
|
331
|
+
// Virtual id → Vite won't watch the file on its own.
|
|
332
|
+
this.addWatchFile?.(file);
|
|
333
|
+
const raw = readFileSync(file, "utf-8");
|
|
334
|
+
return `export default JSON.parse(${JSON.stringify(raw)});`;
|
|
303
335
|
}
|
|
304
336
|
|
|
305
337
|
// loaders.gen.ts — the site's invoke registry (`export const siteLoaders`).
|
|
@@ -588,7 +620,7 @@ export function decoVitePlugin({ fastDeploy = "auto" } = {}) {
|
|
|
588
620
|
console.log(`[deco] meta.gen.json updated (${Date.now() - start}ms)`);
|
|
589
621
|
// Invalidate the meta.gen.json module so SSR picks up fresh schema
|
|
590
622
|
const mod =
|
|
591
|
-
server.environments?.ssr?.moduleGraph?.getModuleById(schemaOutFile);
|
|
623
|
+
server.environments?.ssr?.moduleGraph?.getModuleById(metaGenSsrId(schemaOutFile));
|
|
592
624
|
if (mod) {
|
|
593
625
|
server.environments.ssr.moduleGraph.invalidateModule(mod);
|
|
594
626
|
}
|
package/src/vite/plugin.test.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
1
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
5
|
import { decoVitePlugin, generateSchemaArgs } from "./plugin.js";
|
|
3
6
|
|
|
@@ -122,3 +125,114 @@ describe("decoVitePlugin __DECO_BUILD_HASH__ injection", () => {
|
|
|
122
125
|
}
|
|
123
126
|
});
|
|
124
127
|
});
|
|
128
|
+
|
|
129
|
+
describe("decoVitePlugin meta.gen.json on SSR (regression: vite:json double-parse)", () => {
|
|
130
|
+
// Vite's json plugin transforms every `.json` id — even when another plugin
|
|
131
|
+
// already loaded it as JS — so the virtual id must not end in `.json`.
|
|
132
|
+
let dir;
|
|
133
|
+
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
dir = mkdtempSync(path.join(tmpdir(), "deco-meta-"));
|
|
136
|
+
mkdirSync(path.join(dir, ".deco"));
|
|
137
|
+
mkdirSync(path.join(dir, "src"));
|
|
138
|
+
writeFileSync(path.join(dir, ".deco", "meta.gen.json"), '{"major":1,"schema":{}}');
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
afterEach(() => {
|
|
142
|
+
rmSync(dir, { recursive: true, force: true });
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("resolves to a virtual id vite:json does not transform, and loads it as JSON.parse", () => {
|
|
146
|
+
const p = getPlugin();
|
|
147
|
+
const importer = path.join(dir, "src", "setup.ts");
|
|
148
|
+
const id = p.resolveId.call({}, "../.deco/meta.gen.json", importer, { ssr: true });
|
|
149
|
+
expect(id).toBeTypeOf("string");
|
|
150
|
+
expect(id.endsWith(".json")).toBe(false);
|
|
151
|
+
|
|
152
|
+
const watched = [];
|
|
153
|
+
const code = p.load.call({ addWatchFile: (f) => watched.push(f) }, id, { ssr: true });
|
|
154
|
+
expect(code).toBe(`export default JSON.parse(${JSON.stringify('{"major":1,"schema":{}}')});`);
|
|
155
|
+
expect(watched).toEqual([path.join(dir, ".deco", "meta.gen.json")]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it("gives each meta.gen.json its own module id (no shared state)", () => {
|
|
159
|
+
const p = getPlugin();
|
|
160
|
+
const other = mkdtempSync(path.join(tmpdir(), "deco-meta-other-"));
|
|
161
|
+
try {
|
|
162
|
+
writeFileSync(path.join(other, "meta.gen.json"), '{"major":2}');
|
|
163
|
+
const a = p.resolveId.call({}, "../.deco/meta.gen.json", path.join(dir, "src", "setup.ts"), {
|
|
164
|
+
ssr: true,
|
|
165
|
+
});
|
|
166
|
+
const b = p.resolveId.call({}, path.join(other, "meta.gen.json"), path.join(dir, "x.ts"), {
|
|
167
|
+
ssr: true,
|
|
168
|
+
});
|
|
169
|
+
expect(a).not.toBe(b);
|
|
170
|
+
// Loading in reverse order still returns each file's own content.
|
|
171
|
+
expect(p.load.call({}, b, { ssr: true })).toContain('\\"major\\":2');
|
|
172
|
+
expect(p.load.call({}, a, { ssr: true })).toContain('\\"major\\":1');
|
|
173
|
+
} finally {
|
|
174
|
+
rmSync(other, { recursive: true, force: true });
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("wraps aliased imports after the rest of the pipeline resolves them", async () => {
|
|
179
|
+
const p = getPlugin();
|
|
180
|
+
const file = path.join(dir, ".deco", "meta.gen.json");
|
|
181
|
+
const seen = [];
|
|
182
|
+
const ctx = {
|
|
183
|
+
resolve: async (spec, imp, opts) => {
|
|
184
|
+
seen.push([spec, opts.skipSelf]);
|
|
185
|
+
return { id: file };
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
const id = await p.resolveId.call(
|
|
189
|
+
ctx,
|
|
190
|
+
"~/.deco/meta.gen.json",
|
|
191
|
+
path.join(dir, "src", "setup.ts"),
|
|
192
|
+
{
|
|
193
|
+
ssr: true,
|
|
194
|
+
},
|
|
195
|
+
);
|
|
196
|
+
expect(id.endsWith(".json")).toBe(false);
|
|
197
|
+
expect(seen).toEqual([["~/.deco/meta.gen.json", true]]);
|
|
198
|
+
expect(p.load.call({}, id, { ssr: true })).toContain("JSON.parse(");
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("leaves an aliased import alone when it doesn't resolve to an existing file", async () => {
|
|
202
|
+
const p = getPlugin();
|
|
203
|
+
const ctx = { resolve: async () => null };
|
|
204
|
+
const id = await p.resolveId.call(ctx, "~/missing/meta.gen.json", path.join(dir, "x.ts"), {
|
|
205
|
+
ssr: true,
|
|
206
|
+
});
|
|
207
|
+
expect(id).toBeUndefined();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("falls through when a resolver in the chain throws for the alias", async () => {
|
|
211
|
+
const p = getPlugin();
|
|
212
|
+
const ctx = {
|
|
213
|
+
resolve: async () => {
|
|
214
|
+
throw new Error("exports map error");
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
const id = await p.resolveId.call(ctx, "~/x/meta.gen.json", path.join(dir, "x.ts"), {
|
|
218
|
+
ssr: true,
|
|
219
|
+
});
|
|
220
|
+
expect(id).toBeUndefined();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
it("falls through to Vite when meta.gen.json does not exist yet", () => {
|
|
224
|
+
const p = getPlugin();
|
|
225
|
+
const importer = path.join(dir, "src", "setup.ts");
|
|
226
|
+
expect(
|
|
227
|
+
p.resolveId.call({}, "../missing/meta.gen.json", importer, { ssr: true }),
|
|
228
|
+
).toBeUndefined();
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it("still stubs meta.gen.json on the client", () => {
|
|
232
|
+
const p = getPlugin();
|
|
233
|
+
const importer = path.join(dir, "src", "setup.ts");
|
|
234
|
+
expect(p.resolveId.call({}, "../.deco/meta.gen.json", importer, { ssr: false })).toBe(
|
|
235
|
+
"\0stub:meta-gen",
|
|
236
|
+
);
|
|
237
|
+
});
|
|
238
|
+
});
|