@uniweb/build 0.11.5 → 0.11.7
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/content/index.js +6 -5
- package/src/prerender.js +5 -7
- package/src/vite-foundation-plugin.js +25 -8
- package/src/site/xref-registry.js +0 -192
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.11.
|
|
3
|
+
"version": "0.11.7",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -58,8 +58,8 @@
|
|
|
58
58
|
"@uniweb/theming": "0.1.3"
|
|
59
59
|
},
|
|
60
60
|
"optionalDependencies": {
|
|
61
|
-
"@uniweb/runtime": "0.8.
|
|
62
|
-
"@uniweb/content-reader": "1.1.
|
|
61
|
+
"@uniweb/runtime": "0.8.9",
|
|
62
|
+
"@uniweb/content-reader": "1.1.8",
|
|
63
63
|
"@uniweb/schemas": "0.2.1"
|
|
64
64
|
},
|
|
65
65
|
"peerDependencies": {
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
"@tailwindcss/vite": "^4.0.0",
|
|
70
70
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
71
71
|
"vite-plugin-svgr": "^4.0.0",
|
|
72
|
-
"@uniweb/core": "0.7.
|
|
72
|
+
"@uniweb/core": "0.7.8"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {
|
|
75
75
|
"vite": {
|
package/src/content/index.js
CHANGED
|
@@ -31,8 +31,9 @@ export {
|
|
|
31
31
|
singularize,
|
|
32
32
|
} from '../site/data-fetcher.js'
|
|
33
33
|
|
|
34
|
-
// Cross-reference registry
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
|
|
34
|
+
// Cross-reference registry moved out of build into kit
|
|
35
|
+
// (@uniweb/kit/xref). Foundations that need cross-references import
|
|
36
|
+
// `buildXrefRegistry` from there and re-export it via
|
|
37
|
+
// `foundation.xref.build`; the runtime calls it during initialization
|
|
38
|
+
// (setup.js / ssr-renderer.js). See framework/kit/src/xref/registry.js
|
|
39
|
+
// for the implementation.
|
package/src/prerender.js
CHANGED
|
@@ -11,7 +11,6 @@ import { readFile, writeFile, mkdir } from 'node:fs/promises'
|
|
|
11
11
|
import { existsSync, readdirSync, statSync } from 'node:fs'
|
|
12
12
|
import { join, dirname, resolve } from 'node:path'
|
|
13
13
|
import { pathToFileURL } from 'node:url'
|
|
14
|
-
import { deriveCacheKey } from '@uniweb/core'
|
|
15
14
|
import { executeFetch, mergeDataIntoContent, singularize } from './site/data-fetcher.js'
|
|
16
15
|
import { shouldSplitContent } from './site/split-content.js'
|
|
17
16
|
|
|
@@ -420,6 +419,7 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
420
419
|
// Load shared SSR functions from runtime (lazy — only when prerendering)
|
|
421
420
|
const {
|
|
422
421
|
initPrerender,
|
|
422
|
+
hydrateDataStore,
|
|
423
423
|
prefetchIcons,
|
|
424
424
|
renderPage,
|
|
425
425
|
injectPageContent,
|
|
@@ -557,12 +557,10 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
557
557
|
const uniweb = initPrerender(siteContent, foundation, loadedExtensions, { onProgress })
|
|
558
558
|
|
|
559
559
|
// Build-specific: pre-populate DataStore so EntityStore can resolve data during prerender.
|
|
560
|
-
//
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
}
|
|
565
|
-
}
|
|
560
|
+
// hydrateDataStore handles cache-key derivation + value-shape wrapping
|
|
561
|
+
// — same helper used by the browser SPA boot and by the Cloudflare
|
|
562
|
+
// Worker SSR isolate, so all three render paths agree on shape.
|
|
563
|
+
hydrateDataStore(uniweb.activeWebsite, fetchedData)
|
|
566
564
|
|
|
567
565
|
// Pre-fetch icons for SSR embedding
|
|
568
566
|
await prefetchIcons(siteContent, uniweb, onProgress)
|
|
@@ -42,13 +42,30 @@ async function buildSchemaWithPreviews(srcDir, outDir, isProduction, sectionPath
|
|
|
42
42
|
let _buildingSSRBundle = false
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
|
-
* Build a self-contained ESM bundle for edge SSR (Dynamic Workers).
|
|
45
|
+
* Build a self-contained ESM bundle for edge SSR (Cloudflare Dynamic Workers).
|
|
46
46
|
*
|
|
47
|
-
* Produces
|
|
48
|
-
*
|
|
47
|
+
* Produces `ssr-worker-bundle.js` — a single ESM file with React,
|
|
48
|
+
* ReactDOM/server, `@uniweb/core`, `@uniweb/runtime/ssr`,
|
|
49
|
+
* `@uniweb/theming`, and the foundation's components all inlined. No
|
|
50
|
+
* external imports.
|
|
49
51
|
*
|
|
50
|
-
* This
|
|
51
|
-
*
|
|
52
|
+
* This is **NOT a foundation**. The foundation is `dist/foundation.js`
|
|
53
|
+
* — a foundation-shaped ESM module that externalizes runtime and links
|
|
54
|
+
* to it at runtime, the same as in browser SPA / framework SSG / unipress
|
|
55
|
+
* (Node SSR). This file is something different: a self-contained SSR
|
|
56
|
+
* pipeline shaped for the Cloudflare Workers Dynamic Worker LOADER, with
|
|
57
|
+
* the foundation embedded as one of several inputs. The size ratio
|
|
58
|
+
* (typical ~12× larger than `foundation.js`) reflects what's actually
|
|
59
|
+
* inside it.
|
|
60
|
+
*
|
|
61
|
+
* The artifact is bundled this way because the Dynamic Worker LOADER
|
|
62
|
+
* accepts a closed `modules` map at isolate construction (no external
|
|
63
|
+
* resolver, no path back to npm or R2 for transitive imports). One
|
|
64
|
+
* file in, everything resolves. A future refactor can switch to side-
|
|
65
|
+
* loading runtime + React + core into the modules map separately so
|
|
66
|
+
* foundations stop carrying runtime in their bundles — see
|
|
67
|
+
* `kb/platform/plans/edge-runtime-side-loading.md`. Until then, this
|
|
68
|
+
* build pre-bundles for the isolate's contract.
|
|
52
69
|
*
|
|
53
70
|
* @param {string} outDir - Path to dist/ directory containing foundation.js
|
|
54
71
|
*/
|
|
@@ -142,7 +159,7 @@ async function buildSSRBundle(outDir) {
|
|
|
142
159
|
bundle: true,
|
|
143
160
|
format: 'esm',
|
|
144
161
|
platform: 'browser',
|
|
145
|
-
outfile: join(outDir, '
|
|
162
|
+
outfile: join(outDir, 'ssr-worker-bundle.js'),
|
|
146
163
|
minify: false,
|
|
147
164
|
external: [],
|
|
148
165
|
nodePaths,
|
|
@@ -151,9 +168,9 @@ async function buildSSRBundle(outDir) {
|
|
|
151
168
|
logLevel: 'warning',
|
|
152
169
|
})
|
|
153
170
|
|
|
154
|
-
const ssrFile = join(outDir, '
|
|
171
|
+
const ssrFile = join(outDir, 'ssr-worker-bundle.js')
|
|
155
172
|
const size = (statSync(ssrFile).size / 1024).toFixed(1)
|
|
156
|
-
console.log(`Generated
|
|
173
|
+
console.log(`Generated ssr-worker-bundle.js (${size} KB)`)
|
|
157
174
|
} catch (err) {
|
|
158
175
|
console.warn(`Warning: SSR bundle build failed: ${err.message}`)
|
|
159
176
|
} finally {
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Cross-reference registry — populated at content-collection time.
|
|
3
|
-
*
|
|
4
|
-
* Walks the parsed document tree to find every block-level element
|
|
5
|
-
* carrying a {#id} attribute, infers the element's kind from its node
|
|
6
|
-
* type, and records the entry with a per-kind counter. The result
|
|
7
|
-
* attaches to siteContent.xref and is consumed by the framework's
|
|
8
|
-
* <Ref> component to render `[#id]` cross-references.
|
|
9
|
-
*
|
|
10
|
-
* Built-in kinds:
|
|
11
|
-
* - heading → 'section' (hierarchical counter: 1, 1.1, 1.1.1, 2, …)
|
|
12
|
-
* - image → 'figure' (flat arabic counter)
|
|
13
|
-
* - math_display → 'equation' (flat arabic counter)
|
|
14
|
-
* - table → 'table' (flat arabic counter)
|
|
15
|
-
*
|
|
16
|
-
* Foundation extensions: foundations may declare additional kinds via
|
|
17
|
-
* `foundation.xref.kinds`. The id-collection pass uses the foundation's
|
|
18
|
-
* declared `prefix` (e.g. `thm` → `theorem`) to classify ids that don't
|
|
19
|
-
* land on a built-in element type.
|
|
20
|
-
*
|
|
21
|
-
* The registry is per-document, not per-page: counters span the whole
|
|
22
|
-
* document so authors get consistent numbering even when their document
|
|
23
|
-
* spans multiple pages. (For page-local counter resets, foundations can
|
|
24
|
-
* declare `resetOn: 'chapter'` on a kind — applied during render, not
|
|
25
|
-
* here.)
|
|
26
|
-
*
|
|
27
|
-
* Output shape:
|
|
28
|
-
* {
|
|
29
|
-
* entries: {
|
|
30
|
-
* <id>: {
|
|
31
|
-
* id, // the id itself
|
|
32
|
-
* kind, // 'figure' | 'equation' | 'section' | 'table' | <foundation-declared>
|
|
33
|
-
* counter, // number for flat kinds, dotted string for hierarchical
|
|
34
|
-
* counterText, // displayable counter ('3', '3.2')
|
|
35
|
-
* sourcePath, // page route the id was declared on (for back-refs)
|
|
36
|
-
* caption, // (figures, tables) caption attr, when set
|
|
37
|
-
* text, // (sections) heading's plain text content
|
|
38
|
-
* latex, // (equations) latex source for the equation
|
|
39
|
-
* },
|
|
40
|
-
* },
|
|
41
|
-
* }
|
|
42
|
-
*
|
|
43
|
-
* `caption`, `text`, and `latex` are populated only for the elements
|
|
44
|
-
* that carry them — they're undefined on other kinds. List sections
|
|
45
|
-
* (ListOfFigures / ListOfTables / TableOfContents) read these to render
|
|
46
|
-
* "Figure 3 — A diagram of mitosis ........ 47" entries without
|
|
47
|
-
* re-walking the document tree. Other consumers (the framework's <Ref>
|
|
48
|
-
* component, Typst / LaTeX cross-reference emitters) ignore them.
|
|
49
|
-
*/
|
|
50
|
-
|
|
51
|
-
const KIND_BY_TYPE = {
|
|
52
|
-
heading: 'section',
|
|
53
|
-
image: 'figure',
|
|
54
|
-
math_display: 'equation',
|
|
55
|
-
table: 'table',
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
export function buildXrefRegistry(siteContent, options = {}) {
|
|
59
|
-
const { foundationKinds = {}, onWarn = (msg) => console.warn(msg) } = options
|
|
60
|
-
|
|
61
|
-
const entries = {}
|
|
62
|
-
const flatCounters = {} // kind -> next integer counter (figure, equation, table, foundation-declared kinds)
|
|
63
|
-
const sectionStack = [] // hierarchical counters per heading level
|
|
64
|
-
|
|
65
|
-
// Index foundation prefixes for id-prefix inference.
|
|
66
|
-
const prefixToKind = {}
|
|
67
|
-
for (const [kind, meta] of Object.entries(foundationKinds)) {
|
|
68
|
-
if (meta?.prefix) prefixToKind[meta.prefix] = kind
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function nextFlat(kind) {
|
|
72
|
-
flatCounters[kind] = (flatCounters[kind] || 0) + 1
|
|
73
|
-
return flatCounters[kind]
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Track the shallowest heading depth seen — chapter content commonly
|
|
77
|
-
// starts at h2 (chapter title is rendered separately as h1 by the
|
|
78
|
-
// foundation), so the first encountered level becomes the document's
|
|
79
|
-
// top counter level. Re-anchoring: if a later heading appears at a
|
|
80
|
-
// shallower level than `topLevel`, drop topLevel down to match.
|
|
81
|
-
let topLevel = null
|
|
82
|
-
|
|
83
|
-
function nextHierarchical(level) {
|
|
84
|
-
if (topLevel == null || level < topLevel) topLevel = level
|
|
85
|
-
const depth = level - topLevel + 1 // 1-based depth from the document's top level
|
|
86
|
-
|
|
87
|
-
// Trim deeper levels when surfacing back to a shallower heading.
|
|
88
|
-
while (sectionStack.length >= depth) sectionStack.pop()
|
|
89
|
-
while (sectionStack.length < depth - 1) sectionStack.push(1) // implicit parents start at 1
|
|
90
|
-
sectionStack.push((sectionStack[depth - 1] || 0) + 1)
|
|
91
|
-
sectionStack.length = depth
|
|
92
|
-
return sectionStack.slice().join('.')
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Collect the plain text content of a node tree — used to capture a
|
|
96
|
-
// heading's displayable text for ListOfSections-style entries. Walks
|
|
97
|
-
// the standard ProseMirror text shape (text nodes carry `.text`;
|
|
98
|
-
// structural nodes recurse via `.content`).
|
|
99
|
-
function collectTextContent(node) {
|
|
100
|
-
if (!node || typeof node !== 'object') return ''
|
|
101
|
-
if (node.type === 'text' && typeof node.text === 'string') return node.text
|
|
102
|
-
if (Array.isArray(node.content)) {
|
|
103
|
-
return node.content.map(collectTextContent).join('')
|
|
104
|
-
}
|
|
105
|
-
return ''
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function inferKind(node) {
|
|
109
|
-
// 1. Node-type-based: built-ins.
|
|
110
|
-
const builtin = KIND_BY_TYPE[node.type]
|
|
111
|
-
if (builtin) return builtin
|
|
112
|
-
// 2. Explicit kind attribute on the node ({.kind} or kind=…).
|
|
113
|
-
if (node.attrs?.kind && (KIND_BY_TYPE[node.attrs.kind] || foundationKinds[node.attrs.kind])) {
|
|
114
|
-
return node.attrs.kind
|
|
115
|
-
}
|
|
116
|
-
// 3. Foundation-declared prefix on the id.
|
|
117
|
-
const id = node.attrs?.id
|
|
118
|
-
if (id && id.includes('-')) {
|
|
119
|
-
const prefix = id.slice(0, id.indexOf('-'))
|
|
120
|
-
if (prefixToKind[prefix]) return prefixToKind[prefix]
|
|
121
|
-
}
|
|
122
|
-
return null
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
function visit(node, sourcePath) {
|
|
126
|
-
if (!node || typeof node !== 'object') return
|
|
127
|
-
|
|
128
|
-
// Headings get a hierarchical counter regardless of whether they
|
|
129
|
-
// carry an id — the {#id} just labels them; the counter itself is
|
|
130
|
-
// assigned by tree position.
|
|
131
|
-
let counter = null
|
|
132
|
-
let counterText = null
|
|
133
|
-
if (node.type === 'heading') {
|
|
134
|
-
const level = Math.max(1, Math.min(6, node.attrs?.level || 1))
|
|
135
|
-
counterText = nextHierarchical(level)
|
|
136
|
-
counter = counterText
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const id = node.attrs?.id
|
|
140
|
-
if (id) {
|
|
141
|
-
const kind = inferKind(node)
|
|
142
|
-
if (!kind) {
|
|
143
|
-
onWarn(`[xref] {#${id}} on unrecognized element type "${node.type}" — ignored`)
|
|
144
|
-
} else if (entries[id]) {
|
|
145
|
-
onWarn(`[xref] duplicate id "${id}" — keeping first registration`)
|
|
146
|
-
} else {
|
|
147
|
-
if (counter == null) {
|
|
148
|
-
counter = nextFlat(kind)
|
|
149
|
-
counterText = String(counter)
|
|
150
|
-
}
|
|
151
|
-
const entry = { id, kind, counter, counterText, sourcePath: sourcePath || '' }
|
|
152
|
-
// Per-kind metadata. List sections (ListOfFigures, ListOfTables,
|
|
153
|
-
// TableOfContents) read these directly so they don't have to
|
|
154
|
-
// re-walk the parsed tree to find captions and headings.
|
|
155
|
-
const captionAttr = node.attrs?.caption
|
|
156
|
-
if (kind === 'figure' || kind === 'table') {
|
|
157
|
-
if (captionAttr) entry.caption = String(captionAttr)
|
|
158
|
-
}
|
|
159
|
-
if (node.type === 'heading') {
|
|
160
|
-
const text = collectTextContent(node)
|
|
161
|
-
if (text) entry.text = text
|
|
162
|
-
}
|
|
163
|
-
if (node.type === 'math_display') {
|
|
164
|
-
const latex = node.attrs?.latex
|
|
165
|
-
if (latex) entry.latex = String(latex)
|
|
166
|
-
}
|
|
167
|
-
entries[id] = entry
|
|
168
|
-
}
|
|
169
|
-
} else if (node.type === 'heading') {
|
|
170
|
-
// Heading without an id still advances the counter — but we don't
|
|
171
|
-
// register it (no way to reference it). The advance is needed so
|
|
172
|
-
// that the *next* labeled heading gets the right hierarchical
|
|
173
|
-
// counter.
|
|
174
|
-
// counter already computed above; just no entry.
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (Array.isArray(node.content)) {
|
|
178
|
-
for (const child of node.content) visit(child, sourcePath)
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
for (const page of siteContent.pages || []) {
|
|
183
|
-
for (const section of page.sections || []) {
|
|
184
|
-
const content = section.content
|
|
185
|
-
if (content?.type === 'doc' && Array.isArray(content.content)) {
|
|
186
|
-
for (const child of content.content) visit(child, page.route)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return { entries }
|
|
192
|
-
}
|