@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.
- 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
package/dist/core.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { cwd } from "node:process";
|
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import serialize from "serialize-javascript";
|
|
8
8
|
import { createApp } from "./app.mjs";
|
|
9
|
+
import { resolveModuleOptions } from "./declaration/index.mjs";
|
|
9
10
|
import { getManifestList } from "./manifest-json.mjs";
|
|
10
11
|
import {
|
|
11
12
|
parseModuleConfig
|
|
@@ -243,13 +244,14 @@ export class Esmx {
|
|
|
243
244
|
if (this._readied) {
|
|
244
245
|
throw new Error("Cannot be initialized repeatedly");
|
|
245
246
|
}
|
|
246
|
-
const
|
|
247
|
+
const packageJson = await this.readJson(
|
|
247
248
|
path.resolve(this.root, "package.json")
|
|
248
249
|
);
|
|
250
|
+
const name = String(packageJson.name);
|
|
249
251
|
const moduleConfig = parseModuleConfig(
|
|
250
252
|
name,
|
|
251
253
|
this.root,
|
|
252
|
-
this._options.modules
|
|
254
|
+
resolveModuleOptions(this.root, packageJson, this._options.modules)
|
|
253
255
|
);
|
|
254
256
|
const packConfig = parsePackConfig(this._options.packs);
|
|
255
257
|
this._readied = {
|
|
@@ -312,6 +314,67 @@ export class Esmx {
|
|
|
312
314
|
}
|
|
313
315
|
return true;
|
|
314
316
|
}
|
|
317
|
+
/**
|
|
318
|
+
* Atomic generational relink (RFC 0001 §9).
|
|
319
|
+
*
|
|
320
|
+
* In-process remotes are not hot-swappable: the server import map and the
|
|
321
|
+
* module loader are captured once when the app is created. When a mounted
|
|
322
|
+
* remote republishes (new resolved versions, new wiring), the composer
|
|
323
|
+
* adopts it by relinking — building a NEW generation (fresh cache,
|
|
324
|
+
* re-resolved module config, rebuilt app capturing a new import map +
|
|
325
|
+
* loader) and switching to it atomically.
|
|
326
|
+
*
|
|
327
|
+
* The previous generation keeps serving throughout the rebuild; the new
|
|
328
|
+
* app is installed only once it is fully built, then the previous app's
|
|
329
|
+
* resources are released. If the rebuild fails, the previous generation
|
|
330
|
+
* is fully restored (rollback) and the error propagates — a failed relink
|
|
331
|
+
* never takes the running server down.
|
|
332
|
+
*
|
|
333
|
+
* Scope: the production render path (`start`/`preview`). Dev rebuilds its
|
|
334
|
+
* own app. Cross-compiler dev-watch invalidation remains future work.
|
|
335
|
+
*
|
|
336
|
+
* @returns true once the new generation is live.
|
|
337
|
+
* @throws {NotReadyError} when called before {@link init}.
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* // After a mounted remote republishes:
|
|
342
|
+
* await esmx.reinit(); // new generation live, old requests drained
|
|
343
|
+
* ```
|
|
344
|
+
*/
|
|
345
|
+
async reinit() {
|
|
346
|
+
const previous = this.readied;
|
|
347
|
+
const packageJson = await this.readJson(
|
|
348
|
+
path.resolve(this.root, "package.json")
|
|
349
|
+
);
|
|
350
|
+
const name = String(packageJson.name);
|
|
351
|
+
const moduleConfig = parseModuleConfig(
|
|
352
|
+
name,
|
|
353
|
+
this.root,
|
|
354
|
+
resolveModuleOptions(this.root, packageJson, this._options.modules)
|
|
355
|
+
);
|
|
356
|
+
const packConfig = parsePackConfig(this._options.packs);
|
|
357
|
+
this._readied = {
|
|
358
|
+
command: previous.command,
|
|
359
|
+
app: previous.app,
|
|
360
|
+
moduleConfig,
|
|
361
|
+
packConfig,
|
|
362
|
+
cache: createCache(this.isProd)
|
|
363
|
+
};
|
|
364
|
+
try {
|
|
365
|
+
const command = previous.command;
|
|
366
|
+
const devApp = this._options.devApp || defaultDevApp;
|
|
367
|
+
const app = ["dev" /* dev */, "build" /* build */].includes(command) ? await devApp(this) : await createApp(this, command);
|
|
368
|
+
this.readied.app = app;
|
|
369
|
+
} catch (error) {
|
|
370
|
+
this._readied = previous;
|
|
371
|
+
throw error;
|
|
372
|
+
}
|
|
373
|
+
if (previous.app?.destroy) {
|
|
374
|
+
await previous.app.destroy();
|
|
375
|
+
}
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
315
378
|
/**
|
|
316
379
|
* Execute the application's build process.
|
|
317
380
|
*
|
|
@@ -465,7 +528,7 @@ export class Esmx {
|
|
|
465
528
|
await this._options.postBuild?.(this);
|
|
466
529
|
return true;
|
|
467
530
|
} catch (e) {
|
|
468
|
-
console.error(e);
|
|
531
|
+
console.error("[@esmx/core] postBuild hook failed:", e);
|
|
469
532
|
return false;
|
|
470
533
|
}
|
|
471
534
|
}
|
|
@@ -770,6 +833,7 @@ export class Esmx {
|
|
|
770
833
|
`getImportMap-${mode}`,
|
|
771
834
|
async () => {
|
|
772
835
|
const importmap = await this.getImportMap("client");
|
|
836
|
+
const { integrity } = importmap;
|
|
773
837
|
const { basePathPlaceholder } = this;
|
|
774
838
|
let filepath = null;
|
|
775
839
|
if (this._importmapHash === null) {
|
|
@@ -817,7 +881,8 @@ document.head.appendChild(script);
|
|
|
817
881
|
return {
|
|
818
882
|
src,
|
|
819
883
|
filepath,
|
|
820
|
-
code: `<script data-base="${basePathPlaceholder}" src="${src}"><\/script
|
|
884
|
+
code: `<script data-base="${basePathPlaceholder}" src="${src}"><\/script>`,
|
|
885
|
+
integrity: integrity ?? null
|
|
821
886
|
};
|
|
822
887
|
}
|
|
823
888
|
if (basePathPlaceholder) {
|
|
@@ -835,7 +900,8 @@ document.head.appendChild(script);
|
|
|
835
900
|
return {
|
|
836
901
|
src: null,
|
|
837
902
|
filepath: null,
|
|
838
|
-
code: `<script type="importmap">${serialize(importmap, { isJSON: true, unsafe: true })}<\/script
|
|
903
|
+
code: `<script type="importmap">${serialize(importmap, { isJSON: true, unsafe: true })}<\/script>`,
|
|
904
|
+
integrity: integrity ?? null
|
|
839
905
|
};
|
|
840
906
|
}
|
|
841
907
|
);
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { ModuleConfig } from '../module-config';
|
|
2
|
+
import type { ResolvedMount, ResolverEnvLinks, SupplyEntry } from './resolver';
|
|
3
|
+
import type { Diagnostic } from './types';
|
|
4
|
+
export { lowerDeclaration } from './lower';
|
|
5
|
+
export type { PackageRecord, ReadDeclarationResult } from './reader';
|
|
6
|
+
export { readDeclaration, readPackageRecord } from './reader';
|
|
7
|
+
export type { ResolvedMount, ResolveMountsResult, ResolverEnvLinks, SupplyEntry, SupplyGroup } from './resolver';
|
|
8
|
+
export { resolveMounts, selectSupplyGroup } from './resolver';
|
|
9
|
+
export type { ValidateDeclarationResult } from './schema';
|
|
10
|
+
export { esmxDeclarationSchema, validateDeclaration } from './schema';
|
|
11
|
+
export { parseSemver, satisfiesRange } from './semver';
|
|
12
|
+
export type { Diagnostic, DiagnosticCheck, DiagnosticCodeValue, DiagnosticSeverity, EsmxDeclaration, EsmxDeclarationEntry, EsmxDeclarationExportFork, EsmxDeclarationExportValue } from './types';
|
|
13
|
+
export { DiagnosticCode } from './types';
|
|
14
|
+
export interface ResolveDeclarationOptions {
|
|
15
|
+
/**
|
|
16
|
+
* Explicit mount overrides (environment fact): module name → artifact
|
|
17
|
+
* directory, same semantics as today's `ModuleConfig.links` values,
|
|
18
|
+
* resolved relative to `rootDir`.
|
|
19
|
+
*/
|
|
20
|
+
envLinks?: ResolverEnvLinks;
|
|
21
|
+
}
|
|
22
|
+
export interface ResolveDeclarationResult {
|
|
23
|
+
/** The declaration lowered to today's internal ModuleConfig IR. */
|
|
24
|
+
config: ModuleConfig;
|
|
25
|
+
/** Merged supply table: bare package → per-major group winners. */
|
|
26
|
+
supply: Record<string, SupplyEntry>;
|
|
27
|
+
/** Resolved mount table: module name → mounted artifact location. */
|
|
28
|
+
mounts: Record<string, ResolvedMount>;
|
|
29
|
+
diagnostics: Diagnostic[];
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* One-call helper composing reader + resolver + lowering (RFC Phase 1).
|
|
33
|
+
* Returns null when the package at `rootDir` has no `esmx` declaration.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveDeclaration(rootDir: string, options?: ResolveDeclarationOptions): ResolveDeclarationResult | null;
|
|
36
|
+
/**
|
|
37
|
+
* Integration point for `Esmx.init`: decides between the legacy
|
|
38
|
+
* `options.modules` path and the declaration path.
|
|
39
|
+
*
|
|
40
|
+
* - No `esmx` field in package.json → legacy path, untouched.
|
|
41
|
+
* - `esmx` field present and `options.modules` carries protocol facts
|
|
42
|
+
* (lib/imports/exports) → E_PROTOCOL_IN_BEHAVIOR (protocol facts
|
|
43
|
+
* must live in package.json only).
|
|
44
|
+
* - `links` alone is an environment fact and is passed to the resolver as
|
|
45
|
+
* explicit mount overrides.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveModuleOptions(rootDir: string, packageJson: Record<string, unknown>, modules?: ModuleConfig): ModuleConfig | undefined;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { lowerDeclaration } from "./lower.mjs";
|
|
2
|
+
import { readDeclaration } from "./reader.mjs";
|
|
3
|
+
import { resolveMounts } from "./resolver.mjs";
|
|
4
|
+
export { lowerDeclaration } from "./lower.mjs";
|
|
5
|
+
export { readDeclaration, readPackageRecord } from "./reader.mjs";
|
|
6
|
+
export { resolveMounts, selectSupplyGroup } from "./resolver.mjs";
|
|
7
|
+
export { esmxDeclarationSchema, validateDeclaration } from "./schema.mjs";
|
|
8
|
+
export { parseSemver, satisfiesRange } from "./semver.mjs";
|
|
9
|
+
export { DiagnosticCode } from "./types.mjs";
|
|
10
|
+
export function resolveDeclaration(rootDir, options = {}) {
|
|
11
|
+
const pkg = readDeclaration(rootDir);
|
|
12
|
+
if (!pkg) {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
const resolution = resolveMounts(rootDir, pkg, options.envLinks);
|
|
16
|
+
return {
|
|
17
|
+
config: lowerDeclaration(pkg, resolution),
|
|
18
|
+
supply: resolution.supply,
|
|
19
|
+
mounts: resolution.mounts,
|
|
20
|
+
diagnostics: resolution.diagnostics
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const PROTOCOL_MODULE_KEYS = ["lib", "imports", "exports"];
|
|
24
|
+
function formatDiagnostic(diagnostic) {
|
|
25
|
+
const location = diagnostic.package ? `${diagnostic.module} \u2192 ${diagnostic.package}` : diagnostic.module;
|
|
26
|
+
return `[${diagnostic.code}] (${location}) ${diagnostic.message} Fix: ${diagnostic.fix}`;
|
|
27
|
+
}
|
|
28
|
+
export function resolveModuleOptions(rootDir, packageJson, modules) {
|
|
29
|
+
if (packageJson.esmx === void 0) {
|
|
30
|
+
return modules;
|
|
31
|
+
}
|
|
32
|
+
if (modules) {
|
|
33
|
+
const protocolKeys = PROTOCOL_MODULE_KEYS.filter(
|
|
34
|
+
(key) => modules[key] !== void 0
|
|
35
|
+
);
|
|
36
|
+
if (protocolKeys.length > 0) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`[E_PROTOCOL_IN_BEHAVIOR] package.json declares "esmx" but options.modules contains protocol field(s): ${protocolKeys.join(", ")}. Protocol facts must live in the package.json "esmx" field only; entry.node.ts keeps behavior (devApp, server, postBuild) and environment links. Fix: move ${protocolKeys.join(", ")} into the "esmx" declaration and delete them from options.modules.`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const resolved = resolveDeclaration(rootDir, {
|
|
43
|
+
envLinks: modules?.links
|
|
44
|
+
});
|
|
45
|
+
if (!resolved) {
|
|
46
|
+
return modules;
|
|
47
|
+
}
|
|
48
|
+
const errors = resolved.diagnostics.filter(
|
|
49
|
+
(diagnostic) => diagnostic.severity === "error"
|
|
50
|
+
);
|
|
51
|
+
if (errors.length > 0) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`Esmx declaration resolution failed with ${errors.length} error(s):
|
|
54
|
+
` + errors.map((error) => ` ${formatDiagnostic(error)}`).join("\n")
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
for (const warning of resolved.diagnostics) {
|
|
58
|
+
if (warning.severity === "warning") {
|
|
59
|
+
console.warn(formatDiagnostic(warning));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return resolved.config;
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { resolveDeclaration, resolveModuleOptions } from "./index.mjs";
|
|
4
|
+
import {
|
|
5
|
+
createFixtureRoot,
|
|
6
|
+
removeFixtureRoot,
|
|
7
|
+
writeFixturePackage
|
|
8
|
+
} from "./test-fixtures.mjs";
|
|
9
|
+
const fixtureRoots = [];
|
|
10
|
+
async function fixtureRoot() {
|
|
11
|
+
const root = await createFixtureRoot();
|
|
12
|
+
fixtureRoots.push(root);
|
|
13
|
+
return root;
|
|
14
|
+
}
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
await Promise.all(fixtureRoots.splice(0).map(removeFixtureRoot));
|
|
18
|
+
});
|
|
19
|
+
describe("resolveDeclaration", () => {
|
|
20
|
+
it("should return null when the package has no esmx field", async () => {
|
|
21
|
+
const root = await fixtureRoot();
|
|
22
|
+
const appDir = writeFixturePackage(root, {
|
|
23
|
+
dir: "legacy",
|
|
24
|
+
packageJson: { name: "legacy", version: "1.0.0" }
|
|
25
|
+
});
|
|
26
|
+
expect(resolveDeclaration(appDir)).toBeNull();
|
|
27
|
+
});
|
|
28
|
+
it("should compose reader, resolver and lowering", async () => {
|
|
29
|
+
const root = await fixtureRoot();
|
|
30
|
+
writeFixturePackage(root, {
|
|
31
|
+
dir: "app/node_modules/shared",
|
|
32
|
+
packageJson: {
|
|
33
|
+
name: "shared",
|
|
34
|
+
version: "1.0.0",
|
|
35
|
+
esmx: { provides: ["vue"] }
|
|
36
|
+
},
|
|
37
|
+
built: true
|
|
38
|
+
});
|
|
39
|
+
writeFixturePackage(root, {
|
|
40
|
+
dir: "app/node_modules/shared/node_modules/vue",
|
|
41
|
+
packageJson: { name: "vue", version: "3.4.21" }
|
|
42
|
+
});
|
|
43
|
+
const appDir = writeFixturePackage(root, {
|
|
44
|
+
dir: "app",
|
|
45
|
+
packageJson: {
|
|
46
|
+
name: "app",
|
|
47
|
+
version: "1.0.0",
|
|
48
|
+
dependencies: { vue: "^3.4.0" },
|
|
49
|
+
esmx: {
|
|
50
|
+
entry: {
|
|
51
|
+
client: "./src/entry.client.ts",
|
|
52
|
+
server: "./src/entry.server.ts"
|
|
53
|
+
},
|
|
54
|
+
uses: ["shared"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const result = resolveDeclaration(appDir);
|
|
59
|
+
expect(result).not.toBeNull();
|
|
60
|
+
expect(result?.supply.vue).toEqual({
|
|
61
|
+
groups: [{ major: 3, provider: "shared", version: "3.4.21" }]
|
|
62
|
+
});
|
|
63
|
+
expect(result?.config).toEqual({
|
|
64
|
+
links: {
|
|
65
|
+
shared: path.join(appDir, "node_modules/shared/dist")
|
|
66
|
+
},
|
|
67
|
+
imports: { vue: "shared/vue" }
|
|
68
|
+
});
|
|
69
|
+
expect(
|
|
70
|
+
result?.diagnostics.filter((d) => d.severity === "error")
|
|
71
|
+
).toEqual([]);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
describe("resolveModuleOptions", () => {
|
|
75
|
+
it("should keep the legacy path untouched when there is no esmx field", async () => {
|
|
76
|
+
const root = await fixtureRoot();
|
|
77
|
+
const appDir = writeFixturePackage(root, {
|
|
78
|
+
dir: "legacy",
|
|
79
|
+
packageJson: { name: "legacy", version: "1.0.0" }
|
|
80
|
+
});
|
|
81
|
+
const modules = {
|
|
82
|
+
imports: { vue: "shared/vue" },
|
|
83
|
+
exports: ["pkg:vue"]
|
|
84
|
+
};
|
|
85
|
+
const result = resolveModuleOptions(
|
|
86
|
+
appDir,
|
|
87
|
+
{ name: "legacy" },
|
|
88
|
+
modules
|
|
89
|
+
);
|
|
90
|
+
expect(result).toBe(modules);
|
|
91
|
+
});
|
|
92
|
+
it("should throw E_PROTOCOL_IN_BEHAVIOR when esmx coexists with protocol fields in options.modules", async () => {
|
|
93
|
+
const root = await fixtureRoot();
|
|
94
|
+
const appDir = writeFixturePackage(root, {
|
|
95
|
+
dir: "app",
|
|
96
|
+
packageJson: {
|
|
97
|
+
name: "app",
|
|
98
|
+
version: "1.0.0",
|
|
99
|
+
esmx: { provides: ["vue"] }
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
expect(
|
|
103
|
+
() => resolveModuleOptions(
|
|
104
|
+
appDir,
|
|
105
|
+
{ name: "app", esmx: { provides: ["vue"] } },
|
|
106
|
+
{ imports: { vue: "shared/vue" } }
|
|
107
|
+
)
|
|
108
|
+
).toThrowError(/E_PROTOCOL_IN_BEHAVIOR/);
|
|
109
|
+
});
|
|
110
|
+
it("should allow links-only options.modules as environment overrides", async () => {
|
|
111
|
+
const root = await fixtureRoot();
|
|
112
|
+
writeFixturePackage(root, {
|
|
113
|
+
dir: "shared",
|
|
114
|
+
packageJson: {
|
|
115
|
+
name: "shared",
|
|
116
|
+
version: "1.0.0",
|
|
117
|
+
esmx: { provides: ["vue"] }
|
|
118
|
+
},
|
|
119
|
+
built: true
|
|
120
|
+
});
|
|
121
|
+
writeFixturePackage(root, {
|
|
122
|
+
dir: "shared/node_modules/vue",
|
|
123
|
+
packageJson: { name: "vue", version: "3.4.21" }
|
|
124
|
+
});
|
|
125
|
+
const appDir = writeFixturePackage(root, {
|
|
126
|
+
dir: "app",
|
|
127
|
+
packageJson: {
|
|
128
|
+
name: "app",
|
|
129
|
+
version: "1.0.0",
|
|
130
|
+
dependencies: { vue: "^3.4.0" },
|
|
131
|
+
esmx: {
|
|
132
|
+
entry: {
|
|
133
|
+
client: "./src/entry.client.ts",
|
|
134
|
+
server: "./src/entry.server.ts"
|
|
135
|
+
},
|
|
136
|
+
uses: ["shared"]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
const result = resolveModuleOptions(
|
|
141
|
+
appDir,
|
|
142
|
+
{ name: "app", esmx: {} },
|
|
143
|
+
{ links: { shared: "../shared/dist" } }
|
|
144
|
+
);
|
|
145
|
+
expect(result).toEqual({
|
|
146
|
+
links: { shared: path.join(root, "shared/dist") },
|
|
147
|
+
imports: { vue: "shared/vue" }
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
it("should throw a clear error listing error diagnostics", async () => {
|
|
151
|
+
const root = await fixtureRoot();
|
|
152
|
+
const appDir = writeFixturePackage(root, {
|
|
153
|
+
dir: "app",
|
|
154
|
+
packageJson: {
|
|
155
|
+
name: "app",
|
|
156
|
+
version: "1.0.0",
|
|
157
|
+
esmx: {
|
|
158
|
+
entry: { client: "./src/entry.client.ts" },
|
|
159
|
+
uses: ["ghost"]
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
expect(
|
|
164
|
+
() => resolveModuleOptions(appDir, { name: "app", esmx: {} })
|
|
165
|
+
).toThrowError(/E_NOT_LINKED[\s\S]*ghost/);
|
|
166
|
+
});
|
|
167
|
+
it("should print warnings instead of throwing", async () => {
|
|
168
|
+
const root = await fixtureRoot();
|
|
169
|
+
writeFixturePackage(root, {
|
|
170
|
+
dir: "app/node_modules/base",
|
|
171
|
+
packageJson: {
|
|
172
|
+
name: "base",
|
|
173
|
+
version: "1.0.0",
|
|
174
|
+
dependencies: { vue: "^3.4.0" },
|
|
175
|
+
esmx: { provides: ["vue"] }
|
|
176
|
+
},
|
|
177
|
+
built: true
|
|
178
|
+
});
|
|
179
|
+
writeFixturePackage(root, {
|
|
180
|
+
dir: "app/node_modules/base/node_modules/vue",
|
|
181
|
+
packageJson: { name: "vue", version: "3.4.21" }
|
|
182
|
+
});
|
|
183
|
+
const appDir = writeFixturePackage(root, {
|
|
184
|
+
dir: "app",
|
|
185
|
+
packageJson: {
|
|
186
|
+
name: "app",
|
|
187
|
+
version: "1.0.0",
|
|
188
|
+
esmx: {
|
|
189
|
+
entry: { client: "./src/entry.client.ts" },
|
|
190
|
+
uses: ["base"]
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {
|
|
195
|
+
});
|
|
196
|
+
const result = resolveModuleOptions(appDir, { name: "app", esmx: {} });
|
|
197
|
+
expect(result?.imports).toEqual({ vue: "base/vue" });
|
|
198
|
+
expect(warn).toHaveBeenCalledWith(
|
|
199
|
+
expect.stringContaining("W_NO_RANGE")
|
|
200
|
+
);
|
|
201
|
+
});
|
|
202
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ModuleConfig } from '../module-config';
|
|
2
|
+
import type { ReadDeclarationResult } from './reader';
|
|
3
|
+
import type { ResolveMountsResult } from './resolver';
|
|
4
|
+
/**
|
|
5
|
+
* Lowers a declaration + resolution result to today's internal
|
|
6
|
+
* `ModuleConfig` IR (RFC Phase 1+2): default entry paths ride on the
|
|
7
|
+
* legacy defaults, custom/disabled entries flow through `config.entry`,
|
|
8
|
+
* `provides` become `pkg:` exports, the merged supply table becomes
|
|
9
|
+
* `imports`, and mounts become `links`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function lowerDeclaration(pkg: ReadDeclarationResult, resolution: ResolveMountsResult): ModuleConfig;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { selectSupplyGroup } from "./resolver.mjs";
|
|
2
|
+
const DEFAULT_CLIENT_ENTRY = "./src/entry.client.ts";
|
|
3
|
+
const DEFAULT_SERVER_ENTRY = "./src/entry.server.ts";
|
|
4
|
+
function toRootExport(relativePath) {
|
|
5
|
+
return `root:${relativePath.slice(2)}`;
|
|
6
|
+
}
|
|
7
|
+
function lowerExportValue(value) {
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
return toRootExport(value);
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
client: typeof value.client === "string" ? toRootExport(value.client) : false,
|
|
13
|
+
server: typeof value.server === "string" ? toRootExport(value.server) : false
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function lowerEntry(entry) {
|
|
17
|
+
const lowered = {};
|
|
18
|
+
if (!entry.client) {
|
|
19
|
+
lowered.client = false;
|
|
20
|
+
} else if (entry.client !== DEFAULT_CLIENT_ENTRY) {
|
|
21
|
+
lowered.client = entry.client;
|
|
22
|
+
}
|
|
23
|
+
if (!entry.server) {
|
|
24
|
+
lowered.server = false;
|
|
25
|
+
} else if (entry.server !== DEFAULT_SERVER_ENTRY) {
|
|
26
|
+
lowered.server = entry.server;
|
|
27
|
+
}
|
|
28
|
+
return Object.keys(lowered).length > 0 ? lowered : null;
|
|
29
|
+
}
|
|
30
|
+
export function lowerDeclaration(pkg, resolution) {
|
|
31
|
+
const { declaration } = pkg;
|
|
32
|
+
const config = {};
|
|
33
|
+
if (!declaration.entry) {
|
|
34
|
+
config.lib = true;
|
|
35
|
+
} else {
|
|
36
|
+
const entry = lowerEntry(declaration.entry);
|
|
37
|
+
if (entry) {
|
|
38
|
+
config.entry = entry;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const links = {};
|
|
42
|
+
for (const mount of Object.values(resolution.mounts)) {
|
|
43
|
+
links[mount.name] = mount.artifactDir;
|
|
44
|
+
}
|
|
45
|
+
if (Object.keys(links).length > 0) {
|
|
46
|
+
config.links = links;
|
|
47
|
+
}
|
|
48
|
+
const imports = {};
|
|
49
|
+
for (const [packageName, entry] of Object.entries(resolution.supply)) {
|
|
50
|
+
if (entry.groups.some((group2) => group2.provider === pkg.name)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
const range = pkg.dependencies[packageName] ?? pkg.peerDependencies[packageName];
|
|
54
|
+
const group = selectSupplyGroup(entry, range);
|
|
55
|
+
if (!group) {
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
imports[packageName] = `${group.provider}/${packageName}`;
|
|
59
|
+
}
|
|
60
|
+
if (Object.keys(imports).length > 0) {
|
|
61
|
+
config.imports = imports;
|
|
62
|
+
}
|
|
63
|
+
const exports = [];
|
|
64
|
+
for (const provided of declaration.provides ?? []) {
|
|
65
|
+
exports.push(`pkg:${provided}`);
|
|
66
|
+
}
|
|
67
|
+
const exportObject = {};
|
|
68
|
+
for (const [subpath, value] of Object.entries(declaration.exports ?? {})) {
|
|
69
|
+
exportObject[subpath.slice(2)] = lowerExportValue(value);
|
|
70
|
+
}
|
|
71
|
+
if (Object.keys(exportObject).length > 0) {
|
|
72
|
+
exports.push(exportObject);
|
|
73
|
+
}
|
|
74
|
+
if (exports.length > 0) {
|
|
75
|
+
config.exports = exports;
|
|
76
|
+
}
|
|
77
|
+
return config;
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|