@nextsparkjs/core 0.1.0-beta.133 → 0.1.0-beta.135
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/components/public/pageBuilder/PageRenderer.d.ts +12 -7
- package/dist/components/public/pageBuilder/PageRenderer.d.ts.map +1 -1
- package/dist/components/public/pageBuilder/PageRenderer.js +5 -4
- package/dist/lib/blocks/loader.d.ts +15 -3
- package/dist/lib/blocks/loader.d.ts.map +1 -1
- package/dist/lib/blocks/loader.js +9 -2
- package/dist/styles/classes.json +1 -1
- package/package.json +2 -2
- package/scripts/build/registry/generators/block-registry.mjs +27 -22
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* PageRenderer Component
|
|
2
|
+
* PageRenderer Component — Async Server Component with RSC Streaming
|
|
3
3
|
*
|
|
4
|
-
* Renders pages from the Page Builder
|
|
5
|
-
*
|
|
4
|
+
* Renders pages from the Page Builder using async dynamic imports
|
|
5
|
+
* per block, wrapped in Suspense boundaries for streaming SSR.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* How it works:
|
|
8
|
+
* 1. Each block is loaded via `loadBlockSSR(slug)` — an async import
|
|
9
|
+
* 2. React Server Components resolve the import on the server
|
|
10
|
+
* 3. Suspense boundaries let React stream HTML progressively
|
|
11
|
+
* 4. Client receives complete HTML (zero CLS, SEO-safe)
|
|
12
|
+
* 5. Only JS chunks for blocks actually on the page are sent to client
|
|
13
|
+
*
|
|
14
|
+
* This gives per-block code splitting WITHOUT the CLS issues of next/dynamic,
|
|
15
|
+
* because the server fully resolves each block before flushing the HTML.
|
|
11
16
|
*
|
|
12
17
|
* @module core/components/public/pageBuilder
|
|
13
18
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PageRenderer.d.ts","sourceRoot":"","sources":["../../../../src/components/public/pageBuilder/PageRenderer.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"PageRenderer.d.ts","sourceRoot":"","sources":["../../../../src/components/public/pageBuilder/PageRenderer.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAqC1D,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAA;QACV,KAAK,EAAE,MAAM,CAAA;QACb,IAAI,EAAE,MAAM,CAAA;QACZ,MAAM,EAAE,aAAa,EAAE,CAAA;QACvB,MAAM,EAAE,MAAM,CAAA;KACf,CAAA;CACF;AAED,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE,iBAAiB,2CAiCvD"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Suspense } from "react";
|
|
3
|
+
import { loadBlockSSR, normalizeBlockProps } from "../../../lib/blocks/loader.js";
|
|
3
4
|
function BlockError({ blockSlug }) {
|
|
4
5
|
return /* @__PURE__ */ jsx("div", { className: "w-full py-12 px-4 bg-destructive/10 border border-destructive/20 rounded", children: /* @__PURE__ */ jsxs("div", { className: "max-w-7xl mx-auto text-center", children: [
|
|
5
6
|
/* @__PURE__ */ jsxs("p", { className: "text-destructive", children: [
|
|
@@ -9,8 +10,8 @@ function BlockError({ blockSlug }) {
|
|
|
9
10
|
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mt-2", children: "This block may not be available or there was an error rendering it." })
|
|
10
11
|
] }) });
|
|
11
12
|
}
|
|
12
|
-
function BlockRenderer({ block }) {
|
|
13
|
-
const BlockComponent =
|
|
13
|
+
async function BlockRenderer({ block }) {
|
|
14
|
+
const BlockComponent = await loadBlockSSR(block.blockSlug);
|
|
14
15
|
if (!BlockComponent) {
|
|
15
16
|
console.warn(`Block component not found for slug: ${block.blockSlug}`);
|
|
16
17
|
return /* @__PURE__ */ jsx(BlockError, { blockSlug: block.blockSlug });
|
|
@@ -32,7 +33,7 @@ function PageRenderer({ page }) {
|
|
|
32
33
|
className: "@container w-full",
|
|
33
34
|
"data-block-id": block.id,
|
|
34
35
|
"data-block-slug": block.blockSlug,
|
|
35
|
-
children: /* @__PURE__ */ jsx(BlockRenderer, { block })
|
|
36
|
+
children: /* @__PURE__ */ jsx(Suspense, { children: /* @__PURE__ */ jsx(BlockRenderer, { block }) })
|
|
36
37
|
},
|
|
37
38
|
block.id
|
|
38
39
|
)) });
|
|
@@ -23,14 +23,26 @@ export declare function getBlockComponents(): Record<string, BlockComponent>;
|
|
|
23
23
|
*/
|
|
24
24
|
export declare function getBlockComponent(slug: string): BlockComponent | undefined;
|
|
25
25
|
/**
|
|
26
|
-
*
|
|
27
|
-
* Use for public page rendering where no-JS SSR is required
|
|
26
|
+
* @deprecated Use loadBlockSSR for async Server Component rendering
|
|
28
27
|
*/
|
|
29
28
|
export declare function getBlockComponentsSSR(): Record<string, BlockComponent>;
|
|
30
29
|
/**
|
|
31
|
-
*
|
|
30
|
+
* @deprecated Use loadBlockSSR for async Server Component rendering
|
|
32
31
|
*/
|
|
33
32
|
export declare function getBlockComponentSSR(slug: string): BlockComponent | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Async block loader for RSC streaming.
|
|
35
|
+
*
|
|
36
|
+
* Dynamically imports a block component by slug. Used by PageRenderer
|
|
37
|
+
* (async Server Component) with Suspense for:
|
|
38
|
+
* - Per-block code splitting (only used blocks ship JS to client)
|
|
39
|
+
* - Streaming SSR (server flushes HTML progressively)
|
|
40
|
+
* - Zero CLS (HTML complete before client JS runs)
|
|
41
|
+
*
|
|
42
|
+
* @param slug - Block slug (e.g., 'hero', 'accordion')
|
|
43
|
+
* @returns Resolved block component, or undefined if not found
|
|
44
|
+
*/
|
|
45
|
+
export declare function loadBlockSSR(slug: string): Promise<BlockComponent | undefined>;
|
|
34
46
|
/**
|
|
35
47
|
* Check if a block exists in the registry
|
|
36
48
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAIrC,KAAK,cAAc,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;AAExC;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/lib/blocks/loader.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,OAAO,CAAA;AAIrC,KAAK,cAAc,GAAG,aAAa,CAAC,GAAG,CAAC,CAAA;AAExC;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAEnE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAEtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAKpF;AAED;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgD3F"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { BLOCK_COMPONENTS, BLOCK_COMPONENTS_SSR } from "@nextsparkjs/registries/block-registry";
|
|
1
|
+
import { BLOCK_COMPONENTS, BLOCK_COMPONENTS_SSR, BLOCK_IMPORTS_SSR } from "@nextsparkjs/registries/block-registry";
|
|
2
2
|
function getBlockComponents() {
|
|
3
3
|
return BLOCK_COMPONENTS;
|
|
4
4
|
}
|
|
@@ -11,8 +11,14 @@ function getBlockComponentsSSR() {
|
|
|
11
11
|
function getBlockComponentSSR(slug) {
|
|
12
12
|
return BLOCK_COMPONENTS_SSR[slug];
|
|
13
13
|
}
|
|
14
|
+
async function loadBlockSSR(slug) {
|
|
15
|
+
const importFn = BLOCK_IMPORTS_SSR[slug];
|
|
16
|
+
if (!importFn) return void 0;
|
|
17
|
+
const mod = await importFn();
|
|
18
|
+
return mod.default;
|
|
19
|
+
}
|
|
14
20
|
function hasBlock(slug) {
|
|
15
|
-
return slug in BLOCK_COMPONENTS;
|
|
21
|
+
return slug in BLOCK_COMPONENTS || slug in BLOCK_IMPORTS_SSR;
|
|
16
22
|
}
|
|
17
23
|
function normalizeBlockProps(props) {
|
|
18
24
|
if (!props || typeof props !== "object" || Array.isArray(props)) {
|
|
@@ -58,5 +64,6 @@ export {
|
|
|
58
64
|
getBlockComponents,
|
|
59
65
|
getBlockComponentsSSR,
|
|
60
66
|
hasBlock,
|
|
67
|
+
loadBlockSSR,
|
|
61
68
|
normalizeBlockProps
|
|
62
69
|
};
|
package/dist/styles/classes.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nextsparkjs/core",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.135",
|
|
4
4
|
"description": "NextSpark - The complete SaaS framework for Next.js",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "NextSpark <hello@nextspark.dev>",
|
|
@@ -462,7 +462,7 @@
|
|
|
462
462
|
"tailwind-merge": "^3.3.1",
|
|
463
463
|
"uuid": "^13.0.0",
|
|
464
464
|
"zod": "^4.1.5",
|
|
465
|
-
"@nextsparkjs/testing": "0.1.0-beta.
|
|
465
|
+
"@nextsparkjs/testing": "0.1.0-beta.135"
|
|
466
466
|
},
|
|
467
467
|
"scripts": {
|
|
468
468
|
"postinstall": "node scripts/postinstall.mjs || true",
|
|
@@ -47,10 +47,11 @@ export const BLOCK_CATEGORIES: string[] = []
|
|
|
47
47
|
|
|
48
48
|
export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<unknown>>> = {}
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
51
|
+
export type BlockImportFn = () => Promise<{ default: React.ComponentType<any> }>
|
|
52
|
+
export const BLOCK_IMPORTS_SSR: Record<string, BlockImportFn> = {}
|
|
53
|
+
|
|
54
|
+
/** @deprecated Use BLOCK_IMPORTS_SSR */
|
|
54
55
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
56
|
export const BLOCK_COMPONENTS_SSR: Record<string, React.ComponentType<any>> = {}
|
|
56
57
|
|
|
@@ -66,8 +67,6 @@ export const BLOCK_METADATA = {
|
|
|
66
67
|
|
|
67
68
|
// Import field definitions and examples (if they exist)
|
|
68
69
|
const imports = []
|
|
69
|
-
// Direct component imports for SSR (no React.lazy)
|
|
70
|
-
const ssrImports = []
|
|
71
70
|
|
|
72
71
|
blocks.forEach(block => {
|
|
73
72
|
const slugVar = block.slug.replace(/-/g, '_')
|
|
@@ -75,9 +74,6 @@ export const BLOCK_METADATA = {
|
|
|
75
74
|
// Always import field definitions
|
|
76
75
|
imports.push(`import { fieldDefinitions as ${slugVar}_fields } from '${block.paths.fields}'`)
|
|
77
76
|
|
|
78
|
-
// Direct component import for SSR rendering
|
|
79
|
-
ssrImports.push(`import { default as ${slugVar}_component } from '${block.paths.component}'`)
|
|
80
|
-
|
|
81
77
|
// Conditionally import examples if they exist
|
|
82
78
|
if (block.hasExamples) {
|
|
83
79
|
imports.push(`import { examples as ${slugVar}_examples } from '${block.paths.examples}'`)
|
|
@@ -85,7 +81,6 @@ export const BLOCK_METADATA = {
|
|
|
85
81
|
})
|
|
86
82
|
|
|
87
83
|
const fieldImports = imports.join('\n')
|
|
88
|
-
const ssrComponentImports = ssrImports.join('\n')
|
|
89
84
|
|
|
90
85
|
const registryEntries = blocks.map(block => {
|
|
91
86
|
const slugVar = block.slug.replace(/-/g, '_')
|
|
@@ -137,9 +132,6 @@ import type { BlockConfig, BlockCategory } from '${convertCorePath('@/core/types
|
|
|
137
132
|
|
|
138
133
|
${fieldImports}
|
|
139
134
|
|
|
140
|
-
// Direct component imports for SSR (no React.lazy, no Suspense needed)
|
|
141
|
-
${ssrComponentImports}
|
|
142
|
-
|
|
143
135
|
export const BLOCK_REGISTRY: Record<string, BlockConfig> = {
|
|
144
136
|
${registryEntries}
|
|
145
137
|
}
|
|
@@ -147,9 +139,8 @@ ${registryEntries}
|
|
|
147
139
|
export const BLOCK_CATEGORIES: BlockCategory[] = [${categories.map(c => `'${c}'`).join(', ')}]
|
|
148
140
|
|
|
149
141
|
/**
|
|
150
|
-
* Lazy-loaded block components for
|
|
151
|
-
* Each component is wrapped with React.lazy for code-splitting
|
|
152
|
-
* Uses Object.values to get the first exported component (handles varying naming conventions)
|
|
142
|
+
* Lazy-loaded block components for client-side rendering (admin UI, block picker).
|
|
143
|
+
* Each component is wrapped with React.lazy for code-splitting.
|
|
153
144
|
*/
|
|
154
145
|
export const BLOCK_COMPONENTS: Record<string, React.LazyExoticComponent<React.ComponentType<any>>> = {
|
|
155
146
|
${blocks.map(block => {
|
|
@@ -158,18 +149,32 @@ ${blocks.map(block => {
|
|
|
158
149
|
}
|
|
159
150
|
|
|
160
151
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
152
|
+
* Server-side block import functions for RSC streaming.
|
|
153
|
+
*
|
|
154
|
+
* Each entry is an async import function — NOT a resolved component.
|
|
155
|
+
* PageRenderer (async Server Component) calls these per-block with Suspense,
|
|
156
|
+
* enabling:
|
|
157
|
+
* - Per-block code splitting (only used blocks ship JS to client)
|
|
158
|
+
* - Streaming SSR (server flushes HTML progressively as blocks resolve)
|
|
159
|
+
* - Zero CLS (HTML is complete before client JS runs)
|
|
160
|
+
*
|
|
161
|
+
* For admin/client rendering, use BLOCK_COMPONENTS (React.lazy) instead.
|
|
164
162
|
*/
|
|
165
163
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
166
|
-
export
|
|
164
|
+
export type BlockImportFn = () => Promise<{ default: React.ComponentType<any> }>
|
|
165
|
+
export const BLOCK_IMPORTS_SSR: Record<string, BlockImportFn> = {
|
|
167
166
|
${blocks.map(block => {
|
|
168
|
-
|
|
169
|
-
return ` '${block.slug}': ${slugVar}_component`
|
|
167
|
+
return ` '${block.slug}': () => import('${block.paths.component}')`
|
|
170
168
|
}).join(',\n')}
|
|
171
169
|
}
|
|
172
170
|
|
|
171
|
+
/**
|
|
172
|
+
* @deprecated Use BLOCK_IMPORTS_SSR with async PageRenderer instead.
|
|
173
|
+
* Kept for backwards compatibility with themes that override PageRenderer.
|
|
174
|
+
*/
|
|
175
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
176
|
+
export const BLOCK_COMPONENTS_SSR: Record<string, React.ComponentType<any>> = {}
|
|
177
|
+
|
|
173
178
|
/**
|
|
174
179
|
* Block registry metadata
|
|
175
180
|
*/
|