@esmx/core 3.0.0-rc.117 → 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.
Files changed (82) hide show
  1. package/README.md +17 -3
  2. package/README.zh-CN.md +20 -6
  3. package/dist/app.mjs +4 -2
  4. package/dist/cli/cli.d.ts +1 -0
  5. package/dist/cli/cli.mjs +28 -1
  6. package/dist/cli/validate.d.ts +15 -0
  7. package/dist/cli/validate.mjs +139 -0
  8. package/dist/cli/validate.test.d.ts +1 -0
  9. package/dist/cli/validate.test.mjs +216 -0
  10. package/dist/core.d.ts +31 -0
  11. package/dist/core.mjs +71 -5
  12. package/dist/declaration/index.d.ts +47 -0
  13. package/dist/declaration/index.mjs +63 -0
  14. package/dist/declaration/index.test.d.ts +1 -0
  15. package/dist/declaration/index.test.mjs +202 -0
  16. package/dist/declaration/lower.d.ts +11 -0
  17. package/dist/declaration/lower.mjs +78 -0
  18. package/dist/declaration/lower.test.d.ts +1 -0
  19. package/dist/declaration/lower.test.mjs +333 -0
  20. package/dist/declaration/reader.d.ts +28 -0
  21. package/dist/declaration/reader.mjs +55 -0
  22. package/dist/declaration/reinit.e2e.test.d.ts +1 -0
  23. package/dist/declaration/reinit.e2e.test.mjs +117 -0
  24. package/dist/declaration/resolver.d.ts +59 -0
  25. package/dist/declaration/resolver.mjs +430 -0
  26. package/dist/declaration/resolver.test.d.ts +1 -0
  27. package/dist/declaration/resolver.test.mjs +1005 -0
  28. package/dist/declaration/schema.d.ts +89 -0
  29. package/dist/declaration/schema.mjs +282 -0
  30. package/dist/declaration/schema.test.d.ts +1 -0
  31. package/dist/declaration/schema.test.mjs +101 -0
  32. package/dist/declaration/semver.d.ts +22 -0
  33. package/dist/declaration/semver.mjs +229 -0
  34. package/dist/declaration/semver.test.d.ts +1 -0
  35. package/dist/declaration/semver.test.mjs +87 -0
  36. package/dist/declaration/test-fixtures.d.ts +18 -0
  37. package/dist/declaration/test-fixtures.mjs +69 -0
  38. package/dist/declaration/types.d.ts +58 -0
  39. package/dist/declaration/types.mjs +21 -0
  40. package/dist/index.d.ts +4 -1
  41. package/dist/index.mjs +10 -0
  42. package/dist/manifest-json.d.ts +61 -0
  43. package/dist/manifest-json.mjs +68 -5
  44. package/dist/manifest-json.test.d.ts +1 -0
  45. package/dist/manifest-json.test.mjs +196 -0
  46. package/dist/module-config.d.ts +45 -5
  47. package/dist/module-config.mjs +60 -49
  48. package/dist/module-config.test.mjs +227 -91
  49. package/dist/pack-config.d.ts +2 -9
  50. package/dist/render-context.mjs +22 -5
  51. package/dist/utils/import-map.d.ts +23 -3
  52. package/dist/utils/import-map.mjs +38 -1
  53. package/dist/utils/import-map.test.mjs +228 -0
  54. package/package.json +9 -6
  55. package/src/app.ts +5 -2
  56. package/src/cli/cli.ts +44 -1
  57. package/src/cli/validate.test.ts +251 -0
  58. package/src/cli/validate.ts +196 -0
  59. package/src/core.ts +84 -5
  60. package/src/declaration/index.test.ts +223 -0
  61. package/src/declaration/index.ts +135 -0
  62. package/src/declaration/lower.test.ts +372 -0
  63. package/src/declaration/lower.ts +135 -0
  64. package/src/declaration/reader.ts +93 -0
  65. package/src/declaration/reinit.e2e.test.ts +148 -0
  66. package/src/declaration/resolver.test.ts +1111 -0
  67. package/src/declaration/resolver.ts +638 -0
  68. package/src/declaration/schema.test.ts +118 -0
  69. package/src/declaration/schema.ts +339 -0
  70. package/src/declaration/semver.test.ts +101 -0
  71. package/src/declaration/semver.ts +278 -0
  72. package/src/declaration/test-fixtures.ts +96 -0
  73. package/src/declaration/types.ts +71 -0
  74. package/src/index.ts +28 -1
  75. package/src/manifest-json.test.ts +236 -0
  76. package/src/manifest-json.ts +166 -5
  77. package/src/module-config.test.ts +266 -105
  78. package/src/module-config.ts +130 -58
  79. package/src/pack-config.ts +2 -9
  80. package/src/render-context.ts +34 -6
  81. package/src/utils/import-map.test.ts +261 -0
  82. 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
- createDefaultExports,
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, exports and scopes", () => {
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.utils.lodash).toBe("lodash");
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 not override existing empty string scope", () => {
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
- scopes: {
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[""]).toBeDefined();
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.utils.lodash).toBe("lodash");
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
+ });
@@ -19,15 +19,8 @@ import type { Esmx } from './core';
19
19
  * import type { EsmxOptions } from '@esmx/core';
20
20
  *
21
21
  * export default {
22
- * modules: {
23
- * // Configure modules to export
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
@@ -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 chunkSet = /* @__PURE__ */ new Set([`${esmx.name}@src/entry.client.ts`]);
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}/src/entry.client`
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
- return `<script type="module">import "${this.esmx.name}/src/entry.client";<\/script>`;
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
- return this.files.modulepreload.map((url) => `<link rel="modulepreload" href="${url}">`).join("");
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: Required<ImportMap>): Required<ImportMap>;
50
- export declare function compressImportMap(importMap: Required<ImportMap>): ImportMap;
51
- export declare function createImportMap({ manifests, getFile, getScope }: GetImportMapOptions): Required<ImportMap>;
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
- return compressImportMap(fixed);
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
  }