@morlay/dsh-client-modules 0.2.11-alpha.3 → 0.2.12
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/dist/client.cjs +2483 -1481
- package/dist/index.cjs +359 -115
- package/dist/index.mjs +361 -117
- package/package.json +8 -8
- package/src/__tests__/bundle.spec.ts +305 -60
- package/src/__tests__/client-meta.spec.ts +114 -0
- package/src/__tests__/resolve.spec.ts +20 -2
- package/src/bundle.ts +303 -141
- package/src/client-meta.ts +128 -0
- package/src/index.ts +8 -1
- package/src/resolve.ts +2 -1
- package/src/serve-vite.ts +6 -5
|
@@ -9,10 +9,110 @@ import { tmpdir } from "node:os";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { pathToFileURL } from "node:url";
|
|
11
11
|
import { afterAll, beforeAll, expect, test } from "vitest";
|
|
12
|
-
import { BUNDLER_REV, bundlePackage, clientModulesAlias, urlOf } from "../bundle.ts";
|
|
12
|
+
import { BUNDLER_REV, bundlePackage, clientModulesAlias, moduleKindOf, urlOf } from "../bundle.ts";
|
|
13
13
|
|
|
14
14
|
const repoRoot = process.cwd();
|
|
15
15
|
|
|
16
|
+
test("CSS 出口:产出 CSS 文本(url() 资产改写 + 落 assets),不生成 JS", async () => {
|
|
17
|
+
const pkg = join(work, "node_modules", "style-pkg");
|
|
18
|
+
await mkdir(join(pkg, "dist"), { recursive: true });
|
|
19
|
+
await writeFile(
|
|
20
|
+
join(pkg, "package.json"),
|
|
21
|
+
`${JSON.stringify({
|
|
22
|
+
name: "style-pkg",
|
|
23
|
+
version: "1.0.0",
|
|
24
|
+
exports: { "./theme.css": "./dist/theme.css" },
|
|
25
|
+
})}\n`,
|
|
26
|
+
"utf8",
|
|
27
|
+
);
|
|
28
|
+
await writeFile(join(pkg, "dist", "theme.css"), ".icon { background: url(./dot.svg) }\n", "utf8");
|
|
29
|
+
await writeFile(join(pkg, "dist", "dot.svg"), "<svg/>\n", "utf8");
|
|
30
|
+
|
|
31
|
+
const bundled = await bundlePackage({
|
|
32
|
+
cacheRoot,
|
|
33
|
+
repoRoot: work,
|
|
34
|
+
alias: [],
|
|
35
|
+
name: "style-pkg",
|
|
36
|
+
version: "1.0.0",
|
|
37
|
+
input: { "theme.css": join(pkg, "dist", "theme.css") },
|
|
38
|
+
watch: [pkg],
|
|
39
|
+
});
|
|
40
|
+
const files = [...(bundled?.files.keys() ?? [])];
|
|
41
|
+
expect(files).toContain("theme.css");
|
|
42
|
+
const asset = files.find((file) => file.startsWith("assets/dot-"));
|
|
43
|
+
expect(asset).toBeDefined();
|
|
44
|
+
const css = await readFile(join(cacheRoot, bundled?.files.get("theme.css") ?? ""), "utf8");
|
|
45
|
+
expect(css).toContain(`/@client-modules/style-pkg@1.0.0/${asset ?? ""}`);
|
|
46
|
+
await expect(stat(join(cacheRoot, "style-pkg@1.0.0", "theme.css.js"))).rejects.toThrow();
|
|
47
|
+
await expect(
|
|
48
|
+
readFile(join(cacheRoot, bundled?.files.get(asset ?? "") ?? ""), "utf8"),
|
|
49
|
+
).resolves.toContain("<svg");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("模块类型:扩展名 + 最近的 package.json(什么时候 ESM、什么时候 CJS)", async () => {
|
|
53
|
+
const root = join(work, "kinds");
|
|
54
|
+
await mkdir(join(root, "esm"), { recursive: true });
|
|
55
|
+
await writeFile(
|
|
56
|
+
join(root, "package.json"),
|
|
57
|
+
`${JSON.stringify({ name: "kinds", version: "1.0.0", type: "commonjs" })}\n`,
|
|
58
|
+
"utf8",
|
|
59
|
+
);
|
|
60
|
+
const touch = async (file: string): Promise<string> => {
|
|
61
|
+
const path = join(root, file);
|
|
62
|
+
await writeFile(path, "", "utf8");
|
|
63
|
+
return path;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// 扩展名就能定。
|
|
67
|
+
expect(await moduleKindOf(await touch("a.mjs"))).toBe("esm");
|
|
68
|
+
expect(await moduleKindOf(await touch("a.mts"))).toBe("esm");
|
|
69
|
+
expect(await moduleKindOf(await touch("a.cjs"))).toBe("cjs");
|
|
70
|
+
expect(await moduleKindOf(await touch("a.cts"))).toBe("cjs");
|
|
71
|
+
expect(await moduleKindOf(await touch("a.ts"))).toBe("source");
|
|
72
|
+
expect(await moduleKindOf(await touch("a.tsx"))).toBe("source");
|
|
73
|
+
expect(await moduleKindOf(await touch("a.css"))).toBe("style");
|
|
74
|
+
expect(await moduleKindOf(await touch("a.png"))).toBe("asset");
|
|
75
|
+
|
|
76
|
+
// `.js`:`type` 与**语法检测**(Node 的 `--experimental-detect-module`)。
|
|
77
|
+
expect(await moduleKindOf(await touch("a.js"))).toBe("cjs");
|
|
78
|
+
await writeFile(
|
|
79
|
+
join(root, "esm", "package.json"),
|
|
80
|
+
`${JSON.stringify({ name: "kinds-esm", version: "1.0.0", type: "module" })}\n`,
|
|
81
|
+
"utf8",
|
|
82
|
+
);
|
|
83
|
+
expect(await moduleKindOf(await touch("esm/a.js"))).toBe("esm");
|
|
84
|
+
|
|
85
|
+
const legacy = join(root, "legacy");
|
|
86
|
+
await mkdir(join(legacy, "esm"), { recursive: true });
|
|
87
|
+
await mkdir(join(legacy, "cjs"), { recursive: true });
|
|
88
|
+
await writeFile(
|
|
89
|
+
join(legacy, "package.json"),
|
|
90
|
+
`${JSON.stringify({
|
|
91
|
+
name: "kinds-legacy",
|
|
92
|
+
version: "1.0.0",
|
|
93
|
+
main: "cjs/index.js",
|
|
94
|
+
module: "esm/index.js",
|
|
95
|
+
})}\n`,
|
|
96
|
+
"utf8",
|
|
97
|
+
);
|
|
98
|
+
// 没有 `type`:看源码里有没有 ESM 语法。
|
|
99
|
+
await writeFile(join(legacy, "esm", "index.js"), "export const x = 1;\n", "utf8");
|
|
100
|
+
await writeFile(join(legacy, "cjs", "index.js"), "module.exports = 1;\n", "utf8");
|
|
101
|
+
expect(await moduleKindOf(join(legacy, "esm", "index.js"))).toBe("esm");
|
|
102
|
+
expect(await moduleKindOf(join(legacy, "cjs", "index.js"))).toBe("cjs");
|
|
103
|
+
|
|
104
|
+
// `type: "commonjs"` 时不做语法检测(Node 的规则:显式声明优先)。
|
|
105
|
+
const forced = join(root, "forced-cjs");
|
|
106
|
+
await mkdir(forced, { recursive: true });
|
|
107
|
+
await writeFile(
|
|
108
|
+
join(forced, "package.json"),
|
|
109
|
+
`${JSON.stringify({ name: "forced-cjs", version: "1.0.0", type: "commonjs" })}\n`,
|
|
110
|
+
"utf8",
|
|
111
|
+
);
|
|
112
|
+
await writeFile(join(forced, "index.js"), "export const x = 1;\n", "utf8");
|
|
113
|
+
expect(await moduleKindOf(join(forced, "index.js"))).toBe("cjs");
|
|
114
|
+
});
|
|
115
|
+
|
|
16
116
|
test("包级引用的出口按「是不是行」分:行 → client.js,普通 npm 包 → 根出口 index.js", () => {
|
|
17
117
|
const tables = {
|
|
18
118
|
versions: new Map([
|
|
@@ -222,104 +322,249 @@ test("包住在没有 tsconfig 祖先的地方时,`resolve.tsconfigFilename`
|
|
|
222
322
|
}
|
|
223
323
|
});
|
|
224
324
|
|
|
225
|
-
test("
|
|
226
|
-
//
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
325
|
+
test("CJS/UMD 入口(webpack 包装、局部名与导出名不同):具名导出与 default 同时产出", async () => {
|
|
326
|
+
// 三方包按 **node 的解析**取入口(`main`),老包(`rrule` / `@xterm/xterm` / `@xterm/addon-fit`)进来的
|
|
327
|
+
// 就是 CJS/UMD——导出面是**运行期**的,rolldown 只能给出 `export default module.exports`。消费方还要
|
|
328
|
+
// 具名导入(`import { Terminal } from '@xterm/xterm'`),所以入口求值后按键补成 `export const <键>`。
|
|
329
|
+
//
|
|
330
|
+
// 这里用 **webpack 风格 UMD**(`module.exports = factory()`)且局部名与导出名不同:产物里不能出现
|
|
331
|
+
// `export default { FitAddon }` 这种引用裸导出名的写法——局部作用域里没有它,浏览器会报
|
|
332
|
+
// `ReferenceError: FitAddon is not defined`(`@xterm/addon-fit@0.11.0` 真实踩到的就是这条)。
|
|
333
|
+
const cjs = join(work, "cjs-entry");
|
|
334
|
+
await mkdir(join(cjs, "dist"), { recursive: true });
|
|
232
335
|
await writeFile(
|
|
233
|
-
join(
|
|
336
|
+
join(cjs, "package.json"),
|
|
337
|
+
`${JSON.stringify({ name: "cjs-entry", version: "1.0.0", main: "dist/index.js" })}\n`,
|
|
338
|
+
"utf8",
|
|
339
|
+
);
|
|
340
|
+
await writeFile(
|
|
341
|
+
join(cjs, "dist", "index.js"),
|
|
342
|
+
[
|
|
343
|
+
"!function (root, factory) {",
|
|
344
|
+
' if (typeof exports === "object" && typeof module === "object") module.exports = factory();',
|
|
345
|
+
' else if (typeof define === "function" && define.amd) define([], factory);',
|
|
346
|
+
" else root.FitAddon = factory();",
|
|
347
|
+
"}(globalThis, function () {",
|
|
348
|
+
' "use strict";',
|
|
349
|
+
" var t = {};",
|
|
350
|
+
" t.FitAddon = class FitAddon {};",
|
|
351
|
+
// zod@4 的 CJS 面就有这种键:`export const function = …` 是语法错误,必须被剔掉。
|
|
352
|
+
" t.function = 1;",
|
|
353
|
+
" return t;",
|
|
354
|
+
"});",
|
|
355
|
+
"",
|
|
356
|
+
].join("\n"),
|
|
357
|
+
"utf8",
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const bundled = await bundlePackage({
|
|
361
|
+
cacheRoot,
|
|
362
|
+
repoRoot: work,
|
|
363
|
+
alias: [],
|
|
364
|
+
name: "cjs-entry",
|
|
365
|
+
version: "1.0.0",
|
|
366
|
+
input: { index: join(cjs, "dist", "index.js") },
|
|
367
|
+
watch: [join(cjs, "dist")],
|
|
368
|
+
});
|
|
369
|
+
const file = join(cacheRoot, bundled?.files.get("index") ?? "");
|
|
370
|
+
const loaded = (await import(pathToFileURL(file).href)) as {
|
|
371
|
+
readonly FitAddon?: unknown;
|
|
372
|
+
readonly default?: { readonly FitAddon?: unknown };
|
|
373
|
+
};
|
|
374
|
+
// 两种写法都成立:具名导入(浏览器里 `import { FitAddon }`)与 default(Node/CJS 的 `module.exports`)。
|
|
375
|
+
expect(typeof loaded.FitAddon).toBe("function");
|
|
376
|
+
expect(typeof loaded.default?.FitAddon).toBe("function");
|
|
377
|
+
|
|
378
|
+
// shim 只活在内存里(虚拟入口),缓存目录不落这个文件。
|
|
379
|
+
await expect(stat(join(cacheRoot, "cjs-entry@1.0.0", "index.cjs-shim.mjs"))).rejects.toThrow();
|
|
380
|
+
// 保留字不能出现在 `export const` 里(语法错误)。
|
|
381
|
+
expect(await readFile(file, "utf8")).not.toMatch(/export const function\b/u);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
test("有 ESM 入口的包:不生成 CJS shim(`zod@4` 那种 import/require 双支)", async () => {
|
|
385
|
+
// 入口由清单的 `import` 条件选出(ESM),就交给 rolldown 直出;只有 node 求值确认是 CJS 的入口才包 shim。
|
|
386
|
+
const esm = join(work, "dual-esm");
|
|
387
|
+
await mkdir(join(esm, "dist"), { recursive: true });
|
|
388
|
+
await writeFile(
|
|
389
|
+
join(esm, "package.json"),
|
|
234
390
|
`${JSON.stringify({
|
|
235
|
-
name: "dual",
|
|
391
|
+
name: "dual-esm",
|
|
236
392
|
version: "1.0.0",
|
|
237
|
-
|
|
238
|
-
|
|
393
|
+
type: "module",
|
|
394
|
+
exports: { ".": { import: "./dist/index.js", require: "./dist/index.cjs" } },
|
|
239
395
|
})}\n`,
|
|
240
396
|
"utf8",
|
|
241
397
|
);
|
|
398
|
+
await writeFile(join(esm, "dist", "index.js"), "export const z = 1;\n", "utf8");
|
|
399
|
+
await writeFile(join(esm, "dist", "index.cjs"), "module.exports = { z: 1 };\n", "utf8");
|
|
400
|
+
|
|
401
|
+
const bundled = await bundlePackage({
|
|
402
|
+
cacheRoot,
|
|
403
|
+
repoRoot: work,
|
|
404
|
+
alias: [],
|
|
405
|
+
name: "dual-esm",
|
|
406
|
+
version: "1.0.0",
|
|
407
|
+
input: { index: join(esm, "dist", "index.js") },
|
|
408
|
+
watch: [join(esm, "dist")],
|
|
409
|
+
});
|
|
410
|
+
expect([...(bundled?.files.keys() ?? [])]).toEqual(["index"]);
|
|
411
|
+
await expect(stat(join(cacheRoot, "dual-esm@1.0.0", "index.cjs-shim.mjs"))).rejects.toThrow();
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
test("ESM 入口:交给 rolldown 的具名导出,不补任何 default", async () => {
|
|
415
|
+
// node 的 `require(esm)` 会返回命名空间(`[object Module]`)。这种入口不该被当成 CJS 包补 default:
|
|
416
|
+
// rolldown 自己就能产出具名导出,硬补一个 `default = 命名空间` 只会多一个消费方不该依赖的面。
|
|
417
|
+
const esm = join(work, "esm-entry");
|
|
418
|
+
await mkdir(join(esm, "src"), { recursive: true });
|
|
242
419
|
await writeFile(
|
|
243
|
-
join(
|
|
244
|
-
|
|
420
|
+
join(esm, "package.json"),
|
|
421
|
+
`${JSON.stringify({ name: "esm-entry", version: "1.0.0", type: "module", main: "src/index.js" })}\n`,
|
|
245
422
|
"utf8",
|
|
246
423
|
);
|
|
247
424
|
await writeFile(
|
|
248
|
-
join(
|
|
249
|
-
"
|
|
425
|
+
join(esm, "src", "index.js"),
|
|
426
|
+
"const t = class {};\nexport { t as Terminal };\n",
|
|
250
427
|
"utf8",
|
|
251
428
|
);
|
|
252
429
|
|
|
253
430
|
const bundled = await bundlePackage({
|
|
254
431
|
cacheRoot,
|
|
255
432
|
repoRoot: work,
|
|
256
|
-
alias:
|
|
257
|
-
|
|
258
|
-
nodeStub: join(repoRoot, "packages/client/dsh-client-modules/app-boot/node-stub.cjs"),
|
|
259
|
-
}),
|
|
260
|
-
name: "dual",
|
|
433
|
+
alias: [],
|
|
434
|
+
name: "esm-entry",
|
|
261
435
|
version: "1.0.0",
|
|
262
|
-
input: { index: join(
|
|
263
|
-
watch: [join(
|
|
436
|
+
input: { index: join(esm, "src", "index.js") },
|
|
437
|
+
watch: [join(esm, "src")],
|
|
264
438
|
});
|
|
265
439
|
const file = join(cacheRoot, bundled?.files.get("index") ?? "");
|
|
266
440
|
const code = await readFile(file, "utf8");
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const loaded = (await import(pathToFileURL(file).href)) as {
|
|
271
|
-
readonly default: { readonly alpha?: number };
|
|
272
|
-
readonly beta?: number;
|
|
273
|
-
};
|
|
274
|
-
// default 与 Node 下 CJS 的 `module.exports` 同形:键都在上面。
|
|
275
|
-
expect(loaded.default.alpha).toBe(1);
|
|
276
|
-
expect(loaded.beta).toBe(2);
|
|
441
|
+
expect(code).not.toMatch(/export default/u);
|
|
442
|
+
const loaded = (await import(pathToFileURL(file).href)) as { readonly Terminal?: unknown };
|
|
443
|
+
expect(typeof loaded.Terminal).toBe("function");
|
|
277
444
|
});
|
|
278
445
|
|
|
279
|
-
test("
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const dualDefault = join(work, "dual-default");
|
|
284
|
-
await mkdir(join(dualDefault, "dist", "cjs"), { recursive: true });
|
|
285
|
-
await mkdir(join(dualDefault, "dist", "esm"), { recursive: true });
|
|
446
|
+
test("exports 落点 + 扩展名/type:`.cjs` 才 shim,`.mjs` 与 `module` 字段指向的 `.js` 都不 shim", async () => {
|
|
447
|
+
// 条件决定"哪个文件",扩展名/type 决定"那个文件是什么模块"。两边都处理到,才是完整判定。
|
|
448
|
+
const cond = join(work, "cond-entry");
|
|
449
|
+
await mkdir(join(cond, "dist"), { recursive: true });
|
|
286
450
|
await writeFile(
|
|
287
|
-
join(
|
|
451
|
+
join(cond, "package.json"),
|
|
288
452
|
`${JSON.stringify({
|
|
289
|
-
name: "
|
|
453
|
+
name: "cond-entry",
|
|
290
454
|
version: "1.0.0",
|
|
291
|
-
|
|
292
|
-
module: "dist/esm/index.js",
|
|
455
|
+
exports: { ".": { import: "./dist/index.mjs", require: "./dist/index.cjs" } },
|
|
293
456
|
})}\n`,
|
|
294
457
|
"utf8",
|
|
295
458
|
);
|
|
459
|
+
await writeFile(join(cond, "dist", "index.mjs"), "export const esm = 1;\n", "utf8");
|
|
460
|
+
await writeFile(join(cond, "dist", "index.cjs"), "module.exports = { cjs: 1 };\n", "utf8");
|
|
461
|
+
|
|
462
|
+
const asCjs = await bundlePackage({
|
|
463
|
+
cacheRoot,
|
|
464
|
+
repoRoot: work,
|
|
465
|
+
alias: [],
|
|
466
|
+
name: "cond-entry",
|
|
467
|
+
version: "1.0.0",
|
|
468
|
+
input: { index: join(cond, "dist", "index.cjs") },
|
|
469
|
+
watch: [join(cond, "dist")],
|
|
470
|
+
});
|
|
471
|
+
expect(asCjs?.files.has("index")).toBe(true);
|
|
472
|
+
const cjsLoaded = (await import(
|
|
473
|
+
pathToFileURL(join(cacheRoot, asCjs?.files.get("index") ?? "")).href
|
|
474
|
+
)) as { readonly cjs?: number; readonly default?: { readonly cjs?: number } };
|
|
475
|
+
expect(cjsLoaded.cjs).toBe(1);
|
|
476
|
+
expect(cjsLoaded.default?.cjs).toBe(1);
|
|
477
|
+
|
|
478
|
+
// 换一个包名:缓存目录是**包级**的,同一个包的另一次构建不会删掉上一次的 shim。
|
|
479
|
+
const esmPkg = join(work, "cond-esm");
|
|
480
|
+
await mkdir(join(esmPkg, "dist"), { recursive: true });
|
|
296
481
|
await writeFile(
|
|
297
|
-
join(
|
|
298
|
-
|
|
482
|
+
join(esmPkg, "package.json"),
|
|
483
|
+
`${JSON.stringify({
|
|
484
|
+
name: "cond-esm",
|
|
485
|
+
version: "1.0.0",
|
|
486
|
+
exports: { ".": { import: "./dist/index.mjs", require: "./dist/index.cjs" } },
|
|
487
|
+
})}\n`,
|
|
299
488
|
"utf8",
|
|
300
489
|
);
|
|
490
|
+
await writeFile(join(esmPkg, "dist", "index.mjs"), "export const esm = 1;\n", "utf8");
|
|
491
|
+
await writeFile(join(esmPkg, "dist", "index.cjs"), "module.exports = { cjs: 1 };\n", "utf8");
|
|
492
|
+
const asEsm = await bundlePackage({
|
|
493
|
+
cacheRoot,
|
|
494
|
+
repoRoot: work,
|
|
495
|
+
alias: [],
|
|
496
|
+
name: "cond-esm",
|
|
497
|
+
version: "1.0.0",
|
|
498
|
+
input: { index: join(esmPkg, "dist", "index.mjs") },
|
|
499
|
+
watch: [join(esmPkg, "dist")],
|
|
500
|
+
});
|
|
501
|
+
expect(asEsm?.files.has("index")).toBe(true);
|
|
502
|
+
// ESM 入口交给 rolldown 直出:没有 CJS shim 补出来的 default。
|
|
503
|
+
expect(await readFile(join(cacheRoot, asEsm?.files.get("index") ?? ""), "utf8")).not.toMatch(
|
|
504
|
+
/export default/u,
|
|
505
|
+
);
|
|
506
|
+
|
|
507
|
+
// 老包的 ESM 产物是 `.js`:`module` 字段指向的那份按 ESM 处理(`rrule` 的 dist/esm/index.js)。
|
|
508
|
+
const legacy = join(work, "legacy-module");
|
|
509
|
+
await mkdir(join(legacy, "esm"), { recursive: true });
|
|
510
|
+
await mkdir(join(legacy, "cjs"), { recursive: true });
|
|
301
511
|
await writeFile(
|
|
302
|
-
join(
|
|
303
|
-
|
|
512
|
+
join(legacy, "package.json"),
|
|
513
|
+
`${JSON.stringify({
|
|
514
|
+
name: "legacy-module",
|
|
515
|
+
version: "1.0.0",
|
|
516
|
+
main: "cjs/index.js",
|
|
517
|
+
module: "esm/index.js",
|
|
518
|
+
})}\n`,
|
|
304
519
|
"utf8",
|
|
305
520
|
);
|
|
306
|
-
|
|
307
|
-
|
|
521
|
+
await writeFile(join(legacy, "esm", "index.js"), "export const esm = 1;\n", "utf8");
|
|
522
|
+
await writeFile(join(legacy, "cjs", "index.js"), "module.exports = { cjs: 1 };\n", "utf8");
|
|
523
|
+
const legacyEsm = await bundlePackage({
|
|
308
524
|
cacheRoot,
|
|
309
525
|
repoRoot: work,
|
|
310
526
|
alias: [],
|
|
311
|
-
name: "
|
|
527
|
+
name: "legacy-module",
|
|
312
528
|
version: "1.0.0",
|
|
313
|
-
input: { index: join(
|
|
314
|
-
watch: [join(
|
|
529
|
+
input: { index: join(legacy, "esm", "index.js") },
|
|
530
|
+
watch: [join(legacy, "esm")],
|
|
315
531
|
});
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
532
|
+
expect(legacyEsm?.files.has("index")).toBe(true);
|
|
533
|
+
expect(await readFile(join(cacheRoot, legacyEsm?.files.get("index") ?? ""), "utf8")).not.toMatch(
|
|
534
|
+
/export default/u,
|
|
535
|
+
);
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
test("扩展名即模块类型:`.mjs` / `.ts` 入口不生成 CJS shim", async () => {
|
|
539
|
+
// `.mjs` 是 ESM,`.ts` 是源码面:都不该进 "CJS 具名导出" 那条求值路径。
|
|
540
|
+
for (const [name, file, code, expected] of [
|
|
541
|
+
["ext-mjs", "index.mjs", "const t = class {};\nexport { t as Terminal };\n", "Terminal"],
|
|
542
|
+
["ext-ts", "index.ts", "export const x = 1;\n", "x"],
|
|
543
|
+
] as const) {
|
|
544
|
+
const dir = join(work, name);
|
|
545
|
+
await mkdir(dir, { recursive: true });
|
|
546
|
+
await writeFile(
|
|
547
|
+
join(dir, "package.json"),
|
|
548
|
+
`${JSON.stringify({ name, version: "1.0.0", type: "module" })}\n`,
|
|
549
|
+
"utf8",
|
|
550
|
+
);
|
|
551
|
+
await writeFile(join(dir, file), code, "utf8");
|
|
552
|
+
const built = await bundlePackage({
|
|
553
|
+
cacheRoot,
|
|
554
|
+
repoRoot: work,
|
|
555
|
+
alias: [],
|
|
556
|
+
name,
|
|
557
|
+
version: "1.0.0",
|
|
558
|
+
input: { index: join(dir, file) },
|
|
559
|
+
watch: [dir],
|
|
560
|
+
});
|
|
561
|
+
expect([...(built?.files.keys() ?? [])]).toEqual(["index"]);
|
|
562
|
+
await expect(stat(join(cacheRoot, `${name}@1.0.0`, "index.cjs-shim.mjs"))).rejects.toThrow();
|
|
563
|
+
const loaded = (await import(
|
|
564
|
+
pathToFileURL(join(cacheRoot, built?.files.get("index") ?? "")).href
|
|
565
|
+
)) as Record<string, unknown>;
|
|
566
|
+
expect(loaded[expected]).toBeDefined();
|
|
567
|
+
}
|
|
323
568
|
});
|
|
324
569
|
|
|
325
570
|
test("纯 ESM 的包不补 default(不掩盖真实的导出缺失)", async () => {
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 版本元数据(`DSH_CLIENT_*`):设置页的版本行、侧栏的 `<commit>-dirty` 读它们。取值顺序是
|
|
3
|
+
* 环境(宿主注入)→ `prepare` 写的记录 → 现算,这里三条各钉一条。
|
|
4
|
+
*/
|
|
5
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
9
|
+
import { CLIENT_BUILD_RECORD, clientBuildMeta, clientMetaDefines } from "../client-meta.ts";
|
|
10
|
+
|
|
11
|
+
// 除了那四个注入值,`DEEPSEEK_HARNESS_VERSION` 也要隔离:现算分支优先读它(mise 会往进程里灌),
|
|
12
|
+
// 不隔离的话"没有环境值时现算"这条测的就不是 fixture 了。
|
|
13
|
+
const KEYS = [
|
|
14
|
+
"DSH_CLIENT_VERSION",
|
|
15
|
+
"DSH_CLIENT_COMMIT_HASH",
|
|
16
|
+
"DSH_CLIENT_GIT_DIRTY",
|
|
17
|
+
"DSH_CLIENT_BUILD_PROFILE",
|
|
18
|
+
"DEEPSEEK_HARNESS_VERSION",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
/** fixture 自己的版本:断言它,而不是某个具体版本号。 */
|
|
22
|
+
const FIXTURE_VERSION = "1.2.3-rc.1";
|
|
23
|
+
const saved = new Map<string, string | undefined>();
|
|
24
|
+
let work: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(async () => {
|
|
27
|
+
for (const key of KEYS) {
|
|
28
|
+
saved.set(key, process.env[key]);
|
|
29
|
+
delete process.env[key];
|
|
30
|
+
}
|
|
31
|
+
work = await mkdtemp(join(tmpdir(), "client-meta-"));
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
afterEach(async () => {
|
|
35
|
+
for (const key of KEYS) {
|
|
36
|
+
const value = saved.get(key);
|
|
37
|
+
if (value === undefined) delete process.env[key];
|
|
38
|
+
else process.env[key] = value;
|
|
39
|
+
}
|
|
40
|
+
await rm(work, { recursive: true, force: true });
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
describe("clientMetaDefines", () => {
|
|
44
|
+
it("给真值:每个键换成正文字面量(值原样透传,不在这里定)", () => {
|
|
45
|
+
const meta = { version: "1.2.3-rc.1", commit: "abc1234", dirty: true, profile: "official" };
|
|
46
|
+
expect(clientMetaDefines(meta)).toMatchObject({
|
|
47
|
+
"process.env.DSH_CLIENT_VERSION": JSON.stringify(meta.version),
|
|
48
|
+
"process.env.DSH_CLIENT_COMMIT_HASH": JSON.stringify(meta.commit),
|
|
49
|
+
"process.env.DSH_CLIENT_GIT_DIRTY": JSON.stringify("true"),
|
|
50
|
+
"process.env.DSH_CLIENT_BUILD_PROFILE": JSON.stringify(meta.profile),
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("缺项换成 undefined(前端那几处自己判空),不是字符串 \"undefined\"", () => {
|
|
55
|
+
const defines = clientMetaDefines({});
|
|
56
|
+
expect(defines["process.env.DSH_CLIENT_VERSION"]).toBe("undefined");
|
|
57
|
+
expect(defines["process.env.DSH_CLIENT_COMMIT_HASH"]).toBe("undefined");
|
|
58
|
+
expect(defines["process.env.DSH_CLIENT_GIT_DIRTY"]).toBe("undefined");
|
|
59
|
+
expect(defines["process.env.DSH_CLIENT_BUILD_PROFILE"]).toBe("undefined");
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("clientBuildMeta", () => {
|
|
64
|
+
it("环境里的值优先(dshdev / 桌面壳注入)", async () => {
|
|
65
|
+
process.env["DSH_CLIENT_VERSION"] = "1.2.3-rc.1";
|
|
66
|
+
process.env["DSH_CLIENT_COMMIT_HASH"] = "abc1234";
|
|
67
|
+
process.env["DSH_CLIENT_BUILD_PROFILE"] = "local";
|
|
68
|
+
expect(await clientBuildMeta(work)).toEqual({
|
|
69
|
+
version: "1.2.3-rc.1",
|
|
70
|
+
commit: "abc1234",
|
|
71
|
+
profile: "local",
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("其次是 prepare 写下的记录", async () => {
|
|
76
|
+
await mkdir(join(work, ".dsh-build"), { recursive: true });
|
|
77
|
+
await writeFile(
|
|
78
|
+
join(work, CLIENT_BUILD_RECORD),
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
formatVersion: 1,
|
|
81
|
+
environment: {
|
|
82
|
+
DSH_CLIENT_VERSION: "1.2.3-rc.1",
|
|
83
|
+
DSH_CLIENT_COMMIT_HASH: "abc1234",
|
|
84
|
+
DSH_CLIENT_GIT_DIRTY: "true",
|
|
85
|
+
},
|
|
86
|
+
}),
|
|
87
|
+
"utf8",
|
|
88
|
+
);
|
|
89
|
+
expect(await clientBuildMeta(work)).toEqual({
|
|
90
|
+
version: "1.2.3-rc.1",
|
|
91
|
+
commit: "abc1234",
|
|
92
|
+
dirty: true,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("都没有时现算:只有 package.json 的版本(这个临时目录不是 git 工作树)", async () => {
|
|
97
|
+
await writeFile(
|
|
98
|
+
join(work, "package.json"),
|
|
99
|
+
JSON.stringify({ version: FIXTURE_VERSION }),
|
|
100
|
+
"utf8",
|
|
101
|
+
);
|
|
102
|
+
expect(await clientBuildMeta(work)).toEqual({ version: FIXTURE_VERSION });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("现算也认 `DEEPSEEK_HARNESS_VERSION`(装的是哪一版 harness)", async () => {
|
|
106
|
+
await writeFile(
|
|
107
|
+
join(work, "package.json"),
|
|
108
|
+
JSON.stringify({ version: FIXTURE_VERSION }),
|
|
109
|
+
"utf8",
|
|
110
|
+
);
|
|
111
|
+
process.env["DEEPSEEK_HARNESS_VERSION"] = "9.9.9-other.1";
|
|
112
|
+
expect(await clientBuildMeta(work)).toEqual({ version: "9.9.9-other.1" });
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -64,12 +64,30 @@ describe("exportTargetOf", () => {
|
|
|
64
64
|
);
|
|
65
65
|
});
|
|
66
66
|
|
|
67
|
-
test("
|
|
67
|
+
test("对象条件的 `import` 优先于 `require`(`zod@4` 的形状:index.js / index.cjs)", async () => {
|
|
68
|
+
const root = join(work, "object-conditions");
|
|
69
|
+
await makePackage(
|
|
70
|
+
root,
|
|
71
|
+
{
|
|
72
|
+
name: "object-conditions",
|
|
73
|
+
version: "1.0.0",
|
|
74
|
+
type: "module",
|
|
75
|
+
exports: {
|
|
76
|
+
".": { types: "./index.d.cts", import: "./index.js", require: "./index.cjs" },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
{ "index.js": "export const z = 1;\n", "index.cjs": "module.exports = { z: 1 };\n" },
|
|
80
|
+
);
|
|
81
|
+
// 有 ESM 就走 ESM:不要为了跑 CJS 转换而去挑 `require` 那支。
|
|
82
|
+
await expect(exportTargetOf(root, undefined)).resolves.toBe(join(root, "index.js"));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test("没有 `exports` 时按打包器约定取 `module`(ESM)→ `main`", async () => {
|
|
68
86
|
const root = join(work, "legacy");
|
|
69
87
|
await makePackage(
|
|
70
88
|
root,
|
|
71
89
|
{ name: "legacy", version: "1.0.0", main: "index.js", module: "esm/index.mjs" },
|
|
72
|
-
{ "esm/index.mjs": "export {};\n" },
|
|
90
|
+
{ "index.js": "module.exports = 1;\n", "esm/index.mjs": "export {};\n" },
|
|
73
91
|
);
|
|
74
92
|
await expect(exportTargetOf(root, undefined)).resolves.toBe(join(root, "esm/index.mjs"));
|
|
75
93
|
});
|