@uniweb/build 0.8.19 → 0.8.20
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 +6 -5
- package/src/import-map-plugin.js +145 -0
- package/src/site/config.js +61 -92
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uniweb/build",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.20",
|
|
4
4
|
"description": "Build tooling for the Uniweb Component Web Platform",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
"./dev": "./src/dev/index.js",
|
|
18
18
|
"./prerender": "./src/prerender.js",
|
|
19
19
|
"./i18n": "./src/i18n/index.js",
|
|
20
|
-
"./search": "./src/search/index.js"
|
|
20
|
+
"./search": "./src/search/index.js",
|
|
21
|
+
"./import-map-plugin": "./src/import-map-plugin.js"
|
|
21
22
|
},
|
|
22
23
|
"files": [
|
|
23
24
|
"src"
|
|
@@ -52,9 +53,9 @@
|
|
|
52
53
|
"@uniweb/theming": "0.1.2"
|
|
53
54
|
},
|
|
54
55
|
"optionalDependencies": {
|
|
55
|
-
"@uniweb/schemas": "0.2.1",
|
|
56
56
|
"@uniweb/content-reader": "1.1.4",
|
|
57
|
-
"@uniweb/runtime": "0.6.
|
|
57
|
+
"@uniweb/runtime": "0.6.15",
|
|
58
|
+
"@uniweb/schemas": "0.2.1"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
60
61
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
|
|
@@ -63,7 +64,7 @@
|
|
|
63
64
|
"@tailwindcss/vite": "^4.0.0",
|
|
64
65
|
"@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
|
|
65
66
|
"vite-plugin-svgr": "^4.0.0",
|
|
66
|
-
"@uniweb/core": "0.5.
|
|
67
|
+
"@uniweb/core": "0.5.14"
|
|
67
68
|
},
|
|
68
69
|
"peerDependenciesMeta": {
|
|
69
70
|
"vite": {
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Import Map Plugin
|
|
3
|
+
*
|
|
4
|
+
* Shared Vite plugin that emits import-map bridge modules so that
|
|
5
|
+
* foundations loaded via dynamic import() can resolve bare specifiers
|
|
6
|
+
* (react, @uniweb/core, etc.) to the same instances used by the host app.
|
|
7
|
+
*
|
|
8
|
+
* Production: emits deterministic chunks at _importmap/*.js with explicit
|
|
9
|
+
* named re-exports, and injects a <script type="importmap"> into the HTML.
|
|
10
|
+
*
|
|
11
|
+
* Used by:
|
|
12
|
+
* - Site builds (runtime mode + extensions) — packages/build/src/site/config.js
|
|
13
|
+
* - Runtime shell build — packages/runtime/vite.config.app.js
|
|
14
|
+
* - Dynamic-runtime (editor preview) — packages/uniweb-editor/dynamic-runtime/
|
|
15
|
+
*
|
|
16
|
+
* @module @uniweb/build/import-map-plugin
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/** Default externals shared between foundations and hosts */
|
|
20
|
+
const DEFAULT_EXTERNALS = [
|
|
21
|
+
'react',
|
|
22
|
+
'react-dom',
|
|
23
|
+
'react/jsx-runtime',
|
|
24
|
+
'react/jsx-dev-runtime',
|
|
25
|
+
'@uniweb/core',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const IMPORT_MAP_PREFIX = '\0importmap:'
|
|
29
|
+
|
|
30
|
+
/** Valid JS identifier — filters out non-identifier keys from CJS modules */
|
|
31
|
+
const isValidId = (k) => /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(k)
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create the import map Vite plugin.
|
|
35
|
+
*
|
|
36
|
+
* @param {Object} [options]
|
|
37
|
+
* @param {string[]} [options.externals] - Package specifiers to bridge (default: react, react-dom, @uniweb/core, etc.)
|
|
38
|
+
* @param {string} [options.name] - Plugin name (default: 'uniweb:import-map')
|
|
39
|
+
* @param {string} [options.basePath] - Base path prefix for import map URLs in HTML (default: '/')
|
|
40
|
+
* @param {string} [options.resolveFrom] - Absolute path to resolve bare specifiers from inside virtual modules.
|
|
41
|
+
* Needed when the host project doesn't have the externals as direct dependencies (e.g., site builds
|
|
42
|
+
* under pnpm strict mode resolve from the foundation directory instead).
|
|
43
|
+
* @param {Object} [options.devBridges] - Map of specifier → dev-mode URL for import map injection in dev.
|
|
44
|
+
* When provided, the import map is injected in both dev and prod (with different URLs).
|
|
45
|
+
* When omitted, the import map is only injected in prod (dev uses other mechanisms like transformRequest).
|
|
46
|
+
* @returns {import('vite').Plugin}
|
|
47
|
+
*/
|
|
48
|
+
export function importMapPlugin({
|
|
49
|
+
externals = DEFAULT_EXTERNALS,
|
|
50
|
+
name = 'uniweb:import-map',
|
|
51
|
+
basePath = '/',
|
|
52
|
+
resolveFrom,
|
|
53
|
+
devBridges,
|
|
54
|
+
} = {}) {
|
|
55
|
+
let isBuild = false
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name,
|
|
59
|
+
|
|
60
|
+
configResolved(config) {
|
|
61
|
+
isBuild = config.command === 'build'
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
resolveId(id, importer) {
|
|
65
|
+
if (id.startsWith(IMPORT_MAP_PREFIX)) return id
|
|
66
|
+
// Bare specifiers inside our virtual modules (e.g. '@uniweb/core' re-exported
|
|
67
|
+
// from '\0importmap:@uniweb/core') can't be resolved by Rollup because virtual
|
|
68
|
+
// modules have no filesystem context. When a resolveFrom path is provided,
|
|
69
|
+
// resolve from there (e.g. the foundation directory under pnpm strict mode).
|
|
70
|
+
if (resolveFrom && importer?.startsWith(IMPORT_MAP_PREFIX) && externals.includes(id)) {
|
|
71
|
+
return this.resolve(id, resolveFrom, { skipSelf: true })
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
|
|
75
|
+
async load(id) {
|
|
76
|
+
if (!id.startsWith(IMPORT_MAP_PREFIX)) return
|
|
77
|
+
const pkg = id.slice(IMPORT_MAP_PREFIX.length)
|
|
78
|
+
|
|
79
|
+
// Generate explicit named re-exports (not `export *`) because CJS
|
|
80
|
+
// packages like React only expose a default via `export *`, losing
|
|
81
|
+
// individual named exports (useState, jsx, etc.) that foundations need.
|
|
82
|
+
try {
|
|
83
|
+
const mod = await import(pkg)
|
|
84
|
+
const names = Object.keys(mod).filter((k) => k !== '__esModule' && isValidId(k))
|
|
85
|
+
const hasDefault = 'default' in mod
|
|
86
|
+
const named = names.filter((k) => k !== 'default')
|
|
87
|
+
const lines = []
|
|
88
|
+
if (named.length) {
|
|
89
|
+
lines.push(`export { ${named.join(', ')} } from '${pkg}'`)
|
|
90
|
+
}
|
|
91
|
+
if (hasDefault) {
|
|
92
|
+
lines.push(`export { default } from '${pkg}'`)
|
|
93
|
+
}
|
|
94
|
+
return lines.join('\n') || 'export {}'
|
|
95
|
+
} catch {
|
|
96
|
+
// Fallback: generic re-export (may not preserve named exports for CJS)
|
|
97
|
+
return `export * from '${pkg}'`
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// Emit deterministic chunks for each external (production only).
|
|
102
|
+
// preserveSignature: 'exports-only' tells Rollup to preserve the original
|
|
103
|
+
// export names (useState, jsx, etc.) instead of mangling them.
|
|
104
|
+
buildStart() {
|
|
105
|
+
if (!isBuild) return
|
|
106
|
+
for (const ext of externals) {
|
|
107
|
+
this.emitFile({
|
|
108
|
+
type: 'chunk',
|
|
109
|
+
id: `${IMPORT_MAP_PREFIX}${ext}`,
|
|
110
|
+
fileName: `_importmap/${ext.replace(/\//g, '-')}.js`,
|
|
111
|
+
preserveSignature: 'exports-only',
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
// Inject the import map into the HTML.
|
|
117
|
+
// In prod: always injects with basePath-prefixed _importmap/ URLs.
|
|
118
|
+
// In dev: only injects if devBridges are provided (otherwise, the consumer
|
|
119
|
+
// handles dev-mode resolution via other mechanisms like transformRequest).
|
|
120
|
+
transformIndexHtml: {
|
|
121
|
+
order: 'pre',
|
|
122
|
+
handler(html) {
|
|
123
|
+
const imports = {}
|
|
124
|
+
|
|
125
|
+
if (isBuild) {
|
|
126
|
+
for (const ext of externals) {
|
|
127
|
+
imports[ext] = `${basePath}_importmap/${ext.replace(/\//g, '-')}.js`
|
|
128
|
+
}
|
|
129
|
+
} else if (devBridges) {
|
|
130
|
+
Object.assign(imports, devBridges)
|
|
131
|
+
} else {
|
|
132
|
+
// No dev injection — consumer handles dev mode separately
|
|
133
|
+
return html
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const importMap = JSON.stringify({ imports }, null, 2)
|
|
137
|
+
const script = ` <script type="importmap">\n${importMap}\n </script>\n`
|
|
138
|
+
// Import map must appear before any module scripts
|
|
139
|
+
return html.replace('<head>', '<head>\n' + script)
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export { DEFAULT_EXTERNALS }
|
package/src/site/config.js
CHANGED
|
@@ -24,6 +24,7 @@ import { existsSync, readFileSync } from 'node:fs'
|
|
|
24
24
|
import { resolve, dirname, join } from 'node:path'
|
|
25
25
|
import yaml from 'js-yaml'
|
|
26
26
|
import { generateEntryPoint, shouldRegenerateForFile } from '../generate-entry.js'
|
|
27
|
+
import { importMapPlugin } from '../import-map-plugin.js'
|
|
27
28
|
|
|
28
29
|
/**
|
|
29
30
|
* Normalize a base path for Vite compatibility
|
|
@@ -343,105 +344,73 @@ export async function defineSiteConfig(options = {}) {
|
|
|
343
344
|
|
|
344
345
|
if (noopFoundationPlugin) plugins.push(noopFoundationPlugin)
|
|
345
346
|
|
|
346
|
-
// Import map plugin for runtime mode production builds
|
|
347
|
+
// Import map plugin for runtime mode production builds.
|
|
347
348
|
// Emits re-export modules for each externalized package (react, @uniweb/core, etc.)
|
|
348
|
-
// so the browser can resolve bare specifiers in the dynamically-imported foundation
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
return {
|
|
362
|
-
name: 'uniweb:import-map',
|
|
363
|
-
|
|
364
|
-
configResolved(config) {
|
|
365
|
-
isBuild = config.command === 'build'
|
|
366
|
-
},
|
|
367
|
-
|
|
368
|
-
resolveId(id, importer) {
|
|
369
|
-
if (id.startsWith(IMPORT_MAP_PREFIX)) return id
|
|
370
|
-
// Bare specifiers inside our virtual modules (e.g. '@uniweb/core' re-exported
|
|
371
|
-
// from '\0importmap:@uniweb/core') can't be resolved by Rollup because virtual
|
|
372
|
-
// modules have no filesystem context. Resolve from the foundation directory where
|
|
373
|
-
// @uniweb/core is a direct dependency (the site may not have it under pnpm strict).
|
|
374
|
-
if (importer?.startsWith(IMPORT_MAP_PREFIX) && IMPORT_MAP_EXTERNALS.includes(id)) {
|
|
375
|
-
const resolveFrom = foundationInfo.path
|
|
376
|
-
? resolve(foundationInfo.path, 'package.json')
|
|
377
|
-
: resolve(siteRoot, 'main.js')
|
|
378
|
-
return this.resolve(id, resolveFrom, { skipSelf: true })
|
|
379
|
-
}
|
|
380
|
-
},
|
|
349
|
+
// so the browser can resolve bare specifiers in the dynamically-imported foundation.
|
|
350
|
+
// In dev mode, Vite's transformRequest() handles bare specifier resolution instead.
|
|
351
|
+
if (needsImportMap) {
|
|
352
|
+
plugins.push(importMapPlugin({
|
|
353
|
+
basePath: base || '/',
|
|
354
|
+
// Under pnpm strict mode, the site may not have @uniweb/core in its own
|
|
355
|
+
// node_modules. Resolve from the foundation directory where it's a direct dep.
|
|
356
|
+
resolveFrom: foundationInfo.path
|
|
357
|
+
? resolve(foundationInfo.path, 'package.json')
|
|
358
|
+
: resolve(siteRoot, 'main.js'),
|
|
359
|
+
}))
|
|
360
|
+
}
|
|
381
361
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
const
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
362
|
+
// Preload hints for runtime-loaded foundations and extensions.
|
|
363
|
+
// In runtime mode, foundation JS is loaded via import() and CSS is injected
|
|
364
|
+
// dynamically in JavaScript — the browser doesn't discover them until JS executes.
|
|
365
|
+
// These <link> tags let the browser start fetching during HTML parsing.
|
|
366
|
+
// Shell mode is excluded: URLs come from __DATA__ at serve time (unicloud handles it).
|
|
367
|
+
if (isRuntimeMode && !isShellMode) {
|
|
368
|
+
plugins.push({
|
|
369
|
+
name: 'uniweb:foundation-preload',
|
|
370
|
+
transformIndexHtml: {
|
|
371
|
+
order: 'post',
|
|
372
|
+
handler() {
|
|
373
|
+
const tags = []
|
|
374
|
+
|
|
375
|
+
// Foundation JS modulepreload
|
|
376
|
+
if (foundationConfig.url) {
|
|
377
|
+
tags.push({
|
|
378
|
+
tag: 'link',
|
|
379
|
+
attrs: { rel: 'modulepreload', href: foundationConfig.url },
|
|
380
|
+
injectTo: 'head',
|
|
381
|
+
})
|
|
400
382
|
}
|
|
401
|
-
return lines.join('\n') || `export {}`
|
|
402
|
-
} catch {
|
|
403
|
-
// Fallback: generic re-export (may not preserve named exports for CJS)
|
|
404
|
-
return `export * from '${pkg}'`
|
|
405
|
-
}
|
|
406
|
-
},
|
|
407
383
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
fileName: `_importmap/${ext.replace(/\//g, '-')}.js`,
|
|
419
|
-
preserveSignature: 'exports-only'
|
|
420
|
-
})
|
|
421
|
-
}
|
|
422
|
-
},
|
|
384
|
+
// Foundation CSS — injected as a real <link> so the browser fetches it
|
|
385
|
+
// during HTML parsing instead of waiting for loadFoundationCSS() in JS.
|
|
386
|
+
// The runtime's dynamic <link> deduplicates (same URL, already cached).
|
|
387
|
+
if (foundationConfig.cssUrl) {
|
|
388
|
+
tags.push({
|
|
389
|
+
tag: 'link',
|
|
390
|
+
attrs: { rel: 'stylesheet', href: foundationConfig.cssUrl },
|
|
391
|
+
injectTo: 'head',
|
|
392
|
+
})
|
|
393
|
+
}
|
|
423
394
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
395
|
+
// Extension JS modulepreload (CSS left to runtime — we can't reliably
|
|
396
|
+
// derive CSS URLs for all extension formats)
|
|
397
|
+
const extensions = siteConfig.extensions || []
|
|
398
|
+
for (const ext of extensions) {
|
|
399
|
+
const url = typeof ext === 'string' ? ext : ext?.url
|
|
400
|
+
if (url) {
|
|
401
|
+
tags.push({
|
|
402
|
+
tag: 'link',
|
|
403
|
+
attrs: { rel: 'modulepreload', href: url },
|
|
404
|
+
injectTo: 'head',
|
|
405
|
+
})
|
|
406
|
+
}
|
|
434
407
|
}
|
|
435
|
-
const importMap = JSON.stringify({ imports }, null, 2)
|
|
436
|
-
const script = ` <script type="importmap">\n${importMap}\n </script>\n`
|
|
437
|
-
// Import map must appear before any module scripts
|
|
438
|
-
return html.replace('<head>', '<head>\n' + script)
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
}
|
|
442
|
-
})() : null
|
|
443
408
|
|
|
444
|
-
|
|
409
|
+
return tags
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
})
|
|
413
|
+
}
|
|
445
414
|
|
|
446
415
|
// Build foundation config for runtime
|
|
447
416
|
const foundationConfig = {
|