@uniweb/build 0.15.13 → 0.15.14
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 +7 -7
- package/src/prerender.js +12 -28
- package/src/site/head-markers.js +16 -6
- package/src/uwx/locale-sync.js +37 -16
- package/src/uwx/site.js +8 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.14",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,13 +59,13 @@
|
|
|
59
59
|
"js-yaml": "^4.1.0",
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
|
-
"@uniweb/projections": "0.1.
|
|
63
|
-
"@uniweb/
|
|
64
|
-
"@uniweb/
|
|
62
|
+
"@uniweb/projections": "0.1.6",
|
|
63
|
+
"@uniweb/theming": "0.1.15",
|
|
64
|
+
"@uniweb/content-writer": "0.2.9"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"@uniweb/runtime": "0.8.
|
|
68
|
-
"@uniweb/content-reader": "1.1.
|
|
67
|
+
"@uniweb/runtime": "0.8.42",
|
|
68
|
+
"@uniweb/content-reader": "1.1.19",
|
|
69
69
|
"@uniweb/schemas": "0.2.4"
|
|
70
70
|
},
|
|
71
71
|
"peerDependencies": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"@tailwindcss/vite": "^4.0.0",
|
|
76
76
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
77
77
|
"vite-plugin-svgr": "^4.0.0",
|
|
78
|
-
"@uniweb/core": "0.7.
|
|
78
|
+
"@uniweb/core": "0.7.34"
|
|
79
79
|
},
|
|
80
80
|
"peerDependenciesMeta": {
|
|
81
81
|
"vite": {
|
package/src/prerender.js
CHANGED
|
@@ -381,35 +381,19 @@ export function scopeFetchedData(fetchedData, scopeRoutes) {
|
|
|
381
381
|
export function injectBuildData(html, siteContent, { splitContent = false, currentRoute = null, scopeRoutes = null } = {}) {
|
|
382
382
|
let result = html
|
|
383
383
|
|
|
384
|
-
//
|
|
385
|
-
//
|
|
384
|
+
// Neither the theme <style> NOR the font <link>s are injected here anymore.
|
|
385
|
+
// Both are derived from the website graph (`website.themeData`), so they
|
|
386
|
+
// belong to the shared seam — @uniweb/runtime/ssr's injectPageContent(),
|
|
387
|
+
// which runs just before this — and every lane gets them from one
|
|
388
|
+
// implementation. The theme CSS sat here until 2026-07-28 and cloud-rendered
|
|
389
|
+
// pages were unstyled the whole time; the font links followed once
|
|
390
|
+
// FONT_LINKS_MARKER moved to @uniweb/theming, the one package both this and
|
|
391
|
+
// the runtime can read it from.
|
|
386
392
|
//
|
|
387
|
-
//
|
|
388
|
-
//
|
|
389
|
-
//
|
|
390
|
-
//
|
|
391
|
-
// What blocks the move is the marker, not the principle: FONT_LINKS_MARKER
|
|
392
|
-
// has a second consumer in site/plugin.js (the vite plugin), which must not
|
|
393
|
-
// import the SSR bundle, and @uniweb/build declares no dependency on
|
|
394
|
-
// @uniweb/runtime — so there is no one place both sides can read it from
|
|
395
|
-
// today. Fixing that means giving the runtime a zero-dep leaf export for
|
|
396
|
-
// the constant and re-exporting it here. Degraded-not-broken (wrong
|
|
397
|
-
// typeface, not unstyled), which is why it was scoped out rather than
|
|
398
|
-
// rushed.
|
|
399
|
-
if (siteContent?.theme?.links && !result.includes(FONT_LINKS_MARKER)) {
|
|
400
|
-
result = result.replace(
|
|
401
|
-
'</head>',
|
|
402
|
-
` ${FONT_LINKS_MARKER}\n${siteContent.theme.links}\n </head>`
|
|
403
|
-
)
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Theme CSS is NOT injected here. Like the appearance boot script below, it
|
|
407
|
-
// is derived from the website graph (`website.themeData`), so it belongs to
|
|
408
|
-
// the shared seam — @uniweb/runtime/ssr's injectPageContent(), which runs
|
|
409
|
-
// just before this — and every lane gets it from one implementation. It sat
|
|
410
|
-
// here until 2026-07-28 and cloud-rendered pages were unstyled the whole
|
|
411
|
-
// time. See the "which side of the seam?" note there before adding a new
|
|
412
|
-
// head injection, and note that this file has now got it wrong twice.
|
|
393
|
+
// See the "which side of the seam?" note there before adding a new head
|
|
394
|
+
// injection, and note that this file has now got it wrong twice. The guard
|
|
395
|
+
// against a third is mechanical: tests/head-seam-parity.test.js fails if this
|
|
396
|
+
// function's <head> output gains anything outside its BUILD_ONLY allowlist.
|
|
413
397
|
|
|
414
398
|
// The pre-paint appearance script is NOT injected here. It belongs to the
|
|
415
399
|
// shared seam — @uniweb/runtime/ssr's injectPageContent(), which runs just
|
package/src/site/head-markers.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Stable markers for head content the build injects.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* (which produces `dist/index.html`)
|
|
6
|
-
* that HTML per page)
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Several stages can write the same block: this package's vite plugin
|
|
5
|
+
* (`transformIndexHtml`, which produces `dist/index.html`), this package's
|
|
6
|
+
* prerenderer (which post-processes that HTML per page), and — since the
|
|
7
|
+
* 2026-07-28 seam fix — `@uniweb/runtime`'s `injectPageContent()`, the prerender
|
|
8
|
+
* seam every lane shares. The marker lets a later stage tell "already injected"
|
|
9
|
+
* from "never injected" instead of guessing, so a page passing through several
|
|
10
|
+
* gets exactly one copy.
|
|
9
11
|
*
|
|
10
12
|
* The theme CSS uses `id="uniweb-theme"` on its <style> for the same purpose;
|
|
11
13
|
* <link> tags have no natural id to hang that on, hence the comment marker.
|
|
12
14
|
*
|
|
15
|
+
* **The marker itself now lives in `@uniweb/theming`**, beside the code that
|
|
16
|
+
* generates the block it delimits. This package and `@uniweb/runtime` cannot
|
|
17
|
+
* import one another, but both depend on `@uniweb/theming` — so that is the one
|
|
18
|
+
* home neither has to reach across a dependency boundary to read, and the
|
|
19
|
+
* literal is never duplicated (two halves of a dedupe check that drift apart
|
|
20
|
+
* stop deduping, silently). Re-exported here so every existing import in this
|
|
21
|
+
* package keeps working unchanged.
|
|
22
|
+
*
|
|
13
23
|
* @module @uniweb/build/site
|
|
14
24
|
*/
|
|
15
25
|
|
|
16
|
-
export
|
|
26
|
+
export { FONT_LINKS_MARKER } from '@uniweb/theming'
|
package/src/uwx/locale-sync.js
CHANGED
|
@@ -108,29 +108,50 @@ export function loadLocaleTranslations(siteRoot, locales, subdir = '') {
|
|
|
108
108
|
* map lives only on disk (`locales/{locale}.json`) and is recovered on pull (see
|
|
109
109
|
* unwrapLocalizedContent).
|
|
110
110
|
*
|
|
111
|
-
*
|
|
112
|
-
* for
|
|
113
|
-
*
|
|
114
|
-
*
|
|
111
|
+
* ALWAYS returns the per-locale map — `{ [sourceLocale]: doc }` at minimum. A target
|
|
112
|
+
* locale with no translation for THIS doc is omitted (it falls back to the source
|
|
113
|
+
* locale through the delivery locale chain), so the payload stays lean.
|
|
114
|
+
*
|
|
115
|
+
* It used to return the bare doc when nothing had been translated, which made the
|
|
116
|
+
* wire shape depend on *whether a given section happened to have a translation* —
|
|
117
|
+
* so one push could carry a map for a translated section and a bare doc for its
|
|
118
|
+
* untranslated neighbour, on the same page. The field declares `localized: true`,
|
|
119
|
+
* and a bare doc makes that declaration false: `{type, content}` is an object, so
|
|
120
|
+
* it satisfies "must be a map" with `type` and `content` sitting where locale codes
|
|
121
|
+
* belong, and a store cannot validate the one property the field declares. Every
|
|
122
|
+
* reader then has to disambiguate by asking "does this object happen to look like a
|
|
123
|
+
* ProseMirror doc" — a heuristic that has to be implemented identically in three
|
|
124
|
+
* codebases forever, and has already fail-opened in one of them.
|
|
125
|
+
*
|
|
126
|
+
* `localizeScalar` below has always wrapped unconditionally, so the producer was
|
|
127
|
+
* also inconsistent with itself: a section's `title` shipped as `{en: …}` while its
|
|
128
|
+
* `content` shipped bare, in the same payload.
|
|
129
|
+
*
|
|
130
|
+
* Readers stay tolerant of the bare form permanently — it exists in stores, in
|
|
131
|
+
* `.uwx` files on disk, and in every backup taken before this change. This changes
|
|
132
|
+
* what we WRITE, never what we accept (see `isLocalizedContent`, and
|
|
133
|
+
* `unwrapLocalizedContent`, which returns `content[sourceLocale]` so a source-only
|
|
134
|
+
* map round-trips back to a bare doc on the file lane).
|
|
115
135
|
*
|
|
116
136
|
* @param {object} doc - the source-locale ProseMirror content doc
|
|
117
137
|
* @param {string} sourceLocale
|
|
118
|
-
* @param {string[]} targetLocales
|
|
119
|
-
* @param {object} translations - `{ locale: { hash: tgt } }` from loadLocaleTranslations
|
|
138
|
+
* @param {string[]} [targetLocales]
|
|
139
|
+
* @param {object} [translations] - `{ locale: { hash: tgt } }` from loadLocaleTranslations
|
|
120
140
|
*/
|
|
121
141
|
export function localizeContentDoc(doc, sourceLocale, targetLocales, translations) {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
142
|
+
// Non-docs (null, an already-localized map) pass through untouched.
|
|
143
|
+
if (!isProseMirrorDoc(doc)) return doc
|
|
144
|
+
|
|
125
145
|
const result = { [sourceLocale]: doc }
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
146
|
+
if (targetLocales && translations) {
|
|
147
|
+
for (const locale of targetLocales) {
|
|
148
|
+
const table = translations[locale]
|
|
149
|
+
if (!table) continue
|
|
150
|
+
const resolved = resolveDocForLocale(doc, table)
|
|
151
|
+
if (resolved) result[locale] = resolved
|
|
152
|
+
}
|
|
131
153
|
}
|
|
132
|
-
|
|
133
|
-
return Object.keys(result).length > 1 ? result : doc
|
|
154
|
+
return result
|
|
134
155
|
}
|
|
135
156
|
|
|
136
157
|
/**
|
package/src/uwx/site.js
CHANGED
|
@@ -663,11 +663,14 @@ export async function siteProjectToDocument(siteRoot, opts = {}) {
|
|
|
663
663
|
|
|
664
664
|
// Wrap each section's content into its per-locale form (source doc + target
|
|
665
665
|
// structural maps from locales/{locale}.json, or a free-form body override from
|
|
666
|
-
// locales/freeform/**)
|
|
667
|
-
//
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
666
|
+
// locales/freeform/**). A non-invasive post-pass over the built tree.
|
|
667
|
+
//
|
|
668
|
+
// Runs for EVERY site, including single-locale ones: `content` declares
|
|
669
|
+
// `localized: true`, so it ships as `{ [sourceLocale]: doc }` whatever the
|
|
670
|
+
// language count. Guarding this on `targetLocales.length` is what used to send
|
|
671
|
+
// a bare doc from single-locale sites — see localizeContentDoc for why one
|
|
672
|
+
// field with two shapes is a store that cannot validate its own declaration.
|
|
673
|
+
await localizeContentTree(pages, layoutSections, sourceLocale, targetLocales, translations, siteRoot)
|
|
671
674
|
|
|
672
675
|
// Collection DECLARATIONS — the merged collections.yml + site.yml::collections
|
|
673
676
|
// config (the records themselves are separate entities; this is just the config).
|