@esmx/core 3.0.0-rc.116 → 3.0.0-rc.118
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/LICENSE +21 -0
- package/README.md +17 -3
- package/README.zh-CN.md +20 -6
- package/dist/app.mjs +4 -2
- package/dist/cli/cli.d.ts +1 -0
- package/dist/cli/cli.mjs +28 -1
- package/dist/cli/validate.d.ts +15 -0
- package/dist/cli/validate.mjs +139 -0
- package/dist/cli/validate.test.d.ts +1 -0
- package/dist/cli/validate.test.mjs +216 -0
- package/dist/core.d.ts +31 -0
- package/dist/core.mjs +71 -5
- package/dist/declaration/index.d.ts +47 -0
- package/dist/declaration/index.mjs +63 -0
- package/dist/declaration/index.test.d.ts +1 -0
- package/dist/declaration/index.test.mjs +202 -0
- package/dist/declaration/lower.d.ts +11 -0
- package/dist/declaration/lower.mjs +78 -0
- package/dist/declaration/lower.test.d.ts +1 -0
- package/dist/declaration/lower.test.mjs +333 -0
- package/dist/declaration/reader.d.ts +28 -0
- package/dist/declaration/reader.mjs +55 -0
- package/dist/declaration/reinit.e2e.test.d.ts +1 -0
- package/dist/declaration/reinit.e2e.test.mjs +117 -0
- package/dist/declaration/resolver.d.ts +59 -0
- package/dist/declaration/resolver.mjs +430 -0
- package/dist/declaration/resolver.test.d.ts +1 -0
- package/dist/declaration/resolver.test.mjs +1005 -0
- package/dist/declaration/schema.d.ts +89 -0
- package/dist/declaration/schema.mjs +282 -0
- package/dist/declaration/schema.test.d.ts +1 -0
- package/dist/declaration/schema.test.mjs +101 -0
- package/dist/declaration/semver.d.ts +22 -0
- package/dist/declaration/semver.mjs +229 -0
- package/dist/declaration/semver.test.d.ts +1 -0
- package/dist/declaration/semver.test.mjs +87 -0
- package/dist/declaration/test-fixtures.d.ts +18 -0
- package/dist/declaration/test-fixtures.mjs +69 -0
- package/dist/declaration/types.d.ts +58 -0
- package/dist/declaration/types.mjs +21 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.mjs +10 -0
- package/dist/manifest-json.d.ts +61 -0
- package/dist/manifest-json.mjs +68 -5
- package/dist/manifest-json.test.d.ts +1 -0
- package/dist/manifest-json.test.mjs +196 -0
- package/dist/module-config.d.ts +45 -5
- package/dist/module-config.mjs +60 -49
- package/dist/module-config.test.mjs +227 -91
- package/dist/pack-config.d.ts +2 -9
- package/dist/render-context.mjs +22 -5
- package/dist/utils/import-map.d.ts +23 -3
- package/dist/utils/import-map.mjs +38 -1
- package/dist/utils/import-map.test.mjs +228 -0
- package/package.json +9 -6
- package/src/app.ts +5 -2
- package/src/cli/cli.ts +44 -1
- package/src/cli/validate.test.ts +251 -0
- package/src/cli/validate.ts +196 -0
- package/src/core.ts +84 -5
- package/src/declaration/index.test.ts +223 -0
- package/src/declaration/index.ts +135 -0
- package/src/declaration/lower.test.ts +372 -0
- package/src/declaration/lower.ts +135 -0
- package/src/declaration/reader.ts +93 -0
- package/src/declaration/reinit.e2e.test.ts +148 -0
- package/src/declaration/resolver.test.ts +1111 -0
- package/src/declaration/resolver.ts +638 -0
- package/src/declaration/schema.test.ts +118 -0
- package/src/declaration/schema.ts +339 -0
- package/src/declaration/semver.test.ts +101 -0
- package/src/declaration/semver.ts +278 -0
- package/src/declaration/test-fixtures.ts +96 -0
- package/src/declaration/types.ts +71 -0
- package/src/index.ts +28 -1
- package/src/manifest-json.test.ts +236 -0
- package/src/manifest-json.ts +166 -5
- package/src/module-config.test.ts +266 -105
- package/src/module-config.ts +130 -58
- package/src/pack-config.ts +2 -9
- package/src/render-context.ts +34 -6
- package/src/utils/import-map.test.ts +261 -0
- package/src/utils/import-map.ts +92 -6
|
@@ -1,14 +1,17 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { describe, expect, it } from "vitest";
|
|
3
4
|
import {
|
|
4
5
|
addPackageExportsToScopes,
|
|
5
|
-
|
|
6
|
+
createEntryExports,
|
|
7
|
+
DEFAULT_MODULE_ENTRY,
|
|
8
|
+
getEntryChunkId,
|
|
6
9
|
getEnvironmentExports,
|
|
7
10
|
getEnvironmentImports,
|
|
8
|
-
getEnvironmentScopes,
|
|
9
11
|
getEnvironments,
|
|
10
12
|
getLinks,
|
|
11
13
|
parsedExportValue,
|
|
14
|
+
parseEntryConfig,
|
|
12
15
|
parseModuleConfig,
|
|
13
16
|
processExportArray,
|
|
14
17
|
processObjectExport,
|
|
@@ -209,41 +212,17 @@ describe("Module Config Parser", () => {
|
|
|
209
212
|
expect(Object.keys(result)).toHaveLength(0);
|
|
210
213
|
});
|
|
211
214
|
});
|
|
212
|
-
describe("getEnvironmentScopes", () => {
|
|
213
|
-
it("should process scoped imports per environment", () => {
|
|
214
|
-
const scopes = {
|
|
215
|
-
utils: {
|
|
216
|
-
lodash: "lodash",
|
|
217
|
-
moment: {
|
|
218
|
-
client: "moment",
|
|
219
|
-
server: "moment/server"
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
};
|
|
223
|
-
const result = getEnvironmentScopes("client", scopes);
|
|
224
|
-
expect(result.utils.lodash).toBe("lodash");
|
|
225
|
-
expect(result.utils.moment).toBe("moment");
|
|
226
|
-
});
|
|
227
|
-
it("should handle empty scopes object", () => {
|
|
228
|
-
const result = getEnvironmentScopes("client", {});
|
|
229
|
-
expect(Object.keys(result)).toHaveLength(0);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
215
|
describe("getEnvironments", () => {
|
|
233
|
-
it("should combine imports
|
|
216
|
+
it("should combine imports and exports into the root scope", () => {
|
|
234
217
|
const config = {
|
|
235
218
|
imports: {
|
|
236
219
|
react: "react"
|
|
237
|
-
},
|
|
238
|
-
scopes: {
|
|
239
|
-
utils: {
|
|
240
|
-
lodash: "lodash"
|
|
241
|
-
}
|
|
242
220
|
}
|
|
243
221
|
};
|
|
244
222
|
const result = getEnvironments(config, "client", "test-module");
|
|
245
223
|
expect(result.imports.react).toBe("react");
|
|
246
|
-
expect(result.scopes
|
|
224
|
+
expect(Object.keys(result.scopes)).toEqual([""]);
|
|
225
|
+
expect(result.scopes[""].react).toBe("react");
|
|
247
226
|
expect(result.exports).toBeDefined();
|
|
248
227
|
});
|
|
249
228
|
it("should preserve import mapping types", () => {
|
|
@@ -268,42 +247,19 @@ describe("Module Config Parser", () => {
|
|
|
268
247
|
expect(emptyScope.react).toBe("test-module/react");
|
|
269
248
|
expect(emptyScope["./src/component"]).toBeUndefined();
|
|
270
249
|
});
|
|
271
|
-
it("should
|
|
272
|
-
const config = {
|
|
273
|
-
exports: ["pkg:lodash"],
|
|
274
|
-
scopes: {
|
|
275
|
-
"": {
|
|
276
|
-
existing: "existing-value"
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
const result = getEnvironments(config, "client", "test-module");
|
|
281
|
-
const emptyScope = result.scopes[""];
|
|
282
|
-
expect(emptyScope).toBeDefined();
|
|
283
|
-
expect(emptyScope.existing).toBe("existing-value");
|
|
284
|
-
expect(emptyScope.lodash).toBe("test-module/lodash");
|
|
285
|
-
});
|
|
286
|
-
it("should verify the specific scopes merging logic with imports", () => {
|
|
250
|
+
it("should merge imports and pkg exports into the single root scope", () => {
|
|
287
251
|
const config = {
|
|
288
252
|
imports: {
|
|
289
253
|
react: "react",
|
|
290
254
|
vue: "vue"
|
|
291
255
|
},
|
|
292
|
-
|
|
293
|
-
utils: {
|
|
294
|
-
lodash: "lodash"
|
|
295
|
-
},
|
|
296
|
-
"": {
|
|
297
|
-
existing: "existing-value"
|
|
298
|
-
}
|
|
299
|
-
}
|
|
256
|
+
exports: ["pkg:lodash"]
|
|
300
257
|
};
|
|
301
258
|
const result = getEnvironments(config, "client", "test-module");
|
|
302
|
-
expect(result.scopes[""])
|
|
303
|
-
expect(result.scopes[""].existing).toBe("existing-value");
|
|
259
|
+
expect(Object.keys(result.scopes)).toEqual([""]);
|
|
304
260
|
expect(result.scopes[""].react).toBe("react");
|
|
305
261
|
expect(result.scopes[""].vue).toBe("vue");
|
|
306
|
-
expect(result.scopes.
|
|
262
|
+
expect(result.scopes[""].lodash).toBe("test-module/lodash");
|
|
307
263
|
expect(result.imports.react).toBe("react");
|
|
308
264
|
expect(result.imports.vue).toBe("vue");
|
|
309
265
|
});
|
|
@@ -362,36 +318,6 @@ describe("Module Config Parser", () => {
|
|
|
362
318
|
});
|
|
363
319
|
});
|
|
364
320
|
describe("Export Processing Functions", () => {
|
|
365
|
-
describe("createDefaultExports", () => {
|
|
366
|
-
it("should generate client default exports", () => {
|
|
367
|
-
const result = createDefaultExports("client");
|
|
368
|
-
expect(result["src/entry.client"].file).toBe(
|
|
369
|
-
"./src/entry.client"
|
|
370
|
-
);
|
|
371
|
-
expect(result["src/entry.server"].file).toBe("");
|
|
372
|
-
});
|
|
373
|
-
it("should generate server default exports", () => {
|
|
374
|
-
const result = createDefaultExports("server");
|
|
375
|
-
expect(result["src/entry.client"].file).toBe("");
|
|
376
|
-
expect(result["src/entry.server"].file).toBe(
|
|
377
|
-
"./src/entry.server"
|
|
378
|
-
);
|
|
379
|
-
});
|
|
380
|
-
it("should handle client environment switch case", () => {
|
|
381
|
-
const result = createDefaultExports("client");
|
|
382
|
-
expect(result["src/entry.client"].file).toBe(
|
|
383
|
-
"./src/entry.client"
|
|
384
|
-
);
|
|
385
|
-
expect(result["src/entry.server"].file).toBe("");
|
|
386
|
-
});
|
|
387
|
-
it("should handle server environment switch case", () => {
|
|
388
|
-
const result = createDefaultExports("server");
|
|
389
|
-
expect(result["src/entry.client"].file).toBe("");
|
|
390
|
-
expect(result["src/entry.server"].file).toBe(
|
|
391
|
-
"./src/entry.server"
|
|
392
|
-
);
|
|
393
|
-
});
|
|
394
|
-
});
|
|
395
321
|
describe("processStringExport", () => {
|
|
396
322
|
it("should parse simple string export", () => {
|
|
397
323
|
const result = processStringExport("./src/component");
|
|
@@ -647,11 +573,6 @@ describe("Module Config Parser", () => {
|
|
|
647
573
|
server: "vue/server"
|
|
648
574
|
}
|
|
649
575
|
},
|
|
650
|
-
scopes: {
|
|
651
|
-
utils: {
|
|
652
|
-
lodash: "lodash"
|
|
653
|
-
}
|
|
654
|
-
},
|
|
655
576
|
exports: [
|
|
656
577
|
"./src/component",
|
|
657
578
|
{
|
|
@@ -741,3 +662,218 @@ describe("Module Config Parser", () => {
|
|
|
741
662
|
});
|
|
742
663
|
});
|
|
743
664
|
});
|
|
665
|
+
describe("Framework entry threading (RFC 0001 Phase 2)", () => {
|
|
666
|
+
describe("parseEntryConfig", () => {
|
|
667
|
+
it("should default to legacy entries for an empty config", () => {
|
|
668
|
+
const entry = parseEntryConfig({});
|
|
669
|
+
expect(entry).toEqual({
|
|
670
|
+
client: {
|
|
671
|
+
name: "src/entry.client",
|
|
672
|
+
file: "./src/entry.client"
|
|
673
|
+
},
|
|
674
|
+
server: {
|
|
675
|
+
name: "src/entry.server",
|
|
676
|
+
file: "./src/entry.server"
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
});
|
|
680
|
+
it("should resolve both entries to null for lib modules", () => {
|
|
681
|
+
const entry = parseEntryConfig({ lib: true });
|
|
682
|
+
expect(entry).toEqual({ client: null, server: null });
|
|
683
|
+
});
|
|
684
|
+
it("should derive the export name from a custom entry file path", () => {
|
|
685
|
+
const entry = parseEntryConfig({
|
|
686
|
+
entry: { client: "./custom/main.ts" }
|
|
687
|
+
});
|
|
688
|
+
expect(entry.client).toEqual({
|
|
689
|
+
name: "custom/main",
|
|
690
|
+
file: "./custom/main.ts"
|
|
691
|
+
});
|
|
692
|
+
expect(entry.server).toEqual(DEFAULT_MODULE_ENTRY.server);
|
|
693
|
+
});
|
|
694
|
+
it("should keep the path-derived default name for a standard declaration path", () => {
|
|
695
|
+
const entry = parseEntryConfig({
|
|
696
|
+
entry: { client: "./src/entry.client.ts" }
|
|
697
|
+
});
|
|
698
|
+
expect(entry.client).toEqual({
|
|
699
|
+
name: "src/entry.client",
|
|
700
|
+
file: "./src/entry.client.ts"
|
|
701
|
+
});
|
|
702
|
+
});
|
|
703
|
+
it("should disable a side declared as false", () => {
|
|
704
|
+
const entry = parseEntryConfig({ entry: { server: false } });
|
|
705
|
+
expect(entry.server).toBeNull();
|
|
706
|
+
expect(entry.client).toEqual(DEFAULT_MODULE_ENTRY.client);
|
|
707
|
+
});
|
|
708
|
+
});
|
|
709
|
+
describe("parseModuleConfig entry population", () => {
|
|
710
|
+
it("should populate entry for legacy configs", () => {
|
|
711
|
+
const result = parseModuleConfig("test-module", "/test/root");
|
|
712
|
+
expect(result.entry.client).toEqual(DEFAULT_MODULE_ENTRY.client);
|
|
713
|
+
expect(result.entry.server).toEqual(DEFAULT_MODULE_ENTRY.server);
|
|
714
|
+
});
|
|
715
|
+
it("should populate null entries for lib configs and omit entry exports", () => {
|
|
716
|
+
const result = parseModuleConfig("test-module", "/test/root", {
|
|
717
|
+
lib: true
|
|
718
|
+
});
|
|
719
|
+
expect(result.entry).toEqual({ client: null, server: null });
|
|
720
|
+
expect(result.environments.client.exports).toEqual({});
|
|
721
|
+
expect(result.environments.server.exports).toEqual({});
|
|
722
|
+
});
|
|
723
|
+
it("should thread a custom entry into the environment exports", () => {
|
|
724
|
+
const result = parseModuleConfig("test-module", "/test/root", {
|
|
725
|
+
entry: { client: "./custom/main.ts" }
|
|
726
|
+
});
|
|
727
|
+
expect(result.environments.client.exports["custom/main"]).toEqual({
|
|
728
|
+
name: "custom/main",
|
|
729
|
+
file: "./custom/main.ts",
|
|
730
|
+
pkg: false
|
|
731
|
+
});
|
|
732
|
+
expect(result.environments.server.exports["custom/main"]).toEqual({
|
|
733
|
+
name: "custom/main",
|
|
734
|
+
file: "",
|
|
735
|
+
pkg: false
|
|
736
|
+
});
|
|
737
|
+
expect(
|
|
738
|
+
result.environments.server.exports["src/entry.server"]
|
|
739
|
+
).toEqual({
|
|
740
|
+
name: "src/entry.server",
|
|
741
|
+
file: "./src/entry.server",
|
|
742
|
+
pkg: false
|
|
743
|
+
});
|
|
744
|
+
expect(
|
|
745
|
+
result.environments.client.exports["src/entry.client"]
|
|
746
|
+
).toBeUndefined();
|
|
747
|
+
});
|
|
748
|
+
});
|
|
749
|
+
describe("default entry exports single source of truth", () => {
|
|
750
|
+
it("should derive byte-identical defaults from DEFAULT_MODULE_ENTRY", () => {
|
|
751
|
+
const entry = parseEntryConfig({});
|
|
752
|
+
expect(createEntryExports(entry, "client")).toEqual({
|
|
753
|
+
"src/entry.client": {
|
|
754
|
+
name: "src/entry.client",
|
|
755
|
+
file: "./src/entry.client",
|
|
756
|
+
pkg: false
|
|
757
|
+
},
|
|
758
|
+
"src/entry.server": {
|
|
759
|
+
name: "src/entry.server",
|
|
760
|
+
file: "",
|
|
761
|
+
pkg: false
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
expect(createEntryExports(entry, "server")).toEqual({
|
|
765
|
+
"src/entry.client": {
|
|
766
|
+
name: "src/entry.client",
|
|
767
|
+
file: "",
|
|
768
|
+
pkg: false
|
|
769
|
+
},
|
|
770
|
+
"src/entry.server": {
|
|
771
|
+
name: "src/entry.server",
|
|
772
|
+
file: "./src/entry.server",
|
|
773
|
+
pkg: false
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
});
|
|
777
|
+
});
|
|
778
|
+
describe("getEntryChunkId", () => {
|
|
779
|
+
it("should append .ts to the legacy extensionless default", () => {
|
|
780
|
+
const id = getEntryChunkId(
|
|
781
|
+
"test-module",
|
|
782
|
+
DEFAULT_MODULE_ENTRY.client
|
|
783
|
+
);
|
|
784
|
+
expect(id).toBe("test-module@src/entry.client.ts");
|
|
785
|
+
});
|
|
786
|
+
it("should preserve an explicit extension on custom entries", () => {
|
|
787
|
+
expect(
|
|
788
|
+
getEntryChunkId("m", {
|
|
789
|
+
name: "custom/main",
|
|
790
|
+
file: "./custom/main.tsx"
|
|
791
|
+
})
|
|
792
|
+
).toBe("m@custom/main.tsx");
|
|
793
|
+
expect(
|
|
794
|
+
getEntryChunkId("m", {
|
|
795
|
+
name: "src/entry.client",
|
|
796
|
+
file: "./src/entry.client.ts"
|
|
797
|
+
})
|
|
798
|
+
).toBe("m@src/entry.client.ts");
|
|
799
|
+
});
|
|
800
|
+
});
|
|
801
|
+
describe("fleet invariant: built example manifests", () => {
|
|
802
|
+
const repoRoot = path.resolve(import.meta.dirname, "../../..");
|
|
803
|
+
const examplesDir = path.join(repoRoot, "examples");
|
|
804
|
+
const findClientManifests = (dir) => {
|
|
805
|
+
if (!fs.existsSync(dir)) {
|
|
806
|
+
return [];
|
|
807
|
+
}
|
|
808
|
+
const found = [];
|
|
809
|
+
for (const item of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
810
|
+
if (!item.isDirectory() || item.name === "node_modules") {
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
const child = path.join(dir, item.name);
|
|
814
|
+
const manifest = path.join(child, "dist/client/manifest.json");
|
|
815
|
+
if (fs.existsSync(manifest)) {
|
|
816
|
+
found.push(manifest);
|
|
817
|
+
}
|
|
818
|
+
found.push(...findClientManifests(child));
|
|
819
|
+
}
|
|
820
|
+
return found;
|
|
821
|
+
};
|
|
822
|
+
const manifests = findClientManifests(examplesDir);
|
|
823
|
+
it.skipIf(manifests.length === 0)(
|
|
824
|
+
"should compute the exact historical chunk seed for every built example",
|
|
825
|
+
() => {
|
|
826
|
+
expect(manifests.length).toBeGreaterThan(0);
|
|
827
|
+
for (const manifestPath of manifests) {
|
|
828
|
+
const manifest = JSON.parse(
|
|
829
|
+
fs.readFileSync(manifestPath, "utf-8")
|
|
830
|
+
);
|
|
831
|
+
const parsed = parseModuleConfig(
|
|
832
|
+
manifest.name,
|
|
833
|
+
path.resolve(manifestPath, "../../..")
|
|
834
|
+
);
|
|
835
|
+
if (!parsed.entry.client) {
|
|
836
|
+
continue;
|
|
837
|
+
}
|
|
838
|
+
const seed = getEntryChunkId(
|
|
839
|
+
manifest.name,
|
|
840
|
+
parsed.entry.client
|
|
841
|
+
);
|
|
842
|
+
expect(seed).toBe(`${manifest.name}@src/entry.client.ts`);
|
|
843
|
+
if (manifest.exports["src/entry.client"]) {
|
|
844
|
+
expect(Object.keys(manifest.chunks)).toContain(seed);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
);
|
|
849
|
+
it.skipIf(
|
|
850
|
+
!fs.existsSync(
|
|
851
|
+
path.join(
|
|
852
|
+
examplesDir,
|
|
853
|
+
"micro-app/ssr-micro-shared/dist/client/manifest.json"
|
|
854
|
+
)
|
|
855
|
+
)
|
|
856
|
+
)(
|
|
857
|
+
"should match the ssr-micro-shared manifest chunk key exactly",
|
|
858
|
+
() => {
|
|
859
|
+
const manifestPath = path.join(
|
|
860
|
+
examplesDir,
|
|
861
|
+
"micro-app/ssr-micro-shared/dist/client/manifest.json"
|
|
862
|
+
);
|
|
863
|
+
const manifest = JSON.parse(
|
|
864
|
+
fs.readFileSync(manifestPath, "utf-8")
|
|
865
|
+
);
|
|
866
|
+
const parsed = parseModuleConfig(
|
|
867
|
+
manifest.name,
|
|
868
|
+
path.resolve(manifestPath, "../../..")
|
|
869
|
+
);
|
|
870
|
+
const seed = getEntryChunkId(
|
|
871
|
+
manifest.name,
|
|
872
|
+
parsed.entry.client
|
|
873
|
+
);
|
|
874
|
+
expect(seed).toBe("ssr-micro-shared@src/entry.client.ts");
|
|
875
|
+
expect(Object.keys(manifest.chunks)).toContain(seed);
|
|
876
|
+
}
|
|
877
|
+
);
|
|
878
|
+
});
|
|
879
|
+
});
|
package/dist/pack-config.d.ts
CHANGED
|
@@ -19,15 +19,8 @@ import type { Esmx } from './core';
|
|
|
19
19
|
* import type { EsmxOptions } from '@esmx/core';
|
|
20
20
|
*
|
|
21
21
|
* export default {
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* exports: [
|
|
25
|
-
* 'root:src/components/button.vue',
|
|
26
|
-
* 'root:src/utils/format.ts',
|
|
27
|
-
* 'pkg:vue',
|
|
28
|
-
* 'pkg:vue-router'
|
|
29
|
-
* ]
|
|
30
|
-
* },
|
|
22
|
+
* // Module exports/provides are declared in package.json "esmx"
|
|
23
|
+
* // (RFC 0001), not here. entry.node.ts keeps behavior + packaging.
|
|
31
24
|
* // Packaging configuration
|
|
32
25
|
* pack: {
|
|
33
26
|
* // Enable packaging functionality
|
package/dist/render-context.mjs
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import serialize from "serialize-javascript";
|
|
3
|
+
import {
|
|
4
|
+
DEFAULT_MODULE_ENTRY,
|
|
5
|
+
getEntryChunkId
|
|
6
|
+
} from "./module-config.mjs";
|
|
7
|
+
function getClientEntry(esmx) {
|
|
8
|
+
return esmx.moduleConfig.entry.client ?? DEFAULT_MODULE_ENTRY.client;
|
|
9
|
+
}
|
|
3
10
|
export class RenderContext {
|
|
4
11
|
esmx;
|
|
5
12
|
/**
|
|
@@ -384,7 +391,8 @@ export class RenderContext {
|
|
|
384
391
|
};
|
|
385
392
|
_importMap = {
|
|
386
393
|
src: "",
|
|
387
|
-
code: ""
|
|
394
|
+
code: "",
|
|
395
|
+
integrity: null
|
|
388
396
|
};
|
|
389
397
|
/**
|
|
390
398
|
* Define the generation mode for importmap
|
|
@@ -702,7 +710,8 @@ export class RenderContext {
|
|
|
702
710
|
*/
|
|
703
711
|
async commit() {
|
|
704
712
|
const { esmx } = this;
|
|
705
|
-
const
|
|
713
|
+
const clientEntry = getClientEntry(esmx);
|
|
714
|
+
const chunkSet = /* @__PURE__ */ new Set([getEntryChunkId(esmx.name, clientEntry)]);
|
|
706
715
|
for (const item of this.importMetaSet) {
|
|
707
716
|
if ("chunkName" in item && typeof item.chunkName === "string") {
|
|
708
717
|
chunkSet.add(item.chunkName);
|
|
@@ -729,7 +738,7 @@ export class RenderContext {
|
|
|
729
738
|
});
|
|
730
739
|
const paths = await esmx.getStaticImportPaths(
|
|
731
740
|
"client",
|
|
732
|
-
`${esmx.name}
|
|
741
|
+
`${esmx.name}/${clientEntry.name}`
|
|
733
742
|
);
|
|
734
743
|
paths?.forEach(
|
|
735
744
|
(filepath) => files.modulepreload.add(getUrlPath(filepath))
|
|
@@ -956,7 +965,8 @@ export class RenderContext {
|
|
|
956
965
|
* ```
|
|
957
966
|
*/
|
|
958
967
|
moduleEntry() {
|
|
959
|
-
|
|
968
|
+
const clientEntry = getClientEntry(this.esmx);
|
|
969
|
+
return `<script type="module">import "${this.esmx.name}/${clientEntry.name}";<\/script>`;
|
|
960
970
|
}
|
|
961
971
|
/**
|
|
962
972
|
* Preload module dependencies
|
|
@@ -1005,6 +1015,13 @@ export class RenderContext {
|
|
|
1005
1015
|
* ```
|
|
1006
1016
|
*/
|
|
1007
1017
|
modulePreload() {
|
|
1008
|
-
|
|
1018
|
+
const { integrity } = this._importMap;
|
|
1019
|
+
return this.files.modulepreload.map((url) => {
|
|
1020
|
+
const hash = integrity?.[url];
|
|
1021
|
+
if (hash) {
|
|
1022
|
+
return `<link rel="modulepreload" href="${url}" integrity="${hash}">`;
|
|
1023
|
+
}
|
|
1024
|
+
return `<link rel="modulepreload" href="${url}">`;
|
|
1025
|
+
}).join("");
|
|
1009
1026
|
}
|
|
1010
1027
|
}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { ImportMap, ScopesMap, SpecifierMap } from '@esmx/import';
|
|
2
|
+
/**
|
|
3
|
+
* Import map with `imports` and `scopes` fully resolved. The optional
|
|
4
|
+
* `integrity` field is layered on later by {@link createClientImportMap}.
|
|
5
|
+
*/
|
|
6
|
+
type ResolvedImportMap = Required<Pick<ImportMap, 'imports' | 'scopes'>>;
|
|
2
7
|
export interface ImportMapManifest {
|
|
3
8
|
name: string;
|
|
4
9
|
exports: Record<string, {
|
|
@@ -8,6 +13,20 @@ export interface ImportMapManifest {
|
|
|
8
13
|
pkg: boolean;
|
|
9
14
|
}>;
|
|
10
15
|
scopes: Record<string, Record<string, string>>;
|
|
16
|
+
/**
|
|
17
|
+
* Emitted chunk files (keyed arbitrarily). Each chunk's `js` is a relative
|
|
18
|
+
* path (e.g. "chunks/routes.xxx.mjs"). Used so a module's scope also covers
|
|
19
|
+
* its code-split chunks, not just its export files.
|
|
20
|
+
*/
|
|
21
|
+
chunks?: Record<string, {
|
|
22
|
+
js: string;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* Subresource Integrity hashes for this module's build output files.
|
|
26
|
+
* Keys are relative file paths (e.g. "src/routes.xxx.mjs"), values are
|
|
27
|
+
* integrity strings (e.g. "sha384-..."). Only present in production builds.
|
|
28
|
+
*/
|
|
29
|
+
integrity?: Record<string, string>;
|
|
11
30
|
}
|
|
12
31
|
export interface GetImportMapOptions {
|
|
13
32
|
manifests: readonly ImportMapManifest[];
|
|
@@ -46,7 +65,8 @@ export declare function createScopesMap(imports: SpecifierMap, manifests: readon
|
|
|
46
65
|
* @see https://github.com/guybedford/es-module-shims/issues/529
|
|
47
66
|
* @see https://issues.chromium.org/issues/453147451
|
|
48
67
|
*/
|
|
49
|
-
export declare function fixImportMapNestedScopes(importMap:
|
|
50
|
-
export declare function compressImportMap(importMap:
|
|
51
|
-
export declare function createImportMap({ manifests, getFile, getScope }: GetImportMapOptions):
|
|
68
|
+
export declare function fixImportMapNestedScopes(importMap: ResolvedImportMap): ResolvedImportMap;
|
|
69
|
+
export declare function compressImportMap(importMap: ResolvedImportMap): ImportMap;
|
|
70
|
+
export declare function createImportMap({ manifests, getFile, getScope }: GetImportMapOptions): ResolvedImportMap;
|
|
52
71
|
export declare function createClientImportMap(options: GetImportMapOptions): ImportMap;
|
|
72
|
+
export {};
|
|
@@ -48,6 +48,26 @@ export function fixImportMapNestedScopes(importMap) {
|
|
|
48
48
|
});
|
|
49
49
|
return importMap;
|
|
50
50
|
}
|
|
51
|
+
function addCodeSplitChunkScopes(importMap, base, options) {
|
|
52
|
+
for (const manifest of options.manifests) {
|
|
53
|
+
const moduleScope = manifest.scopes?.[""];
|
|
54
|
+
const chunks = manifest.chunks;
|
|
55
|
+
if (!moduleScope || !chunks) continue;
|
|
56
|
+
const resolved = {};
|
|
57
|
+
for (const [specifier, identifier] of Object.entries(moduleScope)) {
|
|
58
|
+
const target = base.imports[identifier] ?? identifier;
|
|
59
|
+
if (importMap.imports?.[specifier] === target) continue;
|
|
60
|
+
resolved[specifier] = target;
|
|
61
|
+
}
|
|
62
|
+
if (Object.keys(resolved).length === 0) continue;
|
|
63
|
+
for (const chunk of Object.values(chunks)) {
|
|
64
|
+
if (!chunk.js) continue;
|
|
65
|
+
const url = options.getFile(manifest.name, chunk.js);
|
|
66
|
+
importMap.scopes ??= {};
|
|
67
|
+
importMap.scopes[url] = { ...importMap.scopes[url], ...resolved };
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
51
71
|
export function compressImportMap(importMap) {
|
|
52
72
|
const compressed = {
|
|
53
73
|
imports: { ...importMap.imports },
|
|
@@ -108,5 +128,22 @@ export function createImportMap({
|
|
|
108
128
|
export function createClientImportMap(options) {
|
|
109
129
|
const base = createImportMap(options);
|
|
110
130
|
const fixed = fixImportMapNestedScopes(base);
|
|
111
|
-
|
|
131
|
+
const compressed = compressImportMap(fixed);
|
|
132
|
+
addCodeSplitChunkScopes(compressed, base, options);
|
|
133
|
+
const integrity = {};
|
|
134
|
+
for (const manifest of options.manifests) {
|
|
135
|
+
if (manifest.integrity) {
|
|
136
|
+
for (const [filePath, hash] of Object.entries(manifest.integrity)) {
|
|
137
|
+
const urlPath = `/${manifest.name}/${filePath}`;
|
|
138
|
+
integrity[urlPath] = hash;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (Object.keys(integrity).length > 0) {
|
|
143
|
+
return {
|
|
144
|
+
...compressed,
|
|
145
|
+
integrity
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return compressed;
|
|
112
149
|
}
|