@esmx/core 3.0.0-rc.70 → 3.0.0-rc.71
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/README.md +10 -7
- package/README.zh-CN.md +10 -7
- package/dist/cli/cli.mjs +9 -4
- package/dist/core.mjs +2 -2
- package/dist/utils/import-map.d.ts +2 -0
- package/dist/utils/import-map.mjs +50 -0
- package/dist/utils/import-map.test.mjs +159 -13
- package/package.json +9 -9
- package/src/cli/cli.ts +11 -5
- package/src/core.ts +2 -2
- package/src/utils/import-map.test.ts +173 -15
- package/src/utils/import-map.ts +61 -0
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
</a>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
|
-
<p>A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module
|
|
23
|
+
<p>A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module Linking capabilities</p>
|
|
24
24
|
|
|
25
25
|
<p>
|
|
26
26
|
English | <a href="https://github.com/esmnext/esmx/blob/master/packages/core/README.zh-CN.md">中文</a>
|
|
@@ -38,16 +38,19 @@
|
|
|
38
38
|
## 📦 Installation
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
+
# npm
|
|
41
42
|
npm install @esmx/core
|
|
42
|
-
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
# pnpm
|
|
45
|
+
pnpm add @esmx/core
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
# yarn
|
|
48
|
+
yarn add @esmx/core
|
|
48
49
|
```
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
## 🚀 Quick Start
|
|
52
|
+
|
|
53
|
+
See Quick Start guide: https://esmx.dev/guide/start/getting-started.html
|
|
51
54
|
|
|
52
55
|
## 📚 Documentation
|
|
53
56
|
|
|
@@ -55,4 +58,4 @@ Visit the [official documentation](https://esmx.dev) for detailed usage guides a
|
|
|
55
58
|
|
|
56
59
|
## 📄 License
|
|
57
60
|
|
|
58
|
-
MIT © [Esmx Team](https://github.com/esmnext/esmx)
|
|
61
|
+
MIT © [Esmx Team](https://github.com/esmnext/esmx)
|
package/README.zh-CN.md
CHANGED
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
</a>
|
|
21
21
|
</div>
|
|
22
22
|
|
|
23
|
-
<p>支持 Vue、React、Preact、Solid、Svelte 的高性能微前端框架,具备 SSR
|
|
23
|
+
<p>支持 Vue、React、Preact、Solid、Svelte 的高性能微前端框架,具备 SSR 和模块链接能力</p>
|
|
24
24
|
|
|
25
25
|
<p>
|
|
26
26
|
<a href="https://github.com/esmnext/esmx/blob/master/packages/core/README.md">English</a> | 中文
|
|
@@ -38,16 +38,19 @@
|
|
|
38
38
|
## 📦 安装
|
|
39
39
|
|
|
40
40
|
```bash
|
|
41
|
+
# npm
|
|
41
42
|
npm install @esmx/core
|
|
42
|
-
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
# pnpm
|
|
45
|
+
pnpm add @esmx/core
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
# yarn
|
|
48
|
+
yarn add @esmx/core
|
|
48
49
|
```
|
|
49
50
|
|
|
50
|
-
|
|
51
|
+
## 🚀 快速开始
|
|
52
|
+
|
|
53
|
+
查看快速开始指南:https://esmx.dev/zh-CN/guide/start/getting-started.html
|
|
51
54
|
|
|
52
55
|
## 📚 文档
|
|
53
56
|
|
|
@@ -55,4 +58,4 @@ npm install @esmx/core
|
|
|
55
58
|
|
|
56
59
|
## 📄 许可证
|
|
57
60
|
|
|
58
|
-
MIT © [Esmx Team](https://github.com/esmnext/esmx)
|
|
61
|
+
MIT © [Esmx Team](https://github.com/esmnext/esmx)
|
package/dist/cli/cli.mjs
CHANGED
|
@@ -10,12 +10,17 @@ async function getSrcOptions() {
|
|
|
10
10
|
}
|
|
11
11
|
async function getDistOptions() {
|
|
12
12
|
try {
|
|
13
|
-
|
|
13
|
+
const m = await import(resolveImportPath(process.cwd(), "./dist/node/src/entry.node.mjs"));
|
|
14
|
+
return m.default;
|
|
14
15
|
} catch (e) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
console.error(
|
|
17
|
+
styleText(
|
|
18
|
+
"red",
|
|
19
|
+
"Failed to load dist entry: dist/node/src/entry.node.mjs"
|
|
20
|
+
)
|
|
18
21
|
);
|
|
22
|
+
console.error(styleText("yellow", "Run `esmx build` and try again."));
|
|
23
|
+
process.exit(17);
|
|
19
24
|
}
|
|
20
25
|
}
|
|
21
26
|
export async function cli(command) {
|
package/dist/core.mjs
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
parsePackConfig
|
|
15
15
|
} from "./pack-config.mjs";
|
|
16
16
|
import { createCache } from "./utils/cache.mjs";
|
|
17
|
-
import { createImportMap } from "./utils/import-map.mjs";
|
|
17
|
+
import { createClientImportMap, createImportMap } from "./utils/import-map.mjs";
|
|
18
18
|
import { resolvePath } from "./utils/resolve-path.mjs";
|
|
19
19
|
import { getImportPreloadInfo as getStaticImportPaths } from "./utils/static-import-lexer.mjs";
|
|
20
20
|
export var COMMAND = /* @__PURE__ */ ((COMMAND2) => {
|
|
@@ -677,7 +677,7 @@ export class Esmx {
|
|
|
677
677
|
let json = {};
|
|
678
678
|
switch (env) {
|
|
679
679
|
case "client": {
|
|
680
|
-
json =
|
|
680
|
+
json = createClientImportMap({
|
|
681
681
|
manifests,
|
|
682
682
|
getScope(name, scope) {
|
|
683
683
|
return `/${name}${scope}`;
|
|
@@ -47,4 +47,6 @@ export declare function createScopesMap(imports: SpecifierMap, manifests: readon
|
|
|
47
47
|
* @see https://issues.chromium.org/issues/453147451
|
|
48
48
|
*/
|
|
49
49
|
export declare function fixImportMapNestedScopes(importMap: Required<ImportMap>): Required<ImportMap>;
|
|
50
|
+
export declare function compressImportMap(importMap: Required<ImportMap>): ImportMap;
|
|
50
51
|
export declare function createImportMap({ manifests, getFile, getScope }: GetImportMapOptions): Required<ImportMap>;
|
|
52
|
+
export declare function createClientImportMap(options: GetImportMapOptions): ImportMap;
|
|
@@ -48,6 +48,51 @@ export function fixImportMapNestedScopes(importMap) {
|
|
|
48
48
|
});
|
|
49
49
|
return importMap;
|
|
50
50
|
}
|
|
51
|
+
export function compressImportMap(importMap) {
|
|
52
|
+
const compressed = {
|
|
53
|
+
imports: { ...importMap.imports },
|
|
54
|
+
scopes: {}
|
|
55
|
+
};
|
|
56
|
+
const counts = {};
|
|
57
|
+
Object.values(importMap.scopes).forEach((scopeMappings) => {
|
|
58
|
+
Object.entries(scopeMappings).forEach(([specifier, target]) => {
|
|
59
|
+
if (Object.hasOwn(importMap.imports, specifier)) return;
|
|
60
|
+
counts[specifier] ??= {};
|
|
61
|
+
counts[specifier][target] = (counts[specifier][target] ?? 0) + 1;
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
Object.entries(counts).forEach(([specifier, targetCounts]) => {
|
|
65
|
+
const entries = Object.entries(targetCounts);
|
|
66
|
+
let best = null;
|
|
67
|
+
let secondBestCount = 0;
|
|
68
|
+
for (const [t, c] of entries) {
|
|
69
|
+
if (!best || c > best[1]) {
|
|
70
|
+
secondBestCount = best ? Math.max(secondBestCount, best[1]) : secondBestCount;
|
|
71
|
+
best = [t, c];
|
|
72
|
+
} else {
|
|
73
|
+
secondBestCount = Math.max(secondBestCount, c);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (best && best[1] > secondBestCount) {
|
|
77
|
+
compressed.imports[specifier] = best[0];
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
Object.entries(importMap.scopes).forEach(([scopePath, scopeMappings]) => {
|
|
81
|
+
const filtered = {};
|
|
82
|
+
Object.entries(scopeMappings).forEach(([specifier, target]) => {
|
|
83
|
+
const globalTarget = compressed.imports[specifier];
|
|
84
|
+
if (globalTarget === target) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
filtered[specifier] = target;
|
|
88
|
+
});
|
|
89
|
+
if (Object.keys(filtered).length > 0) {
|
|
90
|
+
compressed.scopes[scopePath] = filtered;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
const hasScopes = Object.keys(compressed.scopes).length > 0;
|
|
94
|
+
return hasScopes ? compressed : { imports: compressed.imports };
|
|
95
|
+
}
|
|
51
96
|
export function createImportMap({
|
|
52
97
|
manifests,
|
|
53
98
|
getFile,
|
|
@@ -60,3 +105,8 @@ export function createImportMap({
|
|
|
60
105
|
scopes
|
|
61
106
|
};
|
|
62
107
|
}
|
|
108
|
+
export function createClientImportMap(options) {
|
|
109
|
+
const base = createImportMap(options);
|
|
110
|
+
const fixed = fixImportMapNestedScopes(base);
|
|
111
|
+
return compressImportMap(fixed);
|
|
112
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assert, describe, test } from "vitest";
|
|
2
2
|
import {
|
|
3
|
+
compressImportMap,
|
|
3
4
|
createImportMap,
|
|
4
5
|
createImportsMap,
|
|
5
6
|
createScopesMap,
|
|
@@ -391,18 +392,16 @@ describe("fixImportMapNestedScopes", () => {
|
|
|
391
392
|
}
|
|
392
393
|
};
|
|
393
394
|
const result = fixImportMapNestedScopes(importMap);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
{
|
|
402
|
-
vue: "/shared-modules/vue2.q9r8s7t6.final.mjs",
|
|
403
|
-
"vue-router": "/shared-modules/vue2/router.y1z0a9b8.final.mjs"
|
|
395
|
+
const expected = {
|
|
396
|
+
imports: importMap.imports,
|
|
397
|
+
scopes: {
|
|
398
|
+
"/shared-modules/vue2/component.u5v4w3x2.final.mjs": {
|
|
399
|
+
vue: "/shared-modules/vue2.q9r8s7t6.final.mjs",
|
|
400
|
+
"vue-router": "/shared-modules/vue2/router.y1z0a9b8.final.mjs"
|
|
401
|
+
}
|
|
404
402
|
}
|
|
405
|
-
|
|
403
|
+
};
|
|
404
|
+
assert.deepEqual(result, expected);
|
|
406
405
|
});
|
|
407
406
|
test("should handle complex priority scenarios with multiple nested levels", () => {
|
|
408
407
|
const importMap = {
|
|
@@ -609,8 +608,11 @@ describe("fixImportMapNestedScopes", () => {
|
|
|
609
608
|
}
|
|
610
609
|
};
|
|
611
610
|
const result = fixImportMapNestedScopes(importMap);
|
|
612
|
-
|
|
613
|
-
|
|
611
|
+
const expected = {
|
|
612
|
+
imports: importMap.imports,
|
|
613
|
+
scopes: {}
|
|
614
|
+
};
|
|
615
|
+
assert.deepEqual(result, expected);
|
|
614
616
|
assert.doesNotThrow(() => {
|
|
615
617
|
fixImportMapNestedScopes(importMap);
|
|
616
618
|
});
|
|
@@ -1438,3 +1440,147 @@ describe("createImportMap", () => {
|
|
|
1438
1440
|
});
|
|
1439
1441
|
});
|
|
1440
1442
|
});
|
|
1443
|
+
describe("compressImportMap", () => {
|
|
1444
|
+
test("does not promote when no global exists; keeps scopes intact", () => {
|
|
1445
|
+
const importMap = {
|
|
1446
|
+
imports: {},
|
|
1447
|
+
scopes: {
|
|
1448
|
+
"/a/": {
|
|
1449
|
+
vue: "/a/vue.final.mjs"
|
|
1450
|
+
},
|
|
1451
|
+
"/b/": {
|
|
1452
|
+
vue: "/a/vue.final.mjs"
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
};
|
|
1456
|
+
const result = compressImportMap(importMap);
|
|
1457
|
+
assert.deepEqual(result, {
|
|
1458
|
+
imports: { vue: "/a/vue.final.mjs" }
|
|
1459
|
+
});
|
|
1460
|
+
});
|
|
1461
|
+
test("does not promote when scoped mappings conflict across scopes", () => {
|
|
1462
|
+
const importMap = {
|
|
1463
|
+
imports: {},
|
|
1464
|
+
scopes: {
|
|
1465
|
+
"/a/": { vue: "/a/vue.final.mjs" },
|
|
1466
|
+
"/b/": { vue: "/b/vue.final.mjs" }
|
|
1467
|
+
}
|
|
1468
|
+
};
|
|
1469
|
+
const result = compressImportMap(importMap);
|
|
1470
|
+
assert.deepEqual(result, importMap);
|
|
1471
|
+
});
|
|
1472
|
+
test("removes scoped entries that equal global mapping", () => {
|
|
1473
|
+
const importMap = {
|
|
1474
|
+
imports: { vue: "/shared/vue.final.mjs" },
|
|
1475
|
+
scopes: {
|
|
1476
|
+
"/a/": {
|
|
1477
|
+
vue: "/shared/vue.final.mjs",
|
|
1478
|
+
lodash: "/a/lodash.final.mjs"
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
};
|
|
1482
|
+
const result = compressImportMap(importMap);
|
|
1483
|
+
const expected = {
|
|
1484
|
+
imports: {
|
|
1485
|
+
vue: "/shared/vue.final.mjs",
|
|
1486
|
+
lodash: "/a/lodash.final.mjs"
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1489
|
+
assert.deepEqual(result, expected);
|
|
1490
|
+
});
|
|
1491
|
+
test("promotes to global when global matches single unique target across scopes", () => {
|
|
1492
|
+
const importMap = {
|
|
1493
|
+
imports: { vue: "/shared/vue.final.mjs" },
|
|
1494
|
+
scopes: {
|
|
1495
|
+
"/a/": { vue: "/shared/vue.final.mjs" },
|
|
1496
|
+
"/b/": { vue: "/shared/vue.final.mjs" }
|
|
1497
|
+
}
|
|
1498
|
+
};
|
|
1499
|
+
const result = compressImportMap(importMap);
|
|
1500
|
+
assert.deepEqual(result, {
|
|
1501
|
+
imports: { vue: "/shared/vue.final.mjs" }
|
|
1502
|
+
});
|
|
1503
|
+
});
|
|
1504
|
+
test("promotes to global when no global exists and targets are consistent across scopes (promote mode)", () => {
|
|
1505
|
+
const importMap = {
|
|
1506
|
+
imports: {},
|
|
1507
|
+
scopes: {
|
|
1508
|
+
"/a/": { vue: "/x/vue.final.mjs" },
|
|
1509
|
+
"/b/": { vue: "/x/vue.final.mjs" }
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
const result = compressImportMap(importMap);
|
|
1513
|
+
assert.deepEqual(result, {
|
|
1514
|
+
imports: { vue: "/x/vue.final.mjs" }
|
|
1515
|
+
});
|
|
1516
|
+
});
|
|
1517
|
+
test("promotes dominant target and keeps exceptions in scopes (promote mode)", () => {
|
|
1518
|
+
const importMap = {
|
|
1519
|
+
imports: {},
|
|
1520
|
+
scopes: {
|
|
1521
|
+
"/a/": { vue: "/x/vue.final.mjs" },
|
|
1522
|
+
"/b/": { vue: "/x/vue.final.mjs" },
|
|
1523
|
+
"/c/": { vue: "/y/vue2.final.mjs" }
|
|
1524
|
+
}
|
|
1525
|
+
};
|
|
1526
|
+
const result = compressImportMap(importMap);
|
|
1527
|
+
assert.deepEqual(result, {
|
|
1528
|
+
imports: { vue: "/x/vue.final.mjs" },
|
|
1529
|
+
scopes: {
|
|
1530
|
+
"/c/": { vue: "/y/vue2.final.mjs" }
|
|
1531
|
+
}
|
|
1532
|
+
});
|
|
1533
|
+
});
|
|
1534
|
+
test("does not promote when no global exists; keeps scopes intact", () => {
|
|
1535
|
+
const importMap = {
|
|
1536
|
+
imports: {},
|
|
1537
|
+
scopes: {
|
|
1538
|
+
"/a/": { vue: "/shared/vue.final.mjs" },
|
|
1539
|
+
"/b/": { vue: "/shared/vue.final.mjs" },
|
|
1540
|
+
"/c/": { vue: "/c/vue2.final.mjs" }
|
|
1541
|
+
}
|
|
1542
|
+
};
|
|
1543
|
+
const result = compressImportMap(importMap);
|
|
1544
|
+
assert.deepEqual(result, {
|
|
1545
|
+
imports: { vue: "/shared/vue.final.mjs" },
|
|
1546
|
+
scopes: { "/c/": { vue: "/c/vue2.final.mjs" } }
|
|
1547
|
+
});
|
|
1548
|
+
});
|
|
1549
|
+
test("aggressive default does not override different existing global", () => {
|
|
1550
|
+
const importMap = {
|
|
1551
|
+
imports: { vue: "/global/vue.final.mjs" },
|
|
1552
|
+
scopes: {
|
|
1553
|
+
"/a/": { vue: "/shared/vue.final.mjs" },
|
|
1554
|
+
"/b/": { vue: "/shared/vue.final.mjs" }
|
|
1555
|
+
}
|
|
1556
|
+
};
|
|
1557
|
+
const result = compressImportMap(importMap);
|
|
1558
|
+
assert.deepEqual(result, {
|
|
1559
|
+
imports: { vue: "/global/vue.final.mjs" },
|
|
1560
|
+
scopes: {
|
|
1561
|
+
"/a/": { vue: "/shared/vue.final.mjs" },
|
|
1562
|
+
"/b/": { vue: "/shared/vue.final.mjs" }
|
|
1563
|
+
}
|
|
1564
|
+
});
|
|
1565
|
+
});
|
|
1566
|
+
test("returns new object and keeps input unchanged", () => {
|
|
1567
|
+
const original = {
|
|
1568
|
+
imports: {},
|
|
1569
|
+
scopes: {
|
|
1570
|
+
"/a/": { vue: "/x/vue.final.mjs" },
|
|
1571
|
+
"/b/": { vue: "/x/vue.final.mjs" },
|
|
1572
|
+
"/c/": { vue: "/y/vue2.final.mjs" }
|
|
1573
|
+
}
|
|
1574
|
+
};
|
|
1575
|
+
const importMap = JSON.parse(JSON.stringify(original));
|
|
1576
|
+
const result = compressImportMap(importMap);
|
|
1577
|
+
assert.notStrictEqual(result, importMap);
|
|
1578
|
+
assert.notStrictEqual(result.imports, importMap.imports);
|
|
1579
|
+
assert.notStrictEqual(result.scopes, importMap.scopes);
|
|
1580
|
+
assert.deepEqual(importMap, original);
|
|
1581
|
+
assert.deepEqual(result, {
|
|
1582
|
+
imports: { vue: "/x/vue.final.mjs" },
|
|
1583
|
+
scopes: { "/c/": { vue: "/y/vue2.final.mjs" } }
|
|
1584
|
+
});
|
|
1585
|
+
});
|
|
1586
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@esmx/core",
|
|
3
|
-
"description": "A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module
|
|
3
|
+
"description": "A high-performance microfrontend framework supporting Vue, React, Preact, Solid, and Svelte with SSR and Module Linking capabilities.",
|
|
4
4
|
"contributors": [
|
|
5
5
|
{
|
|
6
6
|
"name": "lzxb",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"Microfrontend",
|
|
40
40
|
"SSR",
|
|
41
41
|
"Rspack",
|
|
42
|
-
"Module
|
|
42
|
+
"Module Linking",
|
|
43
43
|
"High Performance",
|
|
44
44
|
"TypeScript"
|
|
45
45
|
],
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"build": "unbuild"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@esmx/import": "3.0.0-rc.
|
|
62
|
+
"@esmx/import": "3.0.0-rc.71",
|
|
63
63
|
"@types/serialize-javascript": "^5.0.4",
|
|
64
64
|
"es-module-lexer": "^1.7.0",
|
|
65
65
|
"find": "^0.3.0",
|
|
@@ -69,15 +69,15 @@
|
|
|
69
69
|
"devDependencies": {
|
|
70
70
|
"@biomejs/biome": "1.9.4",
|
|
71
71
|
"@types/find": "^0.2.4",
|
|
72
|
-
"@types/node": "^24.
|
|
73
|
-
"@types/send": "^
|
|
72
|
+
"@types/node": "^24.10.0",
|
|
73
|
+
"@types/send": "^1.2.1",
|
|
74
74
|
"@types/write": "^2.0.4",
|
|
75
75
|
"@vitest/coverage-v8": "3.2.4",
|
|
76
|
-
"typescript": "5.9.
|
|
77
|
-
"unbuild": "3.6.
|
|
76
|
+
"typescript": "5.9.3",
|
|
77
|
+
"unbuild": "3.6.1",
|
|
78
78
|
"vitest": "3.2.4"
|
|
79
79
|
},
|
|
80
|
-
"version": "3.0.0-rc.
|
|
80
|
+
"version": "3.0.0-rc.71",
|
|
81
81
|
"type": "module",
|
|
82
82
|
"private": false,
|
|
83
83
|
"exports": {
|
|
@@ -100,5 +100,5 @@
|
|
|
100
100
|
"template",
|
|
101
101
|
"public"
|
|
102
102
|
],
|
|
103
|
-
"gitHead": "
|
|
103
|
+
"gitHead": "1616c7a1f820387d4d14bac0babd42356f6f7f33"
|
|
104
104
|
}
|
package/src/cli/cli.ts
CHANGED
|
@@ -13,13 +13,19 @@ async function getSrcOptions(): Promise<EsmxOptions> {
|
|
|
13
13
|
|
|
14
14
|
async function getDistOptions(): Promise<EsmxOptions> {
|
|
15
15
|
try {
|
|
16
|
-
|
|
17
|
-
resolveImportPath(process.cwd(), './dist/
|
|
18
|
-
)
|
|
16
|
+
const m = await import(
|
|
17
|
+
resolveImportPath(process.cwd(), './dist/node/src/entry.node.mjs')
|
|
18
|
+
);
|
|
19
|
+
return m.default;
|
|
19
20
|
} catch (e) {
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
console.error(
|
|
22
|
+
styleText(
|
|
23
|
+
'red',
|
|
24
|
+
'Failed to load dist entry: dist/node/src/entry.node.mjs'
|
|
25
|
+
)
|
|
22
26
|
);
|
|
27
|
+
console.error(styleText('yellow', 'Run `esmx build` and try again.'));
|
|
28
|
+
process.exit(17);
|
|
23
29
|
}
|
|
24
30
|
}
|
|
25
31
|
|
package/src/core.ts
CHANGED
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
import type { ImportmapMode } from './render-context';
|
|
23
23
|
import type { RenderContext, RenderContextOptions } from './render-context';
|
|
24
24
|
import { type CacheHandle, createCache } from './utils/cache';
|
|
25
|
-
import { createImportMap } from './utils/import-map';
|
|
25
|
+
import { createClientImportMap, createImportMap } from './utils/import-map';
|
|
26
26
|
import type { Middleware } from './utils/middleware';
|
|
27
27
|
import { type ProjectPath, resolvePath } from './utils/resolve-path';
|
|
28
28
|
import { getImportPreloadInfo as getStaticImportPaths } from './utils/static-import-lexer';
|
|
@@ -854,7 +854,7 @@ export class Esmx {
|
|
|
854
854
|
let json: ImportMap = {};
|
|
855
855
|
switch (env) {
|
|
856
856
|
case 'client': {
|
|
857
|
-
json =
|
|
857
|
+
json = createClientImportMap({
|
|
858
858
|
manifests,
|
|
859
859
|
getScope(name, scope) {
|
|
860
860
|
return `/${name}${scope}`;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { assert, describe, test } from 'vitest';
|
|
2
2
|
import {
|
|
3
|
+
compressImportMap,
|
|
3
4
|
createImportMap,
|
|
4
5
|
createImportsMap,
|
|
5
6
|
createScopesMap,
|
|
@@ -461,20 +462,17 @@ describe('fixImportMapNestedScopes', () => {
|
|
|
461
462
|
};
|
|
462
463
|
|
|
463
464
|
const result = fixImportMapNestedScopes(importMap);
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
result.scopes['/shared-modules/vue2/component.u5v4w3x2.final.mjs'],
|
|
473
|
-
{
|
|
474
|
-
vue: '/shared-modules/vue2.q9r8s7t6.final.mjs',
|
|
475
|
-
'vue-router': '/shared-modules/vue2/router.y1z0a9b8.final.mjs'
|
|
465
|
+
const expected = {
|
|
466
|
+
imports: importMap.imports,
|
|
467
|
+
scopes: {
|
|
468
|
+
'/shared-modules/vue2/component.u5v4w3x2.final.mjs': {
|
|
469
|
+
vue: '/shared-modules/vue2.q9r8s7t6.final.mjs',
|
|
470
|
+
'vue-router':
|
|
471
|
+
'/shared-modules/vue2/router.y1z0a9b8.final.mjs'
|
|
472
|
+
}
|
|
476
473
|
}
|
|
477
|
-
|
|
474
|
+
};
|
|
475
|
+
assert.deepEqual(result, expected);
|
|
478
476
|
});
|
|
479
477
|
|
|
480
478
|
test('should handle complex priority scenarios with multiple nested levels', () => {
|
|
@@ -747,8 +745,11 @@ describe('fixImportMapNestedScopes', () => {
|
|
|
747
745
|
};
|
|
748
746
|
|
|
749
747
|
const result = fixImportMapNestedScopes(importMap);
|
|
750
|
-
|
|
751
|
-
|
|
748
|
+
const expected = {
|
|
749
|
+
imports: importMap.imports,
|
|
750
|
+
scopes: {}
|
|
751
|
+
};
|
|
752
|
+
assert.deepEqual(result, expected);
|
|
752
753
|
assert.doesNotThrow(() => {
|
|
753
754
|
fixImportMapNestedScopes(importMap);
|
|
754
755
|
});
|
|
@@ -1620,3 +1621,160 @@ describe('createImportMap', () => {
|
|
|
1620
1621
|
});
|
|
1621
1622
|
});
|
|
1622
1623
|
});
|
|
1624
|
+
|
|
1625
|
+
describe('compressImportMap', () => {
|
|
1626
|
+
test('does not promote when no global exists; keeps scopes intact', () => {
|
|
1627
|
+
const importMap = {
|
|
1628
|
+
imports: {},
|
|
1629
|
+
scopes: {
|
|
1630
|
+
'/a/': {
|
|
1631
|
+
vue: '/a/vue.final.mjs'
|
|
1632
|
+
},
|
|
1633
|
+
'/b/': {
|
|
1634
|
+
vue: '/a/vue.final.mjs'
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
};
|
|
1638
|
+
|
|
1639
|
+
const result = compressImportMap(importMap);
|
|
1640
|
+
assert.deepEqual(result, {
|
|
1641
|
+
imports: { vue: '/a/vue.final.mjs' }
|
|
1642
|
+
});
|
|
1643
|
+
});
|
|
1644
|
+
|
|
1645
|
+
test('does not promote when scoped mappings conflict across scopes', () => {
|
|
1646
|
+
const importMap = {
|
|
1647
|
+
imports: {},
|
|
1648
|
+
scopes: {
|
|
1649
|
+
'/a/': { vue: '/a/vue.final.mjs' },
|
|
1650
|
+
'/b/': { vue: '/b/vue.final.mjs' }
|
|
1651
|
+
}
|
|
1652
|
+
};
|
|
1653
|
+
|
|
1654
|
+
const result = compressImportMap(importMap);
|
|
1655
|
+
assert.deepEqual(result, importMap);
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
test('removes scoped entries that equal global mapping', () => {
|
|
1659
|
+
const importMap = {
|
|
1660
|
+
imports: { vue: '/shared/vue.final.mjs' },
|
|
1661
|
+
scopes: {
|
|
1662
|
+
'/a/': {
|
|
1663
|
+
vue: '/shared/vue.final.mjs',
|
|
1664
|
+
lodash: '/a/lodash.final.mjs'
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
};
|
|
1668
|
+
|
|
1669
|
+
const result = compressImportMap(importMap);
|
|
1670
|
+
const expected = {
|
|
1671
|
+
imports: {
|
|
1672
|
+
vue: '/shared/vue.final.mjs',
|
|
1673
|
+
lodash: '/a/lodash.final.mjs'
|
|
1674
|
+
}
|
|
1675
|
+
};
|
|
1676
|
+
assert.deepEqual(result, expected);
|
|
1677
|
+
});
|
|
1678
|
+
|
|
1679
|
+
test('promotes to global when global matches single unique target across scopes', () => {
|
|
1680
|
+
const importMap = {
|
|
1681
|
+
imports: { vue: '/shared/vue.final.mjs' },
|
|
1682
|
+
scopes: {
|
|
1683
|
+
'/a/': { vue: '/shared/vue.final.mjs' },
|
|
1684
|
+
'/b/': { vue: '/shared/vue.final.mjs' }
|
|
1685
|
+
}
|
|
1686
|
+
};
|
|
1687
|
+
|
|
1688
|
+
const result = compressImportMap(importMap);
|
|
1689
|
+
assert.deepEqual(result, {
|
|
1690
|
+
imports: { vue: '/shared/vue.final.mjs' }
|
|
1691
|
+
});
|
|
1692
|
+
});
|
|
1693
|
+
|
|
1694
|
+
test('promotes to global when no global exists and targets are consistent across scopes (promote mode)', () => {
|
|
1695
|
+
const importMap = {
|
|
1696
|
+
imports: {},
|
|
1697
|
+
scopes: {
|
|
1698
|
+
'/a/': { vue: '/x/vue.final.mjs' },
|
|
1699
|
+
'/b/': { vue: '/x/vue.final.mjs' }
|
|
1700
|
+
}
|
|
1701
|
+
};
|
|
1702
|
+
const result = compressImportMap(importMap);
|
|
1703
|
+
assert.deepEqual(result, {
|
|
1704
|
+
imports: { vue: '/x/vue.final.mjs' }
|
|
1705
|
+
});
|
|
1706
|
+
});
|
|
1707
|
+
|
|
1708
|
+
test('promotes dominant target and keeps exceptions in scopes (promote mode)', () => {
|
|
1709
|
+
const importMap = {
|
|
1710
|
+
imports: {},
|
|
1711
|
+
scopes: {
|
|
1712
|
+
'/a/': { vue: '/x/vue.final.mjs' },
|
|
1713
|
+
'/b/': { vue: '/x/vue.final.mjs' },
|
|
1714
|
+
'/c/': { vue: '/y/vue2.final.mjs' }
|
|
1715
|
+
}
|
|
1716
|
+
};
|
|
1717
|
+
const result = compressImportMap(importMap);
|
|
1718
|
+
assert.deepEqual(result, {
|
|
1719
|
+
imports: { vue: '/x/vue.final.mjs' },
|
|
1720
|
+
scopes: {
|
|
1721
|
+
'/c/': { vue: '/y/vue2.final.mjs' }
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
});
|
|
1725
|
+
|
|
1726
|
+
test('does not promote when no global exists; keeps scopes intact', () => {
|
|
1727
|
+
const importMap = {
|
|
1728
|
+
imports: {},
|
|
1729
|
+
scopes: {
|
|
1730
|
+
'/a/': { vue: '/shared/vue.final.mjs' },
|
|
1731
|
+
'/b/': { vue: '/shared/vue.final.mjs' },
|
|
1732
|
+
'/c/': { vue: '/c/vue2.final.mjs' }
|
|
1733
|
+
}
|
|
1734
|
+
};
|
|
1735
|
+
const result = compressImportMap(importMap);
|
|
1736
|
+
assert.deepEqual(result, {
|
|
1737
|
+
imports: { vue: '/shared/vue.final.mjs' },
|
|
1738
|
+
scopes: { '/c/': { vue: '/c/vue2.final.mjs' } }
|
|
1739
|
+
});
|
|
1740
|
+
});
|
|
1741
|
+
|
|
1742
|
+
test('aggressive default does not override different existing global', () => {
|
|
1743
|
+
const importMap = {
|
|
1744
|
+
imports: { vue: '/global/vue.final.mjs' },
|
|
1745
|
+
scopes: {
|
|
1746
|
+
'/a/': { vue: '/shared/vue.final.mjs' },
|
|
1747
|
+
'/b/': { vue: '/shared/vue.final.mjs' }
|
|
1748
|
+
}
|
|
1749
|
+
};
|
|
1750
|
+
const result = compressImportMap(importMap);
|
|
1751
|
+
assert.deepEqual(result, {
|
|
1752
|
+
imports: { vue: '/global/vue.final.mjs' },
|
|
1753
|
+
scopes: {
|
|
1754
|
+
'/a/': { vue: '/shared/vue.final.mjs' },
|
|
1755
|
+
'/b/': { vue: '/shared/vue.final.mjs' }
|
|
1756
|
+
}
|
|
1757
|
+
});
|
|
1758
|
+
});
|
|
1759
|
+
|
|
1760
|
+
test('returns new object and keeps input unchanged', () => {
|
|
1761
|
+
const original = {
|
|
1762
|
+
imports: {},
|
|
1763
|
+
scopes: {
|
|
1764
|
+
'/a/': { vue: '/x/vue.final.mjs' },
|
|
1765
|
+
'/b/': { vue: '/x/vue.final.mjs' },
|
|
1766
|
+
'/c/': { vue: '/y/vue2.final.mjs' }
|
|
1767
|
+
}
|
|
1768
|
+
};
|
|
1769
|
+
const importMap = JSON.parse(JSON.stringify(original));
|
|
1770
|
+
const result = compressImportMap(importMap);
|
|
1771
|
+
assert.notStrictEqual(result, importMap);
|
|
1772
|
+
assert.notStrictEqual(result.imports, importMap.imports);
|
|
1773
|
+
assert.notStrictEqual(result.scopes, importMap.scopes);
|
|
1774
|
+
assert.deepEqual(importMap, original);
|
|
1775
|
+
assert.deepEqual(result, {
|
|
1776
|
+
imports: { vue: '/x/vue.final.mjs' },
|
|
1777
|
+
scopes: { '/c/': { vue: '/y/vue2.final.mjs' } }
|
|
1778
|
+
});
|
|
1779
|
+
});
|
|
1780
|
+
});
|
package/src/utils/import-map.ts
CHANGED
|
@@ -126,6 +126,61 @@ export function fixImportMapNestedScopes(
|
|
|
126
126
|
return importMap;
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
+
export function compressImportMap(importMap: Required<ImportMap>): ImportMap {
|
|
130
|
+
const compressed: Required<ImportMap> = {
|
|
131
|
+
imports: { ...importMap.imports },
|
|
132
|
+
scopes: {}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const counts: Record<string, Record<string, number>> = {};
|
|
136
|
+
Object.values(importMap.scopes).forEach((scopeMappings) => {
|
|
137
|
+
Object.entries(scopeMappings).forEach(([specifier, target]) => {
|
|
138
|
+
if (Object.hasOwn(importMap.imports, specifier)) return;
|
|
139
|
+
counts[specifier] ??= {};
|
|
140
|
+
counts[specifier][target] = (counts[specifier][target] ?? 0) + 1;
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
Object.entries(counts).forEach(([specifier, targetCounts]) => {
|
|
145
|
+
const entries = Object.entries(targetCounts);
|
|
146
|
+
|
|
147
|
+
let best: [string, number] | null = null;
|
|
148
|
+
let secondBestCount = 0;
|
|
149
|
+
for (const [t, c] of entries) {
|
|
150
|
+
if (!best || c > best[1]) {
|
|
151
|
+
secondBestCount = best
|
|
152
|
+
? Math.max(secondBestCount, best[1])
|
|
153
|
+
: secondBestCount;
|
|
154
|
+
best = [t, c];
|
|
155
|
+
} else {
|
|
156
|
+
secondBestCount = Math.max(secondBestCount, c);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (best && best[1] > secondBestCount) {
|
|
160
|
+
compressed.imports[specifier] = best[0];
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
Object.entries(importMap.scopes).forEach(([scopePath, scopeMappings]) => {
|
|
165
|
+
const filtered: SpecifierMap = {};
|
|
166
|
+
|
|
167
|
+
Object.entries(scopeMappings).forEach(([specifier, target]) => {
|
|
168
|
+
const globalTarget = compressed.imports[specifier];
|
|
169
|
+
if (globalTarget === target) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
filtered[specifier] = target;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
if (Object.keys(filtered).length > 0) {
|
|
176
|
+
compressed.scopes[scopePath] = filtered;
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
const hasScopes = Object.keys(compressed.scopes).length > 0;
|
|
181
|
+
return hasScopes ? compressed : { imports: compressed.imports };
|
|
182
|
+
}
|
|
183
|
+
|
|
129
184
|
export function createImportMap({
|
|
130
185
|
manifests,
|
|
131
186
|
getFile,
|
|
@@ -139,3 +194,9 @@ export function createImportMap({
|
|
|
139
194
|
scopes
|
|
140
195
|
};
|
|
141
196
|
}
|
|
197
|
+
|
|
198
|
+
export function createClientImportMap(options: GetImportMapOptions): ImportMap {
|
|
199
|
+
const base = createImportMap(options);
|
|
200
|
+
const fixed = fixImportMapNestedScopes(base);
|
|
201
|
+
return compressImportMap(fixed);
|
|
202
|
+
}
|