@techninja/clearstack 0.3.47 → 0.3.48
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/lib/build-modulepreload.js +50 -17
- package/package.json +1 -1
|
@@ -2,21 +2,33 @@
|
|
|
2
2
|
* Module preload builder — statically crawls ES module imports and injects
|
|
3
3
|
* <link rel="modulepreload"> tags into index.html so the browser fetches all
|
|
4
4
|
* modules in parallel instead of chaining waterfall requests.
|
|
5
|
+
*
|
|
6
|
+
* Importmap safety: modules that directly import bare specifiers are excluded
|
|
7
|
+
* from preload (the browser can race importmap registration when parsing
|
|
8
|
+
* preloaded modules). Bare specifier targets (vendor files) are preloaded
|
|
9
|
+
* first so they are in the module cache before any dependent module runs.
|
|
5
10
|
* @module lib/build-modulepreload
|
|
6
11
|
*/
|
|
7
12
|
|
|
8
13
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
9
14
|
import { resolve, dirname, relative, extname } from 'node:path';
|
|
10
15
|
|
|
16
|
+
/** Extract the importmap object from an HTML string, or return {}. */
|
|
17
|
+
function parseImportMap(html) {
|
|
18
|
+
const m = html.match(/<script type="importmap">([\s\S]*?)<\/script>/);
|
|
19
|
+
try { return m ? JSON.parse(m[1]).imports ?? {} : {}; } catch { return {}; }
|
|
20
|
+
}
|
|
21
|
+
|
|
11
22
|
/**
|
|
12
|
-
* @param {string} srcDir
|
|
13
|
-
* @param {string} entryFile
|
|
14
|
-
* @param {string[]} ignoreDirs
|
|
15
|
-
* @returns {string[]
|
|
23
|
+
* @param {string} srcDir
|
|
24
|
+
* @param {string} entryFile
|
|
25
|
+
* @param {string[]} ignoreDirs
|
|
26
|
+
* @returns {{ order: string[], hasBareImport: Set<string> }}
|
|
16
27
|
*/
|
|
17
28
|
export function crawlModules(srcDir, entryFile, ignoreDirs = ['vendor', 'deps']) {
|
|
18
29
|
const visited = new Set();
|
|
19
30
|
const order = [];
|
|
31
|
+
const hasBareImport = new Set();
|
|
20
32
|
|
|
21
33
|
/**
|
|
22
34
|
*
|
|
@@ -35,25 +47,28 @@ export function crawlModules(srcDir, entryFile, ignoreDirs = ['vendor', 'deps'])
|
|
|
35
47
|
while ((m = importRe.exec(src)) !== null) {
|
|
36
48
|
const spec = m[1];
|
|
37
49
|
if (spec.startsWith('#')) {
|
|
38
|
-
|
|
39
|
-
const mapped = resolveAlias(spec, relPath);
|
|
50
|
+
const mapped = resolveAlias(spec);
|
|
40
51
|
if (mapped) crawl(mapped);
|
|
41
52
|
} else if (spec.startsWith('./') || spec.startsWith('../')) {
|
|
42
53
|
const abs = resolve(dirname(resolve(srcDir, relPath)), spec);
|
|
43
54
|
const rel = relative(srcDir, abs.endsWith('.js') ? abs : abs + '.js');
|
|
44
55
|
crawl(rel);
|
|
56
|
+
} else {
|
|
57
|
+
// Bare specifier — mark this module as unsafe to preload
|
|
58
|
+
hasBareImport.add(relPath);
|
|
45
59
|
}
|
|
46
|
-
// Skip bare specifiers (vendor/hybrids handled separately)
|
|
47
60
|
}
|
|
48
61
|
order.push(relPath);
|
|
49
62
|
}
|
|
50
63
|
|
|
51
64
|
crawl(entryFile);
|
|
52
|
-
return order;
|
|
65
|
+
return { order, hasBareImport };
|
|
53
66
|
}
|
|
54
67
|
|
|
55
|
-
/**
|
|
56
|
-
|
|
68
|
+
/**
|
|
69
|
+
*
|
|
70
|
+
*/
|
|
71
|
+
function resolveAlias(spec) {
|
|
57
72
|
const aliases = {
|
|
58
73
|
'#store/': 'store/',
|
|
59
74
|
'#utils/': 'utils/',
|
|
@@ -71,7 +86,9 @@ function resolveAlias(spec, fromFile) {
|
|
|
71
86
|
|
|
72
87
|
/**
|
|
73
88
|
* Inject modulepreload tags into index.html.
|
|
74
|
-
*
|
|
89
|
+
* Vendor files (importmap targets) are preloaded first, then app modules
|
|
90
|
+
* that contain no bare specifier imports.
|
|
91
|
+
* @param {{ projectDir: string, srcDir?: string, outDir?: string, entry?: string, ignore?: string[], hashSuffix?: string }} opts
|
|
75
92
|
* @returns {{ modules: number }}
|
|
76
93
|
*/
|
|
77
94
|
export function buildModulePreload(opts) {
|
|
@@ -81,6 +98,7 @@ export function buildModulePreload(opts) {
|
|
|
81
98
|
outDir = resolve(projectDir, 'dist'),
|
|
82
99
|
entry = 'router/index.js',
|
|
83
100
|
ignore = ['vendor', 'deps'],
|
|
101
|
+
hashSuffix = '',
|
|
84
102
|
} = opts;
|
|
85
103
|
|
|
86
104
|
const indexPath = resolve(outDir, 'index.html');
|
|
@@ -89,17 +107,32 @@ export function buildModulePreload(opts) {
|
|
|
89
107
|
return { modules: 0 };
|
|
90
108
|
}
|
|
91
109
|
|
|
92
|
-
const
|
|
93
|
-
const
|
|
110
|
+
const html0 = readFileSync(indexPath, 'utf-8');
|
|
111
|
+
const importMap = parseImportMap(html0);
|
|
112
|
+
|
|
113
|
+
const { order, hasBareImport } = crawlModules(srcDir, entry, ignore);
|
|
114
|
+
|
|
115
|
+
// Vendor files from importmap — preload these first (no bare imports inside them)
|
|
116
|
+
const vendorPaths = [...new Set(Object.values(importMap))]
|
|
117
|
+
.filter((v) => v.startsWith('/') && v.endsWith('.js'));
|
|
118
|
+
|
|
119
|
+
// App modules safe to preload — exclude any that directly import a bare specifier
|
|
120
|
+
const appPaths = order.filter((m) => !hasBareImport.has(m));
|
|
121
|
+
|
|
122
|
+
const v = hashSuffix ? `?v=${hashSuffix}` : '';
|
|
123
|
+
const tags = [
|
|
124
|
+
...vendorPaths.map((p) => ` <link rel="modulepreload" href="${p}${v}">`),
|
|
125
|
+
...appPaths.map((m) => ` <link rel="modulepreload" href="/${m}${v}">`),
|
|
126
|
+
].join('\n');
|
|
94
127
|
|
|
95
|
-
let html =
|
|
128
|
+
let html = html0;
|
|
96
129
|
if (html.includes('rel="modulepreload"')) {
|
|
97
|
-
// Remove existing preload tags before re-injecting
|
|
98
130
|
html = html.replace(/\s*<link rel="modulepreload"[^>]*>/g, '');
|
|
99
131
|
}
|
|
100
132
|
html = html.replace('</head>', `${tags}\n</head>`);
|
|
101
133
|
writeFileSync(indexPath, html);
|
|
102
134
|
|
|
103
|
-
|
|
104
|
-
|
|
135
|
+
const skipped = hasBareImport.size;
|
|
136
|
+
console.log(`✅ Modulepreload: ${vendorPaths.length} vendor + ${appPaths.length} app modules → dist/index.html (${skipped} skipped — bare imports)`);
|
|
137
|
+
return { modules: vendorPaths.length + appPaths.length };
|
|
105
138
|
}
|
package/package.json
CHANGED