@decocms/start 0.29.3 → 0.30.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@decocms/start",
3
- "version": "0.29.3",
3
+ "version": "0.30.1",
4
4
  "type": "module",
5
5
  "description": "Deco framework for TanStack Start - CMS bridge, admin protocol, hooks, schema generation",
6
6
  "main": "./src/index.ts",
@@ -1,6 +1,13 @@
1
1
  #!/usr/bin/env tsx
2
2
  /**
3
- * Reads .deco/blocks/*.json and emits a TypeScript barrel.
3
+ * Reads .deco/blocks/*.json and emits:
4
+ * 1. blocks.gen.json — compact JSON data (the source of truth)
5
+ * 2. blocks.gen.ts — thin TypeScript re-export for editor tooling
6
+ *
7
+ * At runtime the Vite plugin (src/vite/plugin.js) intercepts `blocks.gen.ts`
8
+ * imports and replaces them with `JSON.parse(...)` of the .json file. This
9
+ * avoids Vite's SSR module runner hanging on large (13MB+) JS object literals
10
+ * and lets V8 use its fast JSON parser instead of the full JS parser.
4
11
  *
5
12
  * Usage (from site root):
6
13
  * npx tsx node_modules/@decocms/start/scripts/generate-blocks.ts
@@ -20,6 +27,7 @@ function arg(name: string, fallback: string): string {
20
27
 
21
28
  const blocksDir = path.resolve(process.cwd(), arg("blocks-dir", ".deco/blocks"));
22
29
  const outFile = path.resolve(process.cwd(), arg("out-file", "src/server/cms/blocks.gen.ts"));
30
+ const jsonFile = outFile.replace(/\.ts$/, ".json");
23
31
 
24
32
  function decodeBlockName(filename: string): string {
25
33
  let name = filename.replace(/\.json$/, "");
@@ -35,13 +43,20 @@ function decodeBlockName(filename: string): string {
35
43
  return name;
36
44
  }
37
45
 
46
+ const TS_STUB = [
47
+ "// Auto-generated — thin wrapper around blocks.gen.json.",
48
+ "// The Vite plugin replaces this at load time with JSON.parse(...).",
49
+ "// Do not edit manually.",
50
+ "",
51
+ "export const blocks: Record<string, any> = {};",
52
+ "",
53
+ ].join("\n");
54
+
38
55
  if (!fs.existsSync(blocksDir)) {
39
56
  console.warn(`Blocks directory not found: ${blocksDir} — generating empty barrel.`);
40
57
  fs.mkdirSync(path.dirname(outFile), { recursive: true });
41
- fs.writeFileSync(
42
- outFile,
43
- `// Auto-generated — no blocks found\nexport const blocks: Record<string, any> = {};\n`,
44
- );
58
+ fs.writeFileSync(jsonFile, "{}");
59
+ fs.writeFileSync(outFile, TS_STUB);
45
60
  process.exit(0);
46
61
  }
47
62
 
@@ -70,10 +85,16 @@ for (const [name, file] of Object.entries(blockFiles)) {
70
85
  }
71
86
  }
72
87
 
73
- const output = `// Auto-generated from .deco/blocks/*.json\n// Do not edit manually.\n\nexport const blocks: Record<string, any> = ${JSON.stringify(blocks, null, 2)};\n`;
74
-
75
88
  fs.mkdirSync(path.dirname(outFile), { recursive: true });
76
- fs.writeFileSync(outFile, output);
89
+
90
+ // 1. Compact JSON — the real data (no pretty-printing to save ~40% size)
91
+ const jsonStr = JSON.stringify(blocks);
92
+ fs.writeFileSync(jsonFile, jsonStr);
93
+
94
+ // 2. Thin TS wrapper — just for TypeScript tooling and as a Vite load target
95
+ fs.writeFileSync(outFile, TS_STUB);
96
+
97
+ const jsonSizeMB = (Buffer.byteLength(jsonStr) / 1_048_576).toFixed(1);
77
98
  console.log(
78
- `Generated ${Object.keys(blocks).length} blocks → ${path.relative(process.cwd(), outFile)}`,
99
+ `Generated ${Object.keys(blocks).length} blocks → ${path.relative(process.cwd(), jsonFile)} (${jsonSizeMB} MB)`,
79
100
  );
@@ -228,6 +228,33 @@ export const loadDeferredSection = createServerFn({ method: "POST" })
228
228
  return normalizeUrlsInObject(enriched);
229
229
  });
230
230
 
231
+ // ---------------------------------------------------------------------------
232
+ // Pre-wrapped deferred section loader for IntersectionObserver-based rendering
233
+ // ---------------------------------------------------------------------------
234
+
235
+ /**
236
+ * Convenience wrapper around `loadDeferredSection` that matches the
237
+ * `loadDeferredSectionFn` prop signature of `DecoPageRenderer`.
238
+ *
239
+ * Pass this directly to `<DecoPageRenderer loadDeferredSectionFn={deferredSectionLoader} />`
240
+ * to enable IntersectionObserver-based lazy loading of deferred sections.
241
+ */
242
+ export const deferredSectionLoader = async ({
243
+ component,
244
+ rawProps,
245
+ pagePath,
246
+ pageUrl,
247
+ }: {
248
+ component: string;
249
+ rawProps: Record<string, unknown>;
250
+ pagePath: string;
251
+ pageUrl?: string;
252
+ }): Promise<ResolvedSection | null> => {
253
+ return loadDeferredSection({
254
+ data: { component, rawProps, pagePath, pageUrl },
255
+ });
256
+ };
257
+
231
258
  // ---------------------------------------------------------------------------
232
259
  // Default pending component — shown during SPA navigation while loader runs
233
260
  // ---------------------------------------------------------------------------
@@ -8,6 +8,7 @@ export {
8
8
  type CmsRouteOptions,
9
9
  cmsHomeRouteConfig,
10
10
  cmsRouteConfig,
11
+ deferredSectionLoader,
11
12
  loadCmsHomePage,
12
13
  loadCmsPage,
13
14
  loadDeferredSection,
@@ -5,12 +5,21 @@
5
5
  * are eliminated from the browser bundle. This consolidates stubs that
6
6
  * every Deco site previously had to copy into its own vite.config.ts.
7
7
  *
8
+ * blocks.gen.ts handling:
9
+ * The CMS block registry can be 10MB+. Inlining it as a JS object literal
10
+ * causes Vite's SSR module runner to hang on dynamic imports (transport
11
+ * serialization bottleneck) and is slow to parse even with static imports
12
+ * (V8 full JS parser). Instead, generate-blocks.ts writes a .json data
13
+ * file, and this plugin intercepts the .ts import to return JSON.parse(...)
14
+ * — V8's JSON parser is 2-10x faster than the JS parser for large data.
15
+ *
8
16
  * Usage:
9
17
  * ```ts
10
18
  * import { decoVitePlugin } from "@decocms/start/vite";
11
19
  * export default defineConfig({ plugins: [decoVitePlugin(), ...] });
12
20
  * ```
13
21
  */
22
+ import { readFileSync, existsSync } from "node:fs";
14
23
 
15
24
  // Bare-specifier stubs resolved by ID before Vite touches them.
16
25
  /** @type {Record<string, string>} */
@@ -69,16 +78,45 @@ export function decoVitePlugin() {
69
78
  },
70
79
 
71
80
  load(id, options) {
72
- // blocks.gen.ts — the CMS block registry (often 500KB+ compiled).
73
- // Only the server needs it; the client receives pre-resolved sections.
74
- if (!options?.ssr && id.endsWith("blocks.gen.ts")) {
75
- return "export const blocks = {};";
81
+ // blocks.gen.ts — the CMS block registry (can be 10MB+).
82
+ if (id.endsWith("blocks.gen.ts")) {
83
+ // Client: stub — the browser receives pre-resolved sections.
84
+ if (!options?.ssr) {
85
+ return "export const blocks = {};";
86
+ }
87
+
88
+ // SSR: read .json sibling and emit JSON.parse(...) wrapper.
89
+ // This avoids the Vite SSR module runner hanging on large dynamic
90
+ // imports and lets V8 use its fast JSON parser (~2-10x vs object literal).
91
+ const jsonPath = id.replace(/\.ts$/, ".json");
92
+ if (existsSync(jsonPath)) {
93
+ const raw = readFileSync(jsonPath, "utf-8");
94
+ return `export const blocks = JSON.parse(${JSON.stringify(raw)});`;
95
+ }
96
+
97
+ // Fallback: if .json doesn't exist yet (pre-generate-blocks), let
98
+ // Vite load the .ts file normally (may contain inline data for
99
+ // backward-compatible sites that haven't regenerated).
76
100
  }
77
101
 
78
102
  // Virtual module stubs.
79
103
  return STUB_SOURCE[id];
80
104
  },
81
105
 
106
+ configureServer(server) {
107
+ // When blocks.gen.json changes on disk, invalidate the .ts module
108
+ // so Vite re-runs our load() hook with the fresh data.
109
+ server.watcher.on("change", (file) => {
110
+ if (file.endsWith("blocks.gen.json")) {
111
+ const tsId = file.replace(/\.json$/, ".ts");
112
+ const mod = server.environments?.ssr?.moduleGraph?.getModuleById(tsId);
113
+ if (mod) {
114
+ server.environments.ssr.moduleGraph.invalidateModule(mod);
115
+ }
116
+ }
117
+ });
118
+ },
119
+
82
120
  configEnvironment(name, env) {
83
121
  if (name === "ssr" || name === "client") {
84
122
  env.optimizeDeps = env.optimizeDeps || {};