@uniweb/build 0.73.1 → 0.73.2
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/prerender.js +33 -27
- package/src/schema.js +18 -5
- package/src/site/content-collector.js +9 -3
- package/src/site/emit-extensions.js +21 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.73.
|
|
3
|
+
"version": "0.73.2",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -57,11 +57,11 @@
|
|
|
57
57
|
"js-yaml": "^4.1.0",
|
|
58
58
|
"sharp": "^0.35.3",
|
|
59
59
|
"yaml": "^2.5.0",
|
|
60
|
-
"@uniweb/
|
|
61
|
-
"@uniweb/
|
|
60
|
+
"@uniweb/schemas": "^0.9.3",
|
|
61
|
+
"@uniweb/content-writer": "^0.3.4",
|
|
62
62
|
"@uniweb/projections": "^0.7.11",
|
|
63
63
|
"@uniweb/semantic-parser": "^1.4.1",
|
|
64
|
-
"@uniweb/content-
|
|
64
|
+
"@uniweb/content-reader": "^1.2.7",
|
|
65
65
|
"@uniweb/theming": "^0.2.0"
|
|
66
66
|
},
|
|
67
67
|
"optionalDependencies": {
|
package/src/prerender.js
CHANGED
|
@@ -32,14 +32,34 @@ import { FONT_LINKS_MARKER } from './site/head-markers.js'
|
|
|
32
32
|
import { getAdapter } from './hosts/index.js'
|
|
33
33
|
import { detectCiContext } from './hosts/detect-ci-context.js'
|
|
34
34
|
import { stripBasePath } from './site/extension-urls.js'
|
|
35
|
+
import { isSiteRelative, resolveExtensionDist } from './site/emit-extensions.js'
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
* Resolve an extension URL to
|
|
38
|
+
* Resolve an extension URL to the file prerender imports.
|
|
38
39
|
* Browser URLs like "/effects/entry.js" need mapping to local files.
|
|
39
40
|
*
|
|
41
|
+
* ⭐ AN EXTENSION IS IMPORTED FROM ITS OWN BUILD, as the primary foundation is
|
|
42
|
+
* (`<foundation>/dist/entry.js`, in prerenderSite). A foundation build leaves its
|
|
43
|
+
* externals — `@uniweb/core`, `react`, `react/jsx-runtime` — as bare imports, and
|
|
44
|
+
* Node resolves them from where the FILE sits: inside the extension's own
|
|
45
|
+
* package, which depends on them.
|
|
46
|
+
*
|
|
47
|
+
* ⛔ Not from the copy the site build emits into `dist/` (site/emit-extensions.js).
|
|
48
|
+
* That copy is the browser's, which resolves the same imports through the page's
|
|
49
|
+
* import map; here it sits under the site package, which need not depend on
|
|
50
|
+
* `@uniweb/core` at all. It was the first candidate until 2026-09-28 — an order
|
|
51
|
+
* written when nothing put a file there — and once the build began emitting one
|
|
52
|
+
* (2026-08-05), every workspace extension failed here with "Cannot find package
|
|
53
|
+
* '@uniweb/core'", and the static HTML carried `Component not found` where its
|
|
54
|
+
* sections belonged until the page ran in the browser.
|
|
55
|
+
*
|
|
40
56
|
* Resolution order:
|
|
41
|
-
* 1.
|
|
42
|
-
*
|
|
57
|
+
* 1. The extension's own build — `<project>/<name>/dist/` or
|
|
58
|
+
* `<project>/extensions/<name>/dist/` — found by the emitter's lookup
|
|
59
|
+
* (`resolveExtensionDist`), so the two lanes find the same build.
|
|
60
|
+
* 2. A file in the site's dist, for an extension no workspace package builds (a
|
|
61
|
+
* pre-built module placed in `public/`). Its bare imports resolve only where
|
|
62
|
+
* the site's own dependencies carry them.
|
|
43
63
|
* 3. Original URL (absolute or remote — let import() handle it)
|
|
44
64
|
*/
|
|
45
65
|
export function resolveExtensionPath(url, distDir, projectRoot, base) {
|
|
@@ -50,32 +70,18 @@ export function resolveExtensionPath(url, distDir, projectRoot, base) {
|
|
|
50
70
|
// so a payload produced before this change still resolves.
|
|
51
71
|
url = stripBasePath(url, base)
|
|
52
72
|
|
|
53
|
-
//
|
|
54
|
-
if (url
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
// both at the project root and under an `extensions/` parent — the standard
|
|
61
|
-
// multi-foundation layout puts extensions in `extensions/<name>/`, so the
|
|
62
|
-
// bare-root candidate alone misses the built module and prerender can't load
|
|
63
|
-
// the extension.
|
|
64
|
-
const parts = url.slice(1).split('/')
|
|
65
|
-
if (parts.length >= 2) {
|
|
66
|
-
const pkgName = parts[0]
|
|
67
|
-
const rest = parts.slice(1).join('/')
|
|
68
|
-
const candidates = [
|
|
69
|
-
join(projectRoot, pkgName, 'dist', rest),
|
|
70
|
-
join(projectRoot, 'extensions', pkgName, 'dist', rest),
|
|
71
|
-
]
|
|
72
|
-
for (const devPath of candidates) {
|
|
73
|
-
if (existsSync(devPath)) return devPath
|
|
74
|
-
}
|
|
75
|
-
}
|
|
73
|
+
// Return as-is for absolute paths or remote URLs
|
|
74
|
+
if (!isSiteRelative(url)) return url
|
|
75
|
+
|
|
76
|
+
const build = resolveExtensionDist(url, projectRoot)
|
|
77
|
+
if (build) {
|
|
78
|
+
const inBuild = join(build.distDir, build.rest)
|
|
79
|
+
if (existsSync(inBuild)) return inBuild
|
|
76
80
|
}
|
|
77
81
|
|
|
78
|
-
|
|
82
|
+
const inDist = join(distDir, url)
|
|
83
|
+
if (existsSync(inDist)) return inDist
|
|
84
|
+
|
|
79
85
|
return url
|
|
80
86
|
}
|
|
81
87
|
|
package/src/schema.js
CHANGED
|
@@ -17,7 +17,7 @@ import { isFontVar } from '@uniweb/theming'
|
|
|
17
17
|
import { join, dirname, extname, basename } from 'node:path'
|
|
18
18
|
import { pathToFileURL } from 'node:url'
|
|
19
19
|
import { describeChildren, describeVisuals } from '@uniweb/schemas/component'
|
|
20
|
-
import { SECTION_PARAMS } from '@uniweb/schemas/section'
|
|
20
|
+
import { SECTION_PARAMS, SECTION_KEYS } from '@uniweb/schemas/section'
|
|
21
21
|
import { collectSchemaRefs, buildDataSchemaMap, ownSchemaRefs } from './resolve-data-schema.js'
|
|
22
22
|
import {
|
|
23
23
|
composeSupports,
|
|
@@ -958,19 +958,32 @@ export function reportPlacementDeclarations(components) {
|
|
|
958
958
|
`provides renders "Component not found".`
|
|
959
959
|
)
|
|
960
960
|
}
|
|
961
|
-
// A name framework reserves in a section's params
|
|
962
|
-
//
|
|
961
|
+
// A name framework reserves in a section's params: a setting of the SECTION, which framework
|
|
962
|
+
// applies — `component: false` in `@uniweb/schemas/section`. A component that declares one in
|
|
963
|
+
// its `params:` declares a setting it is never given. ⛔ Until 2026-09-28 this said the param
|
|
964
|
+
// "never receives the author's theme:" and to rename it — read as "the section loses the
|
|
965
|
+
// author's value", the opposite of what happens: the section gets it, and framework applies it.
|
|
963
966
|
const params = entry.params && typeof entry.params === 'object' ? entry.params : {}
|
|
964
967
|
for (const [param, reserved] of Object.entries(SECTION_PARAMS)) {
|
|
965
968
|
if (reserved.component || !Object.hasOwn(params, param)) continue
|
|
966
969
|
console.warn(
|
|
967
|
-
`Warning: ${name} (meta.js):
|
|
968
|
-
`
|
|
970
|
+
`Warning: ${name} (meta.js): params declares "${param}", a setting of the section, not of its ` +
|
|
971
|
+
`component — framework applies an author's ${param}: to the section and never passes it to the ` +
|
|
972
|
+
`component, and an editor offers it for every section (@uniweb/schemas/section). Remove it from params:.` +
|
|
969
973
|
(param === 'grid'
|
|
970
974
|
? ` To offer layouts for the child sections, declare children: { grid: [...] } and lay them out with kit's ChildGrid.`
|
|
971
975
|
: '')
|
|
972
976
|
)
|
|
973
977
|
}
|
|
978
|
+
// …and a key of a section's frontmatter that is not a param at all (`SECTION_KEYS`): the build
|
|
979
|
+
// takes it out before the params are stored, so a param of that name is never given one.
|
|
980
|
+
for (const [key, { is }] of Object.entries(SECTION_KEYS)) {
|
|
981
|
+
if (!Object.hasOwn(params, key)) continue
|
|
982
|
+
console.warn(
|
|
983
|
+
`Warning: ${name} (meta.js): params declares "${key}", a key a section's frontmatter keeps for ` +
|
|
984
|
+
`itself — ${is} — so the component is never given it (@uniweb/schemas/section). Remove it from params:.`
|
|
985
|
+
)
|
|
986
|
+
}
|
|
974
987
|
}
|
|
975
988
|
}
|
|
976
989
|
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import { readFile, readdir, stat } from 'node:fs/promises'
|
|
25
25
|
import { resolveQueriesConfig, toConfigQueries } from './queries-config.js'
|
|
26
|
+
import { isSectionKey } from '@uniweb/schemas/section'
|
|
26
27
|
import { parseNumericPrefix, compareByNumericPrefix } from '../utils/numeric-prefix.js'
|
|
27
28
|
import { isMarkdownFile, isIgnoredFolder } from '../utils/content-files.js'
|
|
28
29
|
import { readLayoutFolder, DEFAULT_LAYOUT, layoutAreaRoute } from './layout-folder.js'
|
|
@@ -1016,9 +1017,14 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
|
|
|
1016
1017
|
console.warn(`[content-collector] ${err.message}`)
|
|
1017
1018
|
}
|
|
1018
1019
|
|
|
1019
|
-
//
|
|
1020
|
-
//
|
|
1021
|
-
|
|
1020
|
+
// ⭐ WHAT IS NOT A PARAM is `SECTION_KEYS` (`@uniweb/schemas/section`) — the list the build's
|
|
1021
|
+
// warning on a component's `params:` reads too — and `fetch`, one of the section's settings,
|
|
1022
|
+
// kept on the section as a field of its own. `query:` / `fetch:` declare the section's own
|
|
1023
|
+
// data, and a leftover `data:` is refused (`declaredFetch`).
|
|
1024
|
+
const { type, hidden, preset, input, props, fetch, query, data, id: frontmatterId } = frontMatter
|
|
1025
|
+
const params = Object.fromEntries(
|
|
1026
|
+
Object.entries(frontMatter).filter(([key]) => !isSectionKey(key) && key !== 'fetch')
|
|
1027
|
+
)
|
|
1022
1028
|
// ⭐ `hidden: true` — a draft section, the page's meaning one level down: left out of a
|
|
1023
1029
|
// published build, kept by `uniweb dev`, and synced as the section's own `hidden`
|
|
1024
1030
|
// [Diego, 2026-09-28]. ⛔ Until then it was an ordinary param: the component received it
|
|
@@ -39,6 +39,11 @@
|
|
|
39
39
|
* Same browser/internal split the runtime's distribution channel draws, for the
|
|
40
40
|
* same reason: what a visitor fetches and what a renderer needs are different
|
|
41
41
|
* sets, and only one of them belongs on a public origin.
|
|
42
|
+
*
|
|
43
|
+
* ⛔ The emitted copy is the BROWSER's, and only the browser's. Prerender imports
|
|
44
|
+
* the extension from its own build (`resolveExtensionPath`), because the copy
|
|
45
|
+
* sits under the site package, where the extension's bare imports
|
|
46
|
+
* (`@uniweb/core`, `react`) do not resolve.
|
|
42
47
|
*/
|
|
43
48
|
|
|
44
49
|
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
@@ -69,26 +74,31 @@ function isBrowserAsset(rel) {
|
|
|
69
74
|
}
|
|
70
75
|
|
|
71
76
|
/**
|
|
72
|
-
* Locate the built `dist/` behind a site-relative extension URL
|
|
77
|
+
* Locate the built `dist/` behind a site-relative extension URL:
|
|
78
|
+
* `/effects/entry.js` → `<project>/effects/dist/` or
|
|
79
|
+
* `<project>/extensions/effects/dist/`, with `rest` the path inside it.
|
|
73
80
|
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
81
|
+
* One lookup for both lanes that need the extension's build: this emitter, which
|
|
82
|
+
* copies it into the site's output, and prerender, which imports it from where it
|
|
83
|
+
* was built (`resolveExtensionPath`). Neither can find an extension the other
|
|
84
|
+
* cannot, by construction rather than by two lists kept in step by hand.
|
|
78
85
|
*
|
|
79
|
-
* @
|
|
86
|
+
* @param {string} url - a site-relative URL, with any deployment base removed
|
|
87
|
+
* @param {string} projectRoot - the directory holding the site and its extensions
|
|
88
|
+
* @returns {{ distDir: string, urlBase: string, rest: string }|null}
|
|
80
89
|
*/
|
|
81
|
-
export function resolveExtensionDist(url,
|
|
90
|
+
export function resolveExtensionDist(url, projectRoot) {
|
|
82
91
|
const parts = url.replace(/^\//, '').split('/')
|
|
83
92
|
if (parts.length < 2) return null
|
|
84
93
|
const pkgName = parts[0]
|
|
85
|
-
const projectRoot = resolve(siteDir, '..')
|
|
86
94
|
|
|
87
95
|
for (const candidate of [
|
|
88
96
|
join(projectRoot, pkgName, 'dist'),
|
|
89
97
|
join(projectRoot, 'extensions', pkgName, 'dist')
|
|
90
98
|
]) {
|
|
91
|
-
if (existsSync(candidate))
|
|
99
|
+
if (existsSync(candidate)) {
|
|
100
|
+
return { distDir: candidate, urlBase: pkgName, rest: parts.slice(1).join('/') }
|
|
101
|
+
}
|
|
92
102
|
}
|
|
93
103
|
return null
|
|
94
104
|
}
|
|
@@ -108,11 +118,12 @@ export function collectExtensionAssets(extensions, siteDir) {
|
|
|
108
118
|
const emit = []
|
|
109
119
|
const unresolved = []
|
|
110
120
|
if (!Array.isArray(extensions)) return { emit, unresolved }
|
|
121
|
+
const projectRoot = resolve(siteDir, '..')
|
|
111
122
|
|
|
112
123
|
for (const decl of extensions) {
|
|
113
124
|
if (!isSiteRelative(decl)) continue // absolute URL or a ref — someone else serves it
|
|
114
125
|
const url = typeof decl === 'string' ? decl : decl.url
|
|
115
|
-
const found = resolveExtensionDist(url,
|
|
126
|
+
const found = resolveExtensionDist(url, projectRoot)
|
|
116
127
|
if (!found) {
|
|
117
128
|
unresolved.push(url)
|
|
118
129
|
continue
|