@uniweb/build 0.16.14 → 0.16.15
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 +5 -5
- package/src/prerender.js +10 -2
- package/src/site/config.js +18 -5
- package/src/site/content-collector.js +8 -0
- package/src/site/extension-urls.js +123 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.15",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -59,15 +59,15 @@
|
|
|
59
59
|
"js-yaml": "^4.1.0",
|
|
60
60
|
"sharp": "^0.35.3",
|
|
61
61
|
"yaml": "^2.5.0",
|
|
62
|
-
"@uniweb/
|
|
62
|
+
"@uniweb/theming": "0.1.15",
|
|
63
63
|
"@uniweb/projections": "0.2.5",
|
|
64
|
-
"@uniweb/
|
|
64
|
+
"@uniweb/content-writer": "0.3.3"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
+
"@uniweb/runtime": "0.9.7",
|
|
67
68
|
"@uniweb/content-reader": "1.2.2",
|
|
68
|
-
"@uniweb/schemas": "0.2.5",
|
|
69
69
|
"@uniweb/semantic-parser": "1.2.1",
|
|
70
|
-
"@uniweb/
|
|
70
|
+
"@uniweb/schemas": "0.2.5"
|
|
71
71
|
},
|
|
72
72
|
"peerDependencies": {
|
|
73
73
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
package/src/prerender.js
CHANGED
|
@@ -17,6 +17,7 @@ import { shouldSplitContent } from './site/split-content.js'
|
|
|
17
17
|
import { FONT_LINKS_MARKER } from './site/head-markers.js'
|
|
18
18
|
import { getAdapter } from './hosts/index.js'
|
|
19
19
|
import { detectCiContext } from './hosts/detect-ci-context.js'
|
|
20
|
+
import { stripBasePath } from './site/extension-urls.js'
|
|
20
21
|
|
|
21
22
|
/**
|
|
22
23
|
* Resolve an extension URL to a filesystem path for prerender.
|
|
@@ -27,7 +28,14 @@ import { detectCiContext } from './hosts/detect-ci-context.js'
|
|
|
27
28
|
* 2. Project root with dist subdir (dev layout, e.g., project/effects/dist/entry.js)
|
|
28
29
|
* 3. Original URL (absolute or remote — let import() handle it)
|
|
29
30
|
*/
|
|
30
|
-
export function resolveExtensionPath(url, distDir, projectRoot) {
|
|
31
|
+
export function resolveExtensionPath(url, distDir, projectRoot, base) {
|
|
32
|
+
// The payload now carries FINAL, base-resolved URLs (see
|
|
33
|
+
// site/extension-urls.js), but `dist/` has no base segment — a site deployed
|
|
34
|
+
// at `/docs/` still writes `dist/effects/entry.js`. Strip the base before
|
|
35
|
+
// mapping onto the build tree. A URL without the base is returned unchanged,
|
|
36
|
+
// so a payload produced before this change still resolves.
|
|
37
|
+
url = stripBasePath(url, base)
|
|
38
|
+
|
|
31
39
|
// Only resolve URLs that look like root-relative paths
|
|
32
40
|
if (url.startsWith('/')) {
|
|
33
41
|
// Try dist directory first (production: files copied to site/dist/)
|
|
@@ -638,7 +646,7 @@ export async function prerenderSite(siteDir, options = {}) {
|
|
|
638
646
|
for (const ext of extensionSources) {
|
|
639
647
|
try {
|
|
640
648
|
const url = typeof ext === 'string' ? ext : ext.url
|
|
641
|
-
const extPath = resolveExtensionPath(url, distDir, projectRoot)
|
|
649
|
+
const extPath = resolveExtensionPath(url, distDir, projectRoot, siteContent.config?.base)
|
|
642
650
|
const extModule = await import(pathToFileURL(extPath).href)
|
|
643
651
|
loadedExtensions.push(extModule)
|
|
644
652
|
onProgress(` Extension loaded: ${url}`)
|
package/src/site/config.js
CHANGED
|
@@ -29,6 +29,7 @@ import {
|
|
|
29
29
|
getStructuralWatchPaths
|
|
30
30
|
} from '../generate-entry.js'
|
|
31
31
|
import { importMapPlugin } from '../import-map-plugin.js'
|
|
32
|
+
import { resolveModuleUrl, resolveExtensionUrls } from './extension-urls.js'
|
|
32
33
|
import { resolveFoundationSrcPath } from '../utils/foundation-source-root.js'
|
|
33
34
|
|
|
34
35
|
/**
|
|
@@ -506,8 +507,14 @@ export async function defineSiteConfig(options = {}) {
|
|
|
506
507
|
}
|
|
507
508
|
|
|
508
509
|
// Extension JS modulepreload (CSS left to runtime — we can't reliably
|
|
509
|
-
// derive CSS URLs for all extension formats)
|
|
510
|
-
|
|
510
|
+
// derive CSS URLs for all extension formats).
|
|
511
|
+
//
|
|
512
|
+
// Resolved through the SAME helper the payload uses, so the hint and
|
|
513
|
+
// `config.extensions` cannot disagree. They did: the payload was
|
|
514
|
+
// base-resolved at load time by the runtime while this emitted the
|
|
515
|
+
// raw URL, so on a subdirectory deploy the browser preloaded one URL
|
|
516
|
+
// and then requested another.
|
|
517
|
+
const extensions = resolveExtensionUrls(siteConfig.extensions, base) || []
|
|
511
518
|
for (const ext of extensions) {
|
|
512
519
|
const url = typeof ext === 'string' ? ext : ext?.url
|
|
513
520
|
if (url) {
|
|
@@ -525,12 +532,18 @@ export async function defineSiteConfig(options = {}) {
|
|
|
525
532
|
})
|
|
526
533
|
}
|
|
527
534
|
|
|
528
|
-
// Build foundation config for runtime
|
|
529
|
-
|
|
535
|
+
// Build foundation config for runtime.
|
|
536
|
+
//
|
|
537
|
+
// URLs are resolved against the deployment base HERE, because what reaches
|
|
538
|
+
// the runtime is final — the loader anchors a root-relative URL to the
|
|
539
|
+
// document origin and applies no base of its own. That is what makes the
|
|
540
|
+
// primary foundation and every extension follow one rule; they used to
|
|
541
|
+
// differ. See site/extension-urls.js.
|
|
542
|
+
const foundationConfig = resolveModuleUrl({
|
|
530
543
|
mode: isRuntimeMode ? 'runtime' : 'bundled',
|
|
531
544
|
url: foundationInfo.url || '/foundation/foundation.js',
|
|
532
545
|
cssUrl: foundationInfo.cssUrl || '/foundation/assets/style.css'
|
|
533
|
-
}
|
|
546
|
+
}, base)
|
|
534
547
|
|
|
535
548
|
return {
|
|
536
549
|
// Base public path for deployment (e.g., '/demos/mysite/')
|
|
@@ -31,6 +31,7 @@ import { collectSectionAssets, mergeAssetCollections, collectConfigAssets } from
|
|
|
31
31
|
import { collectSectionIcons, mergeIconCollections, buildIconManifest } from './icons.js'
|
|
32
32
|
import { normalizeHideIn, dropUnpublishedPages } from './nav-visibility.js'
|
|
33
33
|
import { parseFetchConfig } from './data-fetcher.js'
|
|
34
|
+
import { resolveExtensionUrls } from './extension-urls.js'
|
|
34
35
|
import { buildTheme, extractFoundationVars } from '../theme/index.js'
|
|
35
36
|
import { resolveDefaultLocale, resolvePublishableLocales, validateLanguageConfig } from '@uniweb/core'
|
|
36
37
|
|
|
@@ -2175,6 +2176,13 @@ export async function collectSiteContent(sitePath, options = {}) {
|
|
|
2175
2176
|
// what that subpath looks like; that is the host's shape, not ours.
|
|
2176
2177
|
if (base && base !== '/') {
|
|
2177
2178
|
siteConfig.base = base
|
|
2179
|
+
|
|
2180
|
+
// Extension module URLs are resolved HERE, not at load time. What reaches
|
|
2181
|
+
// the runtime is final — the loader anchors it to the document origin and
|
|
2182
|
+
// applies no base of its own, so the primary foundation and every extension
|
|
2183
|
+
// resolve by one rule. See site/extension-urls.js for why the producer owns
|
|
2184
|
+
// this. Non-URL entries (registry refs) and absolute URLs pass through.
|
|
2185
|
+
siteConfig.extensions = resolveExtensionUrls(siteConfig.extensions, base)
|
|
2178
2186
|
}
|
|
2179
2187
|
|
|
2180
2188
|
// Profile selects workspace-root defaults: site.yml → pages/ + page mode +
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base-path resolution for foundation / extension module URLs.
|
|
3
|
+
*
|
|
4
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
5
|
+
* THE RULE: a module URL that reaches the runtime is FINAL.
|
|
6
|
+
* ─────────────────────────────────────────────────────────────────────────────
|
|
7
|
+
*
|
|
8
|
+
* The runtime anchors a root-relative module URL to the document origin (which
|
|
9
|
+
* host serves it) and does nothing else — no base prefixing. So when a site is
|
|
10
|
+
* deployed under a subdirectory, the base must be applied HERE, by the producer
|
|
11
|
+
* that knows it, and identically for the primary foundation and every extension.
|
|
12
|
+
*
|
|
13
|
+
* Why the producer and not the loader:
|
|
14
|
+
*
|
|
15
|
+
* 1. A module URL is a SERVE LOCATION, not a path under the site's mount
|
|
16
|
+
* point. A host may serve a site under one subpath and serve its
|
|
17
|
+
* foundation from an entirely different root. A loader that prefixed every
|
|
18
|
+
* root-relative module URL with the site's base would corrupt exactly that
|
|
19
|
+
* case. Serve locations are read, never constructed.
|
|
20
|
+
*
|
|
21
|
+
* 2. The loader's only available base was `import.meta.env.BASE_URL`, a
|
|
22
|
+
* BUILD-TIME constant of whichever bundle the runtime shipped in. The
|
|
23
|
+
* framework already ruled that the wrong authority for the sibling problem
|
|
24
|
+
* — `setup.js buildDefaultFetcher()` prefers the payload's
|
|
25
|
+
* `content.config.base` precisely because a host-delivered runtime cannot
|
|
26
|
+
* know from a build-time constant what subpath its host serves under.
|
|
27
|
+
*
|
|
28
|
+
* Until 2026-08-04 the base step lived in `loadExtensions()` in the runtime and
|
|
29
|
+
* was applied to extensions but NOT to the primary foundation, so one string
|
|
30
|
+
* resolved to two places depending on which slot it sat in. It was harmless
|
|
31
|
+
* only by coincidence: on a bundled static site BASE_URL *is* the site's base,
|
|
32
|
+
* and on a hosted site it is '/' so the step was inert. Two meanings on one
|
|
33
|
+
* variable, agreeing by luck on the only two lanes that existed.
|
|
34
|
+
*
|
|
35
|
+
* Consumers of the resolved value, which is why this lives in one module:
|
|
36
|
+
* - the payload's `config.extensions` (content-collector) — what the browser loads
|
|
37
|
+
* - the `<link rel=modulepreload>` hints (site/config.js) — must match the
|
|
38
|
+
* payload exactly, or the preload warms a URL the runtime never requests
|
|
39
|
+
* - SSG prerender (prerender.js) maps back to a filesystem path, via
|
|
40
|
+
* `stripBasePath()` below
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Root-relative means "starts at the origin root" — `/foo`. A protocol-relative
|
|
45
|
+
* URL (`//cdn.example.com/foo`) is ABSOLUTE and must never be prefixed; the
|
|
46
|
+
* runtime's own resolver treats it as absolute too.
|
|
47
|
+
*/
|
|
48
|
+
function isRootRelative(value) {
|
|
49
|
+
return typeof value === 'string' && value.startsWith('/') && !value.startsWith('//')
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function joinBase(base, url) {
|
|
53
|
+
const prefix = base.endsWith('/') ? base : `${base}/`
|
|
54
|
+
return prefix + url.slice(1)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Apply a deployment base to a module source, if it is root-relative.
|
|
59
|
+
*
|
|
60
|
+
* Accepts the same union the runtime's loader does: a URL string, or a
|
|
61
|
+
* `{ url, cssUrl }` object. Both fields are resolved — an explicit
|
|
62
|
+
* root-relative `cssUrl` needs the base exactly as much as `url` does, and
|
|
63
|
+
* missing it produces a silent 404 (the runtime tolerates a failed stylesheet
|
|
64
|
+
* by design, so the only symptom is an unstyled foundation).
|
|
65
|
+
*
|
|
66
|
+
* Anything that is not a root-relative URL — an absolute URL, a
|
|
67
|
+
* protocol-relative URL, a relative path, a registry ref like `@org/name@1.2.3`
|
|
68
|
+
* — passes through untouched.
|
|
69
|
+
*
|
|
70
|
+
* @param {string|Object} source - URL string or {url, cssUrl} object
|
|
71
|
+
* @param {string} [base] - Deployment base path ('/' or absent means no-op)
|
|
72
|
+
* @returns {string|Object} The source with the base applied where applicable
|
|
73
|
+
*/
|
|
74
|
+
export function resolveModuleUrl(source, base) {
|
|
75
|
+
if (!base || base === '/') return source
|
|
76
|
+
|
|
77
|
+
if (typeof source === 'string') {
|
|
78
|
+
return isRootRelative(source) ? joinBase(base, source) : source
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (source && typeof source === 'object') {
|
|
82
|
+
if (!isRootRelative(source.url) && !isRootRelative(source.cssUrl)) return source
|
|
83
|
+
const out = { ...source }
|
|
84
|
+
if (isRootRelative(out.url)) out.url = joinBase(base, out.url)
|
|
85
|
+
if (isRootRelative(out.cssUrl)) out.cssUrl = joinBase(base, out.cssUrl)
|
|
86
|
+
return out
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return source
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Map every entry of a site's `extensions:` list through {@link resolveModuleUrl}.
|
|
94
|
+
* Returns the input untouched when there is nothing to do, so a payload built
|
|
95
|
+
* without a base is byte-identical to before.
|
|
96
|
+
*
|
|
97
|
+
* @param {Array<string|Object>} [extensions]
|
|
98
|
+
* @param {string} [base]
|
|
99
|
+
* @returns {Array<string|Object>|undefined}
|
|
100
|
+
*/
|
|
101
|
+
export function resolveExtensionUrls(extensions, base) {
|
|
102
|
+
if (!Array.isArray(extensions) || extensions.length === 0) return extensions
|
|
103
|
+
if (!base || base === '/') return extensions
|
|
104
|
+
return extensions.map((entry) => resolveModuleUrl(entry, base))
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Remove a deployment base from a URL, for consumers that need to map a served
|
|
109
|
+
* URL back onto the build tree — SSG prerender resolves `/docs/effects/entry.js`
|
|
110
|
+
* against `dist/`, where the file is at `effects/entry.js`.
|
|
111
|
+
*
|
|
112
|
+
* A URL that does not carry the base is returned unchanged, so this is safe
|
|
113
|
+
* against a payload produced before the base was applied at build time.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} url
|
|
116
|
+
* @param {string} [base]
|
|
117
|
+
* @returns {string}
|
|
118
|
+
*/
|
|
119
|
+
export function stripBasePath(url, base) {
|
|
120
|
+
if (!base || base === '/' || typeof url !== 'string') return url
|
|
121
|
+
const prefix = base.endsWith('/') ? base : `${base}/`
|
|
122
|
+
return url.startsWith(prefix) ? `/${url.slice(prefix.length)}` : url
|
|
123
|
+
}
|