@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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +17 -3
  3. package/README.zh-CN.md +20 -6
  4. package/dist/app.mjs +4 -2
  5. package/dist/cli/cli.d.ts +1 -0
  6. package/dist/cli/cli.mjs +28 -1
  7. package/dist/cli/validate.d.ts +15 -0
  8. package/dist/cli/validate.mjs +139 -0
  9. package/dist/cli/validate.test.d.ts +1 -0
  10. package/dist/cli/validate.test.mjs +216 -0
  11. package/dist/core.d.ts +31 -0
  12. package/dist/core.mjs +71 -5
  13. package/dist/declaration/index.d.ts +47 -0
  14. package/dist/declaration/index.mjs +63 -0
  15. package/dist/declaration/index.test.d.ts +1 -0
  16. package/dist/declaration/index.test.mjs +202 -0
  17. package/dist/declaration/lower.d.ts +11 -0
  18. package/dist/declaration/lower.mjs +78 -0
  19. package/dist/declaration/lower.test.d.ts +1 -0
  20. package/dist/declaration/lower.test.mjs +333 -0
  21. package/dist/declaration/reader.d.ts +28 -0
  22. package/dist/declaration/reader.mjs +55 -0
  23. package/dist/declaration/reinit.e2e.test.d.ts +1 -0
  24. package/dist/declaration/reinit.e2e.test.mjs +117 -0
  25. package/dist/declaration/resolver.d.ts +59 -0
  26. package/dist/declaration/resolver.mjs +430 -0
  27. package/dist/declaration/resolver.test.d.ts +1 -0
  28. package/dist/declaration/resolver.test.mjs +1005 -0
  29. package/dist/declaration/schema.d.ts +89 -0
  30. package/dist/declaration/schema.mjs +282 -0
  31. package/dist/declaration/schema.test.d.ts +1 -0
  32. package/dist/declaration/schema.test.mjs +101 -0
  33. package/dist/declaration/semver.d.ts +22 -0
  34. package/dist/declaration/semver.mjs +229 -0
  35. package/dist/declaration/semver.test.d.ts +1 -0
  36. package/dist/declaration/semver.test.mjs +87 -0
  37. package/dist/declaration/test-fixtures.d.ts +18 -0
  38. package/dist/declaration/test-fixtures.mjs +69 -0
  39. package/dist/declaration/types.d.ts +58 -0
  40. package/dist/declaration/types.mjs +21 -0
  41. package/dist/index.d.ts +4 -1
  42. package/dist/index.mjs +10 -0
  43. package/dist/manifest-json.d.ts +61 -0
  44. package/dist/manifest-json.mjs +68 -5
  45. package/dist/manifest-json.test.d.ts +1 -0
  46. package/dist/manifest-json.test.mjs +196 -0
  47. package/dist/module-config.d.ts +45 -5
  48. package/dist/module-config.mjs +60 -49
  49. package/dist/module-config.test.mjs +227 -91
  50. package/dist/pack-config.d.ts +2 -9
  51. package/dist/render-context.mjs +22 -5
  52. package/dist/utils/import-map.d.ts +23 -3
  53. package/dist/utils/import-map.mjs +38 -1
  54. package/dist/utils/import-map.test.mjs +228 -0
  55. package/package.json +9 -6
  56. package/src/app.ts +5 -2
  57. package/src/cli/cli.ts +44 -1
  58. package/src/cli/validate.test.ts +251 -0
  59. package/src/cli/validate.ts +196 -0
  60. package/src/core.ts +84 -5
  61. package/src/declaration/index.test.ts +223 -0
  62. package/src/declaration/index.ts +135 -0
  63. package/src/declaration/lower.test.ts +372 -0
  64. package/src/declaration/lower.ts +135 -0
  65. package/src/declaration/reader.ts +93 -0
  66. package/src/declaration/reinit.e2e.test.ts +148 -0
  67. package/src/declaration/resolver.test.ts +1111 -0
  68. package/src/declaration/resolver.ts +638 -0
  69. package/src/declaration/schema.test.ts +118 -0
  70. package/src/declaration/schema.ts +339 -0
  71. package/src/declaration/semver.test.ts +101 -0
  72. package/src/declaration/semver.ts +278 -0
  73. package/src/declaration/test-fixtures.ts +96 -0
  74. package/src/declaration/types.ts +71 -0
  75. package/src/index.ts +28 -1
  76. package/src/manifest-json.test.ts +236 -0
  77. package/src/manifest-json.ts +166 -5
  78. package/src/module-config.test.ts +266 -105
  79. package/src/module-config.ts +130 -58
  80. package/src/pack-config.ts +2 -9
  81. package/src/render-context.ts +34 -6
  82. package/src/utils/import-map.test.ts +261 -0
  83. package/src/utils/import-map.ts +92 -6
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,196 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { afterEach, describe, expect, it } from "vitest";
4
+ import {
5
+ createFixtureRoot,
6
+ removeFixtureRoot,
7
+ writeFixturePackage
8
+ } from "./declaration/test-fixtures.mjs";
9
+ import {
10
+ buildManifestProtocolFields,
11
+ getManifestList,
12
+ MANIFEST_PROTOCOL_VERSION
13
+ } from "./manifest-json.mjs";
14
+ const fixtureRoots = [];
15
+ async function fixtureRoot() {
16
+ const root = await createFixtureRoot();
17
+ fixtureRoots.push(root);
18
+ return root;
19
+ }
20
+ afterEach(async () => {
21
+ await Promise.all(fixtureRoots.splice(0).map(removeFixtureRoot));
22
+ });
23
+ describe("buildManifestProtocolFields", () => {
24
+ it("should transcribe protocol, version and uses from the module package.json", async () => {
25
+ const root = await fixtureRoot();
26
+ const moduleRoot = writeFixturePackage(root, {
27
+ dir: "shared",
28
+ packageJson: {
29
+ name: "shared",
30
+ version: "1.8.0",
31
+ esmx: { provides: ["vue"], uses: ["base", "theme"] }
32
+ }
33
+ });
34
+ const fields = buildManifestProtocolFields(moduleRoot, []);
35
+ expect(fields.protocol).toBe(MANIFEST_PROTOCOL_VERSION);
36
+ expect(fields.protocol).toBe(2);
37
+ expect(fields.version).toBe("1.8.0");
38
+ expect(fields.uses).toEqual(["base", "theme"]);
39
+ expect(fields.provides).toEqual({});
40
+ });
41
+ it("should resolve provided package versions from node_modules of the module root", async () => {
42
+ const root = await fixtureRoot();
43
+ const moduleRoot = writeFixturePackage(root, {
44
+ dir: "shared",
45
+ packageJson: { name: "shared", version: "1.0.0" }
46
+ });
47
+ writeFixturePackage(root, {
48
+ dir: "shared/node_modules/vue",
49
+ packageJson: { name: "vue", version: "3.4.21" }
50
+ });
51
+ writeFixturePackage(root, {
52
+ dir: "shared/node_modules/@esmx/router",
53
+ packageJson: { name: "@esmx/router", version: "0.6.1" }
54
+ });
55
+ const fields = buildManifestProtocolFields(moduleRoot, [
56
+ "vue",
57
+ "@esmx/router"
58
+ ]);
59
+ expect(fields.provides).toEqual({
60
+ vue: { version: "3.4.21" },
61
+ "@esmx/router": { version: "0.6.1" }
62
+ });
63
+ });
64
+ it("should resolve subpath specifiers via their parent package", async () => {
65
+ const root = await fixtureRoot();
66
+ const moduleRoot = writeFixturePackage(root, {
67
+ dir: "shared",
68
+ packageJson: { name: "shared", version: "1.0.0" }
69
+ });
70
+ writeFixturePackage(root, {
71
+ dir: "shared/node_modules/vue",
72
+ packageJson: { name: "vue", version: "3.4.21" }
73
+ });
74
+ writeFixturePackage(root, {
75
+ dir: "shared/node_modules/@scope/pkg",
76
+ packageJson: { name: "@scope/pkg", version: "2.1.0" }
77
+ });
78
+ const fields = buildManifestProtocolFields(moduleRoot, [
79
+ "vue/jsx-runtime",
80
+ "@scope/pkg/client"
81
+ ]);
82
+ expect(fields.provides["vue/jsx-runtime"].version).toBe("3.4.21");
83
+ expect(fields.provides["@scope/pkg/client"].version).toBe("2.1.0");
84
+ });
85
+ it("should walk up node_modules for hoisted installs", async () => {
86
+ const root = await fixtureRoot();
87
+ const moduleRoot = writeFixturePackage(root, {
88
+ dir: "packages/shared",
89
+ packageJson: { name: "shared", version: "1.0.0" }
90
+ });
91
+ writeFixturePackage(root, {
92
+ dir: "node_modules/vue",
93
+ packageJson: { name: "vue", version: "3.5.2" }
94
+ });
95
+ const fields = buildManifestProtocolFields(moduleRoot, ["vue"]);
96
+ expect(fields.provides.vue.version).toBe("3.5.2");
97
+ });
98
+ it("should fall back to 0.0.0 for unresolvable packages", async () => {
99
+ const root = await fixtureRoot();
100
+ const moduleRoot = writeFixturePackage(root, {
101
+ dir: "shared",
102
+ packageJson: { name: "shared", version: "1.0.0" }
103
+ });
104
+ const fields = buildManifestProtocolFields(moduleRoot, ["ghost"]);
105
+ expect(fields.provides.ghost).toEqual({ version: "0.0.0" });
106
+ });
107
+ it("should default version and uses when package.json is missing or has no esmx field", async () => {
108
+ const root = await fixtureRoot();
109
+ const fields = buildManifestProtocolFields(
110
+ path.join(root, "does-not-exist"),
111
+ []
112
+ );
113
+ expect(fields).toEqual({
114
+ protocol: 2,
115
+ version: "0.0.0",
116
+ provides: {},
117
+ uses: []
118
+ });
119
+ });
120
+ });
121
+ function writeManifest(root, dir, manifest) {
122
+ const artifactDir = path.join(root, dir);
123
+ fs.mkdirSync(artifactDir, { recursive: true });
124
+ fs.writeFileSync(
125
+ path.join(artifactDir, "manifest.json"),
126
+ JSON.stringify(manifest)
127
+ );
128
+ return artifactDir;
129
+ }
130
+ function makeModuleConfig(name, dir) {
131
+ return {
132
+ links: {
133
+ [name]: { name, client: dir, server: dir }
134
+ }
135
+ };
136
+ }
137
+ const ENV = "client";
138
+ describe("getManifestList \u2014 protocol fields at read time", () => {
139
+ it("should default pre-v2 manifests to protocol 1 with empty v2 fields", async () => {
140
+ const root = await fixtureRoot();
141
+ const dir = writeManifest(root, "legacy/dist/client", {
142
+ name: "legacy",
143
+ exports: {},
144
+ scopes: {},
145
+ files: [],
146
+ chunks: {}
147
+ });
148
+ const [manifest] = await getManifestList(
149
+ ENV,
150
+ makeModuleConfig("legacy", dir)
151
+ );
152
+ expect(manifest.protocol).toBe(1);
153
+ expect(manifest.version).toBe("0.0.0");
154
+ expect(manifest.provides).toEqual({});
155
+ expect(manifest.uses).toEqual([]);
156
+ });
157
+ it("should keep v2 fields as emitted", async () => {
158
+ const root = await fixtureRoot();
159
+ const emitted = {
160
+ protocol: 2,
161
+ name: "shared",
162
+ version: "1.8.0",
163
+ provides: {
164
+ vue: { version: "3.4.21" }
165
+ },
166
+ uses: ["base"],
167
+ exports: {},
168
+ scopes: {},
169
+ files: [],
170
+ chunks: {}
171
+ };
172
+ const dir = writeManifest(root, "shared/dist/client", emitted);
173
+ const [manifest] = await getManifestList(
174
+ ENV,
175
+ makeModuleConfig("shared", dir)
176
+ );
177
+ expect(manifest.protocol).toBe(2);
178
+ expect(manifest.version).toBe("1.8.0");
179
+ expect(manifest.provides).toEqual(emitted.provides);
180
+ expect(manifest.uses).toEqual(["base"]);
181
+ });
182
+ it("should reject manifests with a protocol higher than the linker supports", async () => {
183
+ const root = await fixtureRoot();
184
+ const dir = writeManifest(root, "future/dist/client", {
185
+ protocol: MANIFEST_PROTOCOL_VERSION + 1,
186
+ name: "future",
187
+ exports: {},
188
+ scopes: {},
189
+ files: [],
190
+ chunks: {}
191
+ });
192
+ await expect(
193
+ getManifestList(ENV, makeModuleConfig("future", dir))
194
+ ).rejects.toThrow(/E_PROTOCOL/);
195
+ });
196
+ });
@@ -1,11 +1,22 @@
1
1
  import type { BuildEnvironment } from './core';
2
2
  export interface ModuleConfig {
3
3
  lib?: boolean;
4
+ entry?: ModuleConfigEntry;
4
5
  links?: Record<string, string>;
5
6
  imports?: ModuleConfigImportMapping;
6
- scopes?: Record<string, ModuleConfigImportMapping>;
7
7
  exports?: ModuleConfigExportExports;
8
8
  }
9
+ /**
10
+ * Framework entry declaration (RFC 0001 Phase 2).
11
+ *
12
+ * Per side: a `./`-relative source file path declares a custom entry,
13
+ * `false` disables the side, and `undefined` keeps the legacy default
14
+ * (`./src/entry.client` / `./src/entry.server`).
15
+ */
16
+ export interface ModuleConfigEntry {
17
+ client?: string | false;
18
+ server?: string | false;
19
+ }
9
20
  export type ModuleConfigImportMapping = Record<string, string | Record<BuildEnvironment, string>>;
10
21
  export type ModuleConfigExportExports = ModuleConfigExportExport[];
11
22
  export type ModuleConfigExportExport = string | ModuleConfigExportObject;
@@ -15,12 +26,24 @@ export interface ParsedModuleConfig {
15
26
  name: string;
16
27
  root: string;
17
28
  lib: boolean;
29
+ entry: ParsedModuleConfigEntry;
18
30
  links: Record<string, ParsedModuleConfigLink>;
19
31
  environments: {
20
32
  client: ParsedModuleConfigEnvironment;
21
33
  server: ParsedModuleConfigEnvironment;
22
34
  };
23
35
  }
36
+ /** A resolved framework entry: export name + source file path. */
37
+ export interface ParsedModuleConfigEntryTarget {
38
+ /** Export name (import specifier suffix), e.g. 'src/entry.client'. */
39
+ name: string;
40
+ /** Source file path, e.g. './src/entry.client' or './custom/main.ts'. */
41
+ file: string;
42
+ }
43
+ export interface ParsedModuleConfigEntry {
44
+ client: ParsedModuleConfigEntryTarget | null;
45
+ server: ParsedModuleConfigEntryTarget | null;
46
+ }
24
47
  export type ParsedModuleConfigExports = Record<string, ParsedModuleConfigExport>;
25
48
  export interface ParsedModuleConfigExport {
26
49
  name: string;
@@ -40,16 +63,33 @@ export interface ParsedModuleConfigLink {
40
63
  server: string;
41
64
  serverManifestJson: string;
42
65
  }
66
+ /**
67
+ * Single source of truth for the default framework entries, used as the
68
+ * fallback when a module declares no explicit `entry` (RFC 0001 Phase 2
69
+ * threads resolved entries through the config instead of hard-coding them).
70
+ */
71
+ export declare const DEFAULT_MODULE_ENTRY: Record<'client' | 'server', ParsedModuleConfigEntryTarget>;
72
+ export declare function parseEntryConfig(config: ModuleConfig): ParsedModuleConfigEntry;
73
+ /**
74
+ * Manifest chunk identifier for an entry target: `<module>@<srcRelPath>`
75
+ * including the file extension (bundlers key chunks by the resolved source
76
+ * file, so the legacy extensionless default maps to `.ts`).
77
+ */
78
+ export declare function getEntryChunkId(moduleName: string, target: ParsedModuleConfigEntryTarget): string;
43
79
  export declare function parseModuleConfig(name: string, root: string, config?: ModuleConfig): ParsedModuleConfig;
44
80
  export declare function getLinks(name: string, root: string, config: ModuleConfig): Record<string, ParsedModuleConfigLink>;
45
81
  export declare function getEnvironmentImports(environment: BuildEnvironment, imports?: ModuleConfigImportMapping): Record<string, string>;
46
- export declare function getEnvironmentScopes(environment: BuildEnvironment, scopes?: Record<string, ModuleConfigImportMapping>): Record<string, Record<string, string>>;
47
- export declare function getEnvironments(config: ModuleConfig, env: BuildEnvironment, moduleName: string): ParsedModuleConfigEnvironment;
48
- export declare function createDefaultExports(env: BuildEnvironment): ParsedModuleConfigExports;
82
+ export declare function getEnvironments(config: ModuleConfig, env: BuildEnvironment, moduleName: string, entry?: ParsedModuleConfigEntry): ParsedModuleConfigEnvironment;
83
+ /**
84
+ * Builds the per-environment exports for the resolved framework entries.
85
+ * Each entry exports its source file in its own environment and an empty
86
+ * file on the other side (the inactive side is skipped by adapters).
87
+ */
88
+ export declare function createEntryExports(entry: ParsedModuleConfigEntry, env: BuildEnvironment): ParsedModuleConfigExports;
49
89
  export declare function processStringExport(exportString: string): ParsedModuleConfigExports;
50
90
  export declare function processObjectExport(exportObject: ModuleConfigExportObject, env: BuildEnvironment): ParsedModuleConfigExports;
51
91
  export declare function resolveExportFile(config: ModuleConfigExportObjectValue, env: BuildEnvironment, name: string): string;
52
92
  export declare function processExportArray(exportArray: ModuleConfigExportExports, env: BuildEnvironment): ParsedModuleConfigExports;
53
- export declare function getEnvironmentExports(config: ModuleConfig, env: BuildEnvironment): ParsedModuleConfigExports;
93
+ export declare function getEnvironmentExports(config: ModuleConfig, env: BuildEnvironment, entry?: ParsedModuleConfigEntry): ParsedModuleConfigExports;
54
94
  export declare function addPackageExportsToScopes(exports: ParsedModuleConfigExports, scopes: Record<string, Record<string, string>>, moduleName: string): Record<string, Record<string, string>>;
55
95
  export declare function parsedExportValue(value: string): ParsedModuleConfigExport;
@@ -1,13 +1,51 @@
1
1
  import path from "node:path";
2
+ export const DEFAULT_MODULE_ENTRY = {
3
+ client: { name: "src/entry.client", file: "./src/entry.client" },
4
+ server: { name: "src/entry.server", file: "./src/entry.server" }
5
+ };
6
+ const FILE_EXT_REGEX = /\.(js|mjs|cjs|jsx|mjsx|cjsx|ts|mts|cts|tsx|mtsx|ctsx)$/i;
7
+ function parseEntryTarget(value, fallback) {
8
+ if (value === false) {
9
+ return null;
10
+ }
11
+ if (value === void 0) {
12
+ return { ...fallback };
13
+ }
14
+ const relativePath = value.startsWith("./") ? value.slice(2) : value;
15
+ const parsed = parsedExportValue(`root:${relativePath}`);
16
+ return { name: parsed.name, file: parsed.file };
17
+ }
18
+ export function parseEntryConfig(config) {
19
+ if (config.lib) {
20
+ return { client: null, server: null };
21
+ }
22
+ return {
23
+ client: parseEntryTarget(
24
+ config.entry?.client,
25
+ DEFAULT_MODULE_ENTRY.client
26
+ ),
27
+ server: parseEntryTarget(
28
+ config.entry?.server,
29
+ DEFAULT_MODULE_ENTRY.server
30
+ )
31
+ };
32
+ }
33
+ export function getEntryChunkId(moduleName, target) {
34
+ const relativePath = target.file.startsWith("./") ? target.file.slice(2) : target.file;
35
+ const withExtension = FILE_EXT_REGEX.test(relativePath) ? relativePath : `${relativePath}.ts`;
36
+ return `${moduleName}@${withExtension}`;
37
+ }
2
38
  export function parseModuleConfig(name, root, config = {}) {
39
+ const entry = parseEntryConfig(config);
3
40
  return {
4
41
  name,
5
42
  root,
6
43
  lib: config.lib ?? false,
44
+ entry,
7
45
  links: getLinks(name, root, config),
8
46
  environments: {
9
- client: getEnvironments(config, "client", name),
10
- server: getEnvironments(config, "server", name)
47
+ client: getEnvironments(config, "client", name, entry),
48
+ server: getEnvironments(config, "server", name, entry)
11
49
  }
12
50
  };
13
51
  }
@@ -46,23 +84,12 @@ export function getEnvironmentImports(environment, imports = {}) {
46
84
  }
47
85
  return result;
48
86
  }
49
- export function getEnvironmentScopes(environment, scopes = {}) {
50
- const result = {};
51
- for (const [scopeName, scopeImports] of Object.entries(scopes)) {
52
- result[scopeName] = getEnvironmentImports(environment, scopeImports);
53
- }
54
- return result;
55
- }
56
- export function getEnvironments(config, env, moduleName) {
87
+ export function getEnvironments(config, env, moduleName, entry = parseEntryConfig(config)) {
57
88
  const imports = getEnvironmentImports(env, config.imports);
58
- const exports = getEnvironmentExports(config, env);
59
- const scopes = getEnvironmentScopes(env, {
60
- ...config.scopes,
61
- "": {
62
- ...config.scopes?.[""],
63
- ...imports
64
- }
65
- });
89
+ const exports = getEnvironmentExports(config, env, entry);
90
+ const scopes = {
91
+ "": { ...imports }
92
+ };
66
93
  addPackageExportsToScopes(exports, scopes, moduleName);
67
94
  return {
68
95
  imports,
@@ -70,35 +97,20 @@ export function getEnvironments(config, env, moduleName) {
70
97
  scopes
71
98
  };
72
99
  }
73
- export function createDefaultExports(env) {
74
- switch (env) {
75
- case "client":
76
- return {
77
- "src/entry.client": {
78
- name: "src/entry.client",
79
- file: "./src/entry.client",
80
- pkg: false
81
- },
82
- "src/entry.server": {
83
- name: "src/entry.server",
84
- file: "",
85
- pkg: false
86
- }
87
- };
88
- case "server":
89
- return {
90
- "src/entry.client": {
91
- name: "src/entry.client",
92
- file: "",
93
- pkg: false
94
- },
95
- "src/entry.server": {
96
- name: "src/entry.server",
97
- file: "./src/entry.server",
98
- pkg: false
99
- }
100
- };
100
+ export function createEntryExports(entry, env) {
101
+ const exports = {};
102
+ for (const side of ["client", "server"]) {
103
+ const target = entry[side];
104
+ if (!target) {
105
+ continue;
106
+ }
107
+ exports[target.name] = {
108
+ name: target.name,
109
+ file: side === env ? target.file : "",
110
+ pkg: false
111
+ };
101
112
  }
113
+ return exports;
102
114
  }
103
115
  export function processStringExport(exportString) {
104
116
  const parsedValue = parsedExportValue(exportString);
@@ -144,8 +156,8 @@ export function processExportArray(exportArray, env) {
144
156
  });
145
157
  return exports;
146
158
  }
147
- export function getEnvironmentExports(config, env) {
148
- const exports = config.lib ? {} : createDefaultExports(env);
159
+ export function getEnvironmentExports(config, env, entry = parseEntryConfig(config)) {
160
+ const exports = createEntryExports(entry, env);
149
161
  if (config.exports) {
150
162
  const userExports = processExportArray(config.exports, env);
151
163
  Object.assign(exports, userExports);
@@ -164,7 +176,6 @@ export function addPackageExportsToScopes(exports, scopes, moduleName) {
164
176
  return scopes;
165
177
  }
166
178
  export function parsedExportValue(value) {
167
- const FILE_EXT_REGEX = /\.(js|mjs|cjs|jsx|mjsx|cjsx|ts|mts|cts|tsx|mtsx|ctsx)$/i;
168
179
  if (value.startsWith("pkg:")) {
169
180
  const item = value.substring("pkg:".length);
170
181
  return {