@morlay/dsh-client-modules 0.2.11-alpha.2 → 0.2.11-alpha.3

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 CHANGED
@@ -1461,24 +1461,24 @@ window.__ModuleLoader__.load({
1461
1461
  }
1462
1462
  var schema_form_module_css_default = {
1463
1463
  "lineFold": "oZhWGq_lineFold",
1464
- "hoverActions": "oZhWGq_hoverActions",
1465
- "hint": "oZhWGq_hint",
1464
+ "valueTrigger": "oZhWGq_valueTrigger",
1465
+ "addWrap": "oZhWGq_addWrap",
1466
1466
  "lineRow": "oZhWGq_lineRow",
1467
1467
  "lineNumber": "oZhWGq_lineNumber",
1468
- "valueSlot": "oZhWGq_valueSlot",
1469
- "editorRoot": "oZhWGq_editorRoot",
1470
- "lineToken": "oZhWGq_lineToken",
1471
- "lineValue": "oZhWGq_lineValue",
1472
- "valueTrigger": "oZhWGq_valueTrigger",
1473
1468
  "lineBody": "oZhWGq_lineBody",
1474
- "lineComment": "oZhWGq_lineComment",
1475
1469
  "compactInput": "oZhWGq_compactInput",
1476
- "compactTextField": "oZhWGq_compactTextField",
1477
- "addWrap": "oZhWGq_addWrap",
1478
- "lineFoldSpacer": "oZhWGq_lineFoldSpacer",
1470
+ "lineComment": "oZhWGq_lineComment",
1479
1471
  "lineKey": "oZhWGq_lineKey",
1480
1472
  "lineInvalid": "oZhWGq_lineInvalid",
1481
- "lineActions": "oZhWGq_lineActions"
1473
+ "compactTextField": "oZhWGq_compactTextField",
1474
+ "valueSlot": "oZhWGq_valueSlot",
1475
+ "editorRoot": "oZhWGq_editorRoot",
1476
+ "lineFoldSpacer": "oZhWGq_lineFoldSpacer",
1477
+ "lineToken": "oZhWGq_lineToken",
1478
+ "hoverActions": "oZhWGq_hoverActions",
1479
+ "lineActions": "oZhWGq_lineActions",
1480
+ "hint": "oZhWGq_hint",
1481
+ "lineValue": "oZhWGq_lineValue"
1482
1482
  };
1483
1483
  // ../dsh-client-ui-primitives/src/client/schema-form/TruncatedTooltip.tsx
1484
1484
  function TruncatedTooltip({ label, children }) {
@@ -2425,55 +2425,55 @@ window.__ModuleLoader__.load({
2425
2425
  document.head.appendChild(tag);
2426
2426
  }
2427
2427
  var layout_module_css_default = {
2428
- "narrow": "uxI0LG_narrow",
2429
- "toneSecondary": "uxI0LG_toneSecondary",
2430
- "gap2": "uxI0LG_gap2",
2431
- "toneDanger": "uxI0LG_toneDanger",
2432
- "gap10": "uxI0LG_gap10",
2428
+ "gap16": "uxI0LG_gap16",
2429
+ "justifyBetween": "uxI0LG_justifyBetween",
2430
+ "weightMedium": "uxI0LG_weightMedium",
2431
+ "tabular": "uxI0LG_tabular",
2432
+ "fixed": "uxI0LG_fixed",
2433
+ "sizeBase": "uxI0LG_sizeBase",
2434
+ "stack": "uxI0LG_stack",
2435
+ "gap14": "uxI0LG_gap14",
2433
2436
  "mono": "uxI0LG_mono",
2437
+ "boxed": "uxI0LG_boxed",
2438
+ "strong": "uxI0LG_strong",
2439
+ "sizeMd": "uxI0LG_sizeMd",
2440
+ "gap12": "uxI0LG_gap12",
2441
+ "plain": "uxI0LG_plain",
2442
+ "padBlocking": "uxI0LG_padBlocking",
2434
2443
  "padBody": "uxI0LG_padBody",
2435
- "sizeBase": "uxI0LG_sizeBase",
2436
- "gap16": "uxI0LG_gap16",
2444
+ "grow": "uxI0LG_grow",
2445
+ "scroll": "uxI0LG_scroll",
2446
+ "justifyCenter": "uxI0LG_justifyCenter",
2447
+ "text": "uxI0LG_text",
2448
+ "sizeSm": "uxI0LG_sizeSm",
2449
+ "toneTertiary": "uxI0LG_toneTertiary",
2450
+ "gapWide": "uxI0LG_gapWide",
2451
+ "gap2": "uxI0LG_gap2",
2452
+ "wSearch": "uxI0LG_wSearch",
2453
+ "sizeLg": "uxI0LG_sizeLg",
2454
+ "justifyEnd": "uxI0LG_justifyEnd",
2455
+ "narrow": "uxI0LG_narrow",
2456
+ "alignCenter": "uxI0LG_alignCenter",
2437
2457
  "wrap": "uxI0LG_wrap",
2438
- "justifyBetween": "uxI0LG_justifyBetween",
2439
- "padNone": "uxI0LG_padNone",
2458
+ "tonePrimary": "uxI0LG_tonePrimary",
2459
+ "toneSecondary": "uxI0LG_toneSecondary",
2460
+ "gap8": "uxI0LG_gap8",
2461
+ "gap4": "uxI0LG_gap4",
2462
+ "fill": "uxI0LG_fill",
2463
+ "truncate": "uxI0LG_truncate",
2464
+ "padPage": "uxI0LG_padPage",
2465
+ "alignEnd": "uxI0LG_alignEnd",
2440
2466
  "padRow": "uxI0LG_padRow",
2441
- "gap12": "uxI0LG_gap12",
2442
2467
  "alignStart": "uxI0LG_alignStart",
2443
2468
  "push": "uxI0LG_push",
2444
- "padPage": "uxI0LG_padPage",
2445
- "sizeLg": "uxI0LG_sizeLg",
2446
- "padCard": "uxI0LG_padCard",
2447
- "fill": "uxI0LG_fill",
2448
- "tonePrimary": "uxI0LG_tonePrimary",
2449
- "strong": "uxI0LG_strong",
2450
- "tabular": "uxI0LG_tabular",
2451
- "boxed": "uxI0LG_boxed",
2469
+ "padNone": "uxI0LG_padNone",
2452
2470
  "gap6": "uxI0LG_gap6",
2471
+ "padCard": "uxI0LG_padCard",
2453
2472
  "sizeXs": "uxI0LG_sizeXs",
2454
- "gapWide": "uxI0LG_gapWide",
2455
- "sizeSm": "uxI0LG_sizeSm",
2473
+ "toneDanger": "uxI0LG_toneDanger",
2456
2474
  "row": "uxI0LG_row",
2457
- "gap14": "uxI0LG_gap14",
2458
- "justifyEnd": "uxI0LG_justifyEnd",
2459
- "alignCenter": "uxI0LG_alignCenter",
2460
2475
  "gap0": "uxI0LG_gap0",
2461
- "alignEnd": "uxI0LG_alignEnd",
2462
- "justifyCenter": "uxI0LG_justifyCenter",
2463
- "grow": "uxI0LG_grow",
2464
- "scroll": "uxI0LG_scroll",
2465
- "plain": "uxI0LG_plain",
2466
- "padBlocking": "uxI0LG_padBlocking",
2467
- "text": "uxI0LG_text",
2468
- "weightMedium": "uxI0LG_weightMedium",
2469
- "truncate": "uxI0LG_truncate",
2470
- "gap4": "uxI0LG_gap4",
2471
- "wSearch": "uxI0LG_wSearch",
2472
- "gap8": "uxI0LG_gap8",
2473
- "toneTertiary": "uxI0LG_toneTertiary",
2474
- "fixed": "uxI0LG_fixed",
2475
- "sizeMd": "uxI0LG_sizeMd",
2476
- "stack": "uxI0LG_stack"
2476
+ "gap10": "uxI0LG_gap10"
2477
2477
  };
2478
2478
  // ../dsh-client-ui-primitives/src/client/layout/scales.ts
2479
2479
  function gapClass(css, gap) {
@@ -2545,8 +2545,8 @@ window.__ModuleLoader__.load({
2545
2545
  document.head.appendChild(tag);
2546
2546
  }
2547
2547
  var Button_module_css_default = {
2548
- "label": "SBA8ka_label",
2549
- "danger": "SBA8ka_danger"
2548
+ "danger": "SBA8ka_danger",
2549
+ "label": "SBA8ka_label"
2550
2550
  };
2551
2551
  (0, react.forwardRef)(function Button({ className, tone, ...rest }, ref) {
2552
2552
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_deepseek_ai_dsh_client_ui_primitives.Button, {
package/dist/index.cjs CHANGED
@@ -386,7 +386,7 @@ function clientModulesAlias(options) {
386
386
  * 实例)。内容变了**不换 URL**,靠 `cache-control: no-cache` + `ETag` 协商(vite dev 对它自己的模块
387
387
  * 也是这套)。
388
388
  */
389
- const BUNDLER_REV = (0, node_crypto.createHash)("sha1").update(`client-modules:rules:22`).digest("hex").slice(0, 8);
389
+ const BUNDLER_REV = (0, node_crypto.createHash)("sha1").update(`client-modules:rules:28`).digest("hex").slice(0, 8);
390
390
  /** 包 → 它在浏览器里的地址(联邦式)。 */
391
391
  function depsUrlOf(specifier, version, file = "index.js") {
392
392
  const name = packageNameOf(specifier) || specifier;
@@ -528,6 +528,28 @@ function browserProcessPlugin() {
528
528
  };
529
529
  }
530
530
  /**
531
+ * 入口是否按**它所属包的声明**是 CJS(Node 的 ESM 解析会做 `default = module.exports` 的那一侧)。
532
+ *
533
+ * 判据只看声明,不看源码内容:
534
+ * - `type: "module"` 的包 → 恒为 ESM(`.mjs` 同理);
535
+ * - 否则是"老约定":`module` 字段指向的那份视为 ESM(打包器约定,Node 不认),其余 `.js` 视为 CJS、
536
+ * `.cjs` 恒为 CJS;`.ts` 这类源码交给语法(rolldown 自己判)。
537
+ *
538
+ * 以前这里是"读源码找 `module.exports =`",那是猜:`exports.` 出现在注释里就会误判,而 `type: module` 的
539
+ * 包即使有 CJS 写法也不该被当 CJS。
540
+ */
541
+ async function declaresCjs(entry) {
542
+ if (entry.endsWith(".mjs")) return false;
543
+ if (entry.endsWith(".cjs")) return true;
544
+ if (!entry.endsWith(".js")) return false;
545
+ const manifestPath = await packageJsonNear(entry);
546
+ const manifest = JSON.parse(await (0, node_fs_promises.readFile)(manifestPath, "utf8").catch(() => "{}"));
547
+ if (manifest.type === "module") return false;
548
+ const manifestModule = manifest.module;
549
+ if (manifestModule === void 0) return true;
550
+ return (0, node_path.resolve)((0, node_path.dirname)(manifestPath), manifestModule) !== (0, node_path.resolve)(entry);
551
+ }
552
+ /**
531
553
  * CJS 入口的**具名导出包装**(`react` 这类)。
532
554
  *
533
555
  * `react` 的入口是 `module.exports = require('./cjs/react.development.js')`,rolldown 静态分析不出具名
@@ -538,8 +560,7 @@ function browserProcessPlugin() {
538
560
  * @returns 包装文件路径;入口不是 CJS、或求值拿不到键时 undefined(保持原样)。
539
561
  */
540
562
  async function cjsShimOf(options) {
541
- const source = await (0, node_fs_promises.readFile)(options.entry, "utf8").catch(() => "");
542
- if (!(/\.(?:cjs|js)$/u.test(options.entry) && /(?:^|\n)\s*(?:module\.exports\s*=|exports\.[A-Za-z_$][A-Za-z0-9_$]*\s*=)/u.test(source))) return void 0;
563
+ if (!await declaresCjs(options.entry)) return void 0;
543
564
  let keys;
544
565
  try {
545
566
  const loaded = (0, node_module.createRequire)(options.entry)(options.entry);
@@ -561,6 +582,51 @@ async function cjsShimOf(options) {
561
582
  options.log?.(`[bundle-cjs] ${options.name}/${options.exportName}: 补 ${String(named.length)} 个具名导出\n`);
562
583
  return file;
563
584
  }
585
+ /** 根出口的产物名(`urlOf` 对非 client 行写的就是它)。 */
586
+ const ROOT_EXPORT = "index";
587
+ /** 具名导出的合法标识符:补 default 时按它挑键(非标识符的导出名用不了简写)。 */
588
+ const EXPORT_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
589
+ /**
590
+ * 这个入口是否需要「CJS 包的 ESM 便利入口」的 default 补偿。判据**只看 package.json 的声明**:
591
+ *
592
+ * - `type: "module"` 的包:Node 与我们都按 `exports`(或 `main` 本身就是 ESM)解析,两边一致,不补;
593
+ * - 其余是 CJS 包:`module` 只是打包器约定(视为 ESM),`main` 才是 CJS。这条链选 `module`(更小、没有
594
+ * webpack 垫片),而 Node 的 `import pkg from 'pkg'` 落到 `main`、default 是它的 `module.exports`——
595
+ * 所以浏览器侧得补一个 default,否则消费方那句 default import 直接报
596
+ * `does not provide an export named 'default'`(`rrule` 就是这一例)。
597
+ * - 有 `exports` 的包两边都按条件解析,不涉及这条约定;入口不是 `module` 的(选了 `main`)由
598
+ * {@link cjsShimOf} 处理。`main`/`module` 只描述根出口,子路径不在此列。
599
+ */
600
+ async function needsCjsInterop(entry, exportName) {
601
+ if (exportName !== ROOT_EXPORT) return false;
602
+ const manifestPath = await packageJsonNear(entry);
603
+ const manifest = JSON.parse(await (0, node_fs_promises.readFile)(manifestPath, "utf8").catch(() => "{}"));
604
+ if (manifest.type === "module" || manifest.exports !== void 0) return false;
605
+ const manifestModule = manifest.module;
606
+ if (manifestModule === void 0 || manifest.main === void 0) return false;
607
+ const modulePath = (0, node_path.resolve)((0, node_path.dirname)(manifestPath), manifestModule);
608
+ return await (0, node_fs_promises.realpath)(modulePath).catch(() => modulePath) === await (0, node_fs_promises.realpath)(entry).catch(() => (0, node_path.resolve)(entry)) || modulePath === (0, node_path.resolve)(entry);
609
+ }
610
+ /**
611
+ * 给「CJS 包的 ESM 便利入口」补 default:**看产物的真实导出面**(`chunk.exports`),不猜源码。
612
+ * 已经有 default 的(`jsbi` 的 `export default JSBI`)原样保留——补成对象会把它顶掉,
613
+ * `@js-temporal/polyfill` 那句 `import e from "jsbi"` 于是拿到命名空间、运行期 `e.BigInt is not a function`;
614
+ * 没有的补成 `{ 具名导出… }`,与 CJS 的 `module.exports` 同形(`rrule` 的 `ALL_WEEKDAYS` / `rrulestr` 那批)。
615
+ */
616
+ function cjsInteropPlugin(exportName) {
617
+ return {
618
+ name: "dsh-cjs-interop",
619
+ generateBundle(_options, bundle) {
620
+ for (const chunk of Object.values(bundle)) {
621
+ if (chunk.type !== "chunk" || chunk.isEntry !== true || chunk.name !== exportName) continue;
622
+ if (chunk.exports.includes("default")) continue;
623
+ const named = chunk.exports.filter((name) => EXPORT_IDENTIFIER.test(name));
624
+ if (named.length === 0) continue;
625
+ chunk.code += `\nexport default { ${named.join(", ")} };\n`;
626
+ }
627
+ }
628
+ };
629
+ }
564
630
  /**
565
631
  * CSS 入口的包装:**样式文件不能直接当入口**——vite 的库模式固定 `cssCodeSplit: false`,遇到
566
632
  * `rolldownOptions.input` 里有 `.css` 直接报错。包一层 side-effect import,让样式插件按它一贯的方式
@@ -687,20 +753,29 @@ async function bundlePackageOnce(options, key) {
687
753
  const startedAt = Date.now();
688
754
  options.log?.(`[bundle-build] ${options.name}: rolldown start\n`);
689
755
  const input = {};
690
- for (const [exportName, entry] of Object.entries(options.input)) input[exportName] = await cjsShimOf({
691
- name: options.name,
692
- exportName,
693
- entry,
694
- ...options.log === void 0 ? {} : { log: options.log },
695
- outDir
696
- }) ?? await cssShimOf({
697
- exportName,
698
- entry,
699
- outDir
700
- }) ?? entry;
756
+ let interopExport;
757
+ for (const [exportName, entry] of Object.entries(options.input)) {
758
+ if (await needsCjsInterop(entry, exportName)) interopExport = exportName;
759
+ input[exportName] = await cjsShimOf({
760
+ name: options.name,
761
+ exportName,
762
+ entry,
763
+ ...options.log === void 0 ? {} : { log: options.log },
764
+ outDir
765
+ }) ?? await cssShimOf({
766
+ exportName,
767
+ entry,
768
+ outDir
769
+ }) ?? entry;
770
+ }
771
+ if (interopExport !== void 0) plugins.push(cjsInteropPlugin(interopExport));
701
772
  const bundle = await (0, rolldown.rolldown)({
702
773
  input,
703
774
  plugins,
775
+ onLog: (level, log, handler) => {
776
+ if (log.code === "CONFIGURATION_FIELD_CONFLICT" && log.message?.includes("experimentalDecorators") === true) return;
777
+ handler(level, log);
778
+ },
704
779
  ...options.tsconfig === void 0 ? {} : { resolve: { tsconfigFilename: options.tsconfig } },
705
780
  transform: {
706
781
  target: [
@@ -1036,7 +1111,6 @@ async function startViteDev(ctx, options) {
1036
1111
  }
1037
1112
  },
1038
1113
  optimizeDeps: {
1039
- disabled: true,
1040
1114
  noDiscovery: true,
1041
1115
  include: []
1042
1116
  },
@@ -1098,7 +1172,8 @@ const inject = ["loader", "webServer"];
1098
1172
  */
1099
1173
  async function clientRowsOf(loader) {
1100
1174
  const found = /* @__PURE__ */ new Map();
1101
- for (const entry of loader.entries()) {
1175
+ for (const raw of loader.entries()) {
1176
+ const entry = raw;
1102
1177
  if (entry.disabled === true || entry.fiber === void 0) continue;
1103
1178
  const name = entry.options?.name;
1104
1179
  if (typeof name !== "string" || name === "") continue;
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { createRequire, findPackageJSON } from "node:module";
2
- import { appendFile, mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
2
+ import { appendFile, mkdir, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
3
3
  import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { ASSET_MIME, NODE_BUILTIN, clientAssetPlugin } from "@morlay/dsh-cordis-assets/assets";
@@ -385,7 +385,7 @@ function clientModulesAlias(options) {
385
385
  * 实例)。内容变了**不换 URL**,靠 `cache-control: no-cache` + `ETag` 协商(vite dev 对它自己的模块
386
386
  * 也是这套)。
387
387
  */
388
- const BUNDLER_REV = createHash("sha1").update(`client-modules:rules:22`).digest("hex").slice(0, 8);
388
+ const BUNDLER_REV = createHash("sha1").update(`client-modules:rules:28`).digest("hex").slice(0, 8);
389
389
  /** 包 → 它在浏览器里的地址(联邦式)。 */
390
390
  function depsUrlOf(specifier, version, file = "index.js") {
391
391
  const name = packageNameOf(specifier) || specifier;
@@ -527,6 +527,28 @@ function browserProcessPlugin() {
527
527
  };
528
528
  }
529
529
  /**
530
+ * 入口是否按**它所属包的声明**是 CJS(Node 的 ESM 解析会做 `default = module.exports` 的那一侧)。
531
+ *
532
+ * 判据只看声明,不看源码内容:
533
+ * - `type: "module"` 的包 → 恒为 ESM(`.mjs` 同理);
534
+ * - 否则是"老约定":`module` 字段指向的那份视为 ESM(打包器约定,Node 不认),其余 `.js` 视为 CJS、
535
+ * `.cjs` 恒为 CJS;`.ts` 这类源码交给语法(rolldown 自己判)。
536
+ *
537
+ * 以前这里是"读源码找 `module.exports =`",那是猜:`exports.` 出现在注释里就会误判,而 `type: module` 的
538
+ * 包即使有 CJS 写法也不该被当 CJS。
539
+ */
540
+ async function declaresCjs(entry) {
541
+ if (entry.endsWith(".mjs")) return false;
542
+ if (entry.endsWith(".cjs")) return true;
543
+ if (!entry.endsWith(".js")) return false;
544
+ const manifestPath = await packageJsonNear(entry);
545
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8").catch(() => "{}"));
546
+ if (manifest.type === "module") return false;
547
+ const manifestModule = manifest.module;
548
+ if (manifestModule === void 0) return true;
549
+ return resolve(dirname(manifestPath), manifestModule) !== resolve(entry);
550
+ }
551
+ /**
530
552
  * CJS 入口的**具名导出包装**(`react` 这类)。
531
553
  *
532
554
  * `react` 的入口是 `module.exports = require('./cjs/react.development.js')`,rolldown 静态分析不出具名
@@ -537,8 +559,7 @@ function browserProcessPlugin() {
537
559
  * @returns 包装文件路径;入口不是 CJS、或求值拿不到键时 undefined(保持原样)。
538
560
  */
539
561
  async function cjsShimOf(options) {
540
- const source = await readFile(options.entry, "utf8").catch(() => "");
541
- if (!(/\.(?:cjs|js)$/u.test(options.entry) && /(?:^|\n)\s*(?:module\.exports\s*=|exports\.[A-Za-z_$][A-Za-z0-9_$]*\s*=)/u.test(source))) return void 0;
562
+ if (!await declaresCjs(options.entry)) return void 0;
542
563
  let keys;
543
564
  try {
544
565
  const loaded = createRequire(options.entry)(options.entry);
@@ -560,6 +581,51 @@ async function cjsShimOf(options) {
560
581
  options.log?.(`[bundle-cjs] ${options.name}/${options.exportName}: 补 ${String(named.length)} 个具名导出\n`);
561
582
  return file;
562
583
  }
584
+ /** 根出口的产物名(`urlOf` 对非 client 行写的就是它)。 */
585
+ const ROOT_EXPORT = "index";
586
+ /** 具名导出的合法标识符:补 default 时按它挑键(非标识符的导出名用不了简写)。 */
587
+ const EXPORT_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
588
+ /**
589
+ * 这个入口是否需要「CJS 包的 ESM 便利入口」的 default 补偿。判据**只看 package.json 的声明**:
590
+ *
591
+ * - `type: "module"` 的包:Node 与我们都按 `exports`(或 `main` 本身就是 ESM)解析,两边一致,不补;
592
+ * - 其余是 CJS 包:`module` 只是打包器约定(视为 ESM),`main` 才是 CJS。这条链选 `module`(更小、没有
593
+ * webpack 垫片),而 Node 的 `import pkg from 'pkg'` 落到 `main`、default 是它的 `module.exports`——
594
+ * 所以浏览器侧得补一个 default,否则消费方那句 default import 直接报
595
+ * `does not provide an export named 'default'`(`rrule` 就是这一例)。
596
+ * - 有 `exports` 的包两边都按条件解析,不涉及这条约定;入口不是 `module` 的(选了 `main`)由
597
+ * {@link cjsShimOf} 处理。`main`/`module` 只描述根出口,子路径不在此列。
598
+ */
599
+ async function needsCjsInterop(entry, exportName) {
600
+ if (exportName !== ROOT_EXPORT) return false;
601
+ const manifestPath = await packageJsonNear(entry);
602
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8").catch(() => "{}"));
603
+ if (manifest.type === "module" || manifest.exports !== void 0) return false;
604
+ const manifestModule = manifest.module;
605
+ if (manifestModule === void 0 || manifest.main === void 0) return false;
606
+ const modulePath = resolve(dirname(manifestPath), manifestModule);
607
+ return await realpath(modulePath).catch(() => modulePath) === await realpath(entry).catch(() => resolve(entry)) || modulePath === resolve(entry);
608
+ }
609
+ /**
610
+ * 给「CJS 包的 ESM 便利入口」补 default:**看产物的真实导出面**(`chunk.exports`),不猜源码。
611
+ * 已经有 default 的(`jsbi` 的 `export default JSBI`)原样保留——补成对象会把它顶掉,
612
+ * `@js-temporal/polyfill` 那句 `import e from "jsbi"` 于是拿到命名空间、运行期 `e.BigInt is not a function`;
613
+ * 没有的补成 `{ 具名导出… }`,与 CJS 的 `module.exports` 同形(`rrule` 的 `ALL_WEEKDAYS` / `rrulestr` 那批)。
614
+ */
615
+ function cjsInteropPlugin(exportName) {
616
+ return {
617
+ name: "dsh-cjs-interop",
618
+ generateBundle(_options, bundle) {
619
+ for (const chunk of Object.values(bundle)) {
620
+ if (chunk.type !== "chunk" || chunk.isEntry !== true || chunk.name !== exportName) continue;
621
+ if (chunk.exports.includes("default")) continue;
622
+ const named = chunk.exports.filter((name) => EXPORT_IDENTIFIER.test(name));
623
+ if (named.length === 0) continue;
624
+ chunk.code += `\nexport default { ${named.join(", ")} };\n`;
625
+ }
626
+ }
627
+ };
628
+ }
563
629
  /**
564
630
  * CSS 入口的包装:**样式文件不能直接当入口**——vite 的库模式固定 `cssCodeSplit: false`,遇到
565
631
  * `rolldownOptions.input` 里有 `.css` 直接报错。包一层 side-effect import,让样式插件按它一贯的方式
@@ -686,20 +752,29 @@ async function bundlePackageOnce(options, key) {
686
752
  const startedAt = Date.now();
687
753
  options.log?.(`[bundle-build] ${options.name}: rolldown start\n`);
688
754
  const input = {};
689
- for (const [exportName, entry] of Object.entries(options.input)) input[exportName] = await cjsShimOf({
690
- name: options.name,
691
- exportName,
692
- entry,
693
- ...options.log === void 0 ? {} : { log: options.log },
694
- outDir
695
- }) ?? await cssShimOf({
696
- exportName,
697
- entry,
698
- outDir
699
- }) ?? entry;
755
+ let interopExport;
756
+ for (const [exportName, entry] of Object.entries(options.input)) {
757
+ if (await needsCjsInterop(entry, exportName)) interopExport = exportName;
758
+ input[exportName] = await cjsShimOf({
759
+ name: options.name,
760
+ exportName,
761
+ entry,
762
+ ...options.log === void 0 ? {} : { log: options.log },
763
+ outDir
764
+ }) ?? await cssShimOf({
765
+ exportName,
766
+ entry,
767
+ outDir
768
+ }) ?? entry;
769
+ }
770
+ if (interopExport !== void 0) plugins.push(cjsInteropPlugin(interopExport));
700
771
  const bundle = await rolldown({
701
772
  input,
702
773
  plugins,
774
+ onLog: (level, log, handler) => {
775
+ if (log.code === "CONFIGURATION_FIELD_CONFLICT" && log.message?.includes("experimentalDecorators") === true) return;
776
+ handler(level, log);
777
+ },
703
778
  ...options.tsconfig === void 0 ? {} : { resolve: { tsconfigFilename: options.tsconfig } },
704
779
  transform: {
705
780
  target: [
@@ -1035,7 +1110,6 @@ async function startViteDev(ctx, options) {
1035
1110
  }
1036
1111
  },
1037
1112
  optimizeDeps: {
1038
- disabled: true,
1039
1113
  noDiscovery: true,
1040
1114
  include: []
1041
1115
  },
@@ -1097,7 +1171,8 @@ const inject = ["loader", "webServer"];
1097
1171
  */
1098
1172
  async function clientRowsOf(loader) {
1099
1173
  const found = /* @__PURE__ */ new Map();
1100
- for (const entry of loader.entries()) {
1174
+ for (const raw of loader.entries()) {
1175
+ const entry = raw;
1101
1176
  if (entry.disabled === true || entry.fiber === void 0) continue;
1102
1177
  const name = entry.options?.name;
1103
1178
  if (typeof name !== "string" || name === "") continue;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@morlay/dsh-client-modules",
3
- "version": "0.2.11-alpha.2",
3
+ "version": "0.2.11-alpha.3",
4
4
  "description": "Dev-form web layer for dsh: serves the page, the app entry and every client resource through an in-process vite dev server (middleware mode) — the node-side counterpart of upstream @deepseek-ai/dsh-client-modules, with the module table replaced by vite's resolver, dependency prebundling and HMR.",
5
5
  "keywords": [
6
6
  "client",
@@ -36,26 +36,26 @@
36
36
  "./locale/*.json": "./locale/*.json"
37
37
  },
38
38
  "dependencies": {
39
- "@deepseek-ai/dsh-client-ui-theme": "^0.2.2-alpha.2",
40
- "@deepseek-ai/dsh-host-webserver": "^0.2.2-alpha.2",
41
- "@morlay/dsh-client-ui-primitives": "0.0.14-alpha.2",
42
- "@morlay/dsh-cordis-assets": "0.2.10-alpha.2",
39
+ "@deepseek-ai/dsh-client-ui-theme": "^0.2.2-alpha.3",
40
+ "@deepseek-ai/dsh-host-webserver": "^0.2.2-alpha.3",
41
+ "@morlay/dsh-client-ui-primitives": "0.0.14-alpha.3",
42
+ "@morlay/dsh-cordis-assets": "0.2.10-alpha.3",
43
43
  "@oxc-project/runtime": "^0.153.0",
44
44
  "lightningcss": "^1.30.0",
45
45
  "vite": "^8.3.1"
46
46
  },
47
47
  "devDependencies": {
48
- "@deepseek-ai/cordis": "^4.0.6-alpha.1",
48
+ "@deepseek-ai/cordis": "^4.0.6-alpha.2",
49
49
  "@local/devkit": "0.1.0",
50
50
  "@types/node": "^26.5.0"
51
51
  },
52
52
  "peerDependencies": {
53
- "@deepseek-ai/cordis": "^4.0.6-alpha.1",
53
+ "@deepseek-ai/cordis": "^4.0.6-alpha.2",
54
54
  "react": "^18.3.1",
55
55
  "rolldown": "^1.2.14"
56
56
  },
57
57
  "engines": {
58
- "dsh": ">= 0.2.2-alpha.2 < 0.2.3",
58
+ "dsh": ">= 0.2.2-alpha.3 < 0.2.3",
59
59
  "node": ">=24.11.0"
60
60
  },
61
61
  "dsh": {
@@ -7,6 +7,7 @@
7
7
  import { appendFile, mkdir, mkdtemp, readFile, rm, stat, writeFile } from "node:fs/promises";
8
8
  import { tmpdir } from "node:os";
9
9
  import { join } from "node:path";
10
+ import { pathToFileURL } from "node:url";
10
11
  import { afterAll, beforeAll, expect, test } from "vitest";
11
12
  import { BUNDLER_REV, bundlePackage, clientModulesAlias, urlOf } from "../bundle.ts";
12
13
 
@@ -82,6 +83,7 @@ beforeAll(async () => {
82
83
  "utf8",
83
84
  );
84
85
  await writeFile(join(packageRoot, "src", "b.ts"), "export const b = 2;\n", "utf8");
86
+ await writeFile(join(packageRoot, "src", "c.ts"), "export const c = 3;\n", "utf8");
85
87
  });
86
88
 
87
89
  afterAll(async () => {
@@ -154,6 +156,23 @@ test("装饰器在产物里必须已被转译,且**不依赖进程 cwd**", asy
154
156
  }
155
157
  });
156
158
 
159
+ test("tsconfig 与 `transform.decorator` 都声明装饰器时不再提示冲突(语义相同,口径取 transform 那侧)", async () => {
160
+ // 两处声明是必须的:tsconfig 那项管不到 `node_modules` 里的包(include 面默认排除它),transform 那侧才是
161
+ // 保底。rolldown 的 `CONFIGURATION_FIELD_CONFLICT` 只是"重复声明"级提示,本链在 `onLog` 里过滤掉——
162
+ // 这条用例钉住"过滤生效",别让它哪天又冒出来。
163
+ const warned: string[] = [];
164
+ const original = console.warn;
165
+ console.warn = (...args: unknown[]) => {
166
+ warned.push(args.map(String).join(" "));
167
+ };
168
+ try {
169
+ await bundlePackage({ ...options("c"), tsconfig: join(work, "tsconfig.json") });
170
+ } finally {
171
+ console.warn = original;
172
+ }
173
+ expect(warned.filter((line) => line.includes("CONFIGURATION_FIELD_CONFLICT"))).toEqual([]);
174
+ });
175
+
157
176
  test("包住在没有 tsconfig 祖先的地方时,`resolve.tsconfigFilename` 也能让它转", async () => {
158
177
  // 外置/官方的包就住在 `node_modules/.pnpm/…` 里——向上找不到任何 tsconfig。这条守着那个开关:
159
178
  // 只给 `transform.decorator`(实测 vite 不带进 rolldown)不行,`resolve.tsconfigFilename` 才行。
@@ -172,8 +191,9 @@ test("包住在没有 tsconfig 祖先的地方时,`resolve.tsconfigFilename`
172
191
  ].join("\n"),
173
192
  "utf8",
174
193
  );
175
- // tsconfig 放在**解析根之外**(那个包在 `work2/node_modules/…`,祖先链里没有 tsconfig),由
176
- // `resolve.tsconfigFilename` 显式指过去——这正是外置/官方包在产物里的处境。
194
+ // 包在 `work2/node_modules/…`(祖先链里没有 tsconfig),tsconfig 由 `resolve.tsconfigFilename` 显式指过去——
195
+ // 外置/官方包在产物里的处境(`dshdev --prod` 的闭包面就是 node_modules 形态)。**装饰器由 transform 那一侧
196
+ // 保底**(tsconfig 的 include 面默认排除 node_modules),这条用例同时钉住"指了 tsconfig 也不会把装饰器弄丢"。
177
197
  const elsewhere = join(work2, "..", `dsh-tsconfig-${String(Date.now())}`, "tsconfig.json");
178
198
  await mkdir(join(elsewhere, ".."), { recursive: true });
179
199
  await writeFile(
@@ -202,6 +222,111 @@ test("包住在没有 tsconfig 祖先的地方时,`resolve.tsconfigFilename`
202
222
  }
203
223
  });
204
224
 
225
+ test("`main` 是 CJS、`module` 是 ESM 的双形态包:产物补出 default(Node 下那句 import 拿的就是 CJS 的 module.exports)", async () => {
226
+ // `rrule` 就是这个形态:`main`(CJS)与 `module`(ESM,没有 default)导出面不同。浏览器里取的是 `module`,
227
+ // 不补 default 的话,上游那句 `import rrulePackage from 'rrule'` 会报
228
+ // `does not provide an export named 'default'`。
229
+ const dual = join(work, "dual");
230
+ await mkdir(join(dual, "dist", "cjs"), { recursive: true });
231
+ await mkdir(join(dual, "dist", "esm"), { recursive: true });
232
+ await writeFile(
233
+ join(dual, "package.json"),
234
+ `${JSON.stringify({
235
+ name: "dual",
236
+ version: "1.0.0",
237
+ main: "dist/cjs/index.js",
238
+ module: "dist/esm/index.js",
239
+ })}\n`,
240
+ "utf8",
241
+ );
242
+ await writeFile(
243
+ join(dual, "dist", "cjs", "index.js"),
244
+ "module.exports = { alpha: 1, beta: 2 };\n",
245
+ "utf8",
246
+ );
247
+ await writeFile(
248
+ join(dual, "dist", "esm", "index.js"),
249
+ "export const alpha = 1;\nexport const beta = 2;\n",
250
+ "utf8",
251
+ );
252
+
253
+ const bundled = await bundlePackage({
254
+ cacheRoot,
255
+ repoRoot: work,
256
+ alias: clientModulesAlias({
257
+ repoRoot,
258
+ nodeStub: join(repoRoot, "packages/client/dsh-client-modules/app-boot/node-stub.cjs"),
259
+ }),
260
+ name: "dual",
261
+ version: "1.0.0",
262
+ input: { index: join(dual, "dist", "esm", "index.js") },
263
+ watch: [join(dual, "dist", "esm")],
264
+ });
265
+ const file = join(cacheRoot, bundled?.files.get("index") ?? "");
266
+ const code = await readFile(file, "utf8");
267
+ // rolldown 会把 `export default` 压成 `export { x as default }`,两种形态都算补上了。
268
+ expect(code).toMatch(/\bas default\b|export default/u);
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);
277
+ });
278
+
279
+ test("双形态包的 ESM 入口自带 default 时不覆盖它(`jsbi` 那种:`export default JSBI`)", async () => {
280
+ // 补 default 只对"ESM 入口没有 default"的那种包。`jsbi` 的 `module` 里有 `export default JSBI`,若也套上
281
+ // 命名空间对象,`@js-temporal/polyfill` 的 `import e from "jsbi"` 就拿不到 JSBI 类,运行期报
282
+ // `e.BigInt is not a function`。
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 });
286
+ await writeFile(
287
+ join(dualDefault, "package.json"),
288
+ `${JSON.stringify({
289
+ name: "dual-default",
290
+ version: "1.0.0",
291
+ main: "dist/cjs/index.js",
292
+ module: "dist/esm/index.js",
293
+ })}\n`,
294
+ "utf8",
295
+ );
296
+ await writeFile(
297
+ join(dualDefault, "dist", "cjs", "index.js"),
298
+ "module.exports = { other: 1 };\n",
299
+ "utf8",
300
+ );
301
+ await writeFile(
302
+ join(dualDefault, "dist", "esm", "index.js"),
303
+ "const marker = { own: true };\nexport const other = 1;\nexport default marker;\n",
304
+ "utf8",
305
+ );
306
+
307
+ const bundled = await bundlePackage({
308
+ cacheRoot,
309
+ repoRoot: work,
310
+ alias: [],
311
+ name: "dual-default",
312
+ version: "1.0.0",
313
+ input: { index: join(dualDefault, "dist", "esm", "index.js") },
314
+ watch: [join(dualDefault, "dist", "esm")],
315
+ });
316
+ const file = join(cacheRoot, bundled?.files.get("index") ?? "");
317
+ const loaded = (await import(pathToFileURL(file).href)) as {
318
+ readonly default: { readonly own?: boolean; readonly other?: number };
319
+ };
320
+ // 包自己的 default(不是命名空间对象:命名空间上没有 `own`、倒是有 `other`)。
321
+ expect(loaded.default.own).toBe(true);
322
+ expect(loaded.default.other).toBeUndefined();
323
+ });
324
+
325
+ test("纯 ESM 的包不补 default(不掩盖真实的导出缺失)", async () => {
326
+ const code = await readFile(join(cacheRoot, "fixture@1.0.0", "b.js"), "utf8");
327
+ expect(code).not.toContain("export default");
328
+ });
329
+
205
330
  test("清单里解析不到的依赖不算可外置候选,也不该让预编译失败", async () => {
206
331
  // 真实里踩到的形状:`zustand` 的 `peerDependencies` 里有 `@types/react`(optional)——类型包不在运行时闭包
207
332
  // 里,于是解析它必然失败。它是**可选** peer、又解析不到,两条都不该让它进候选;若把异常抛出去,死的是
package/src/bundle.ts CHANGED
@@ -9,10 +9,10 @@
9
9
  * 两者产出的地址同构(`@client-modules/<pkg>@<version>/<file>`),所以运行时按 URL 取即可。
10
10
  */
11
11
  import { createHash } from "node:crypto";
12
- import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
12
+ import { mkdir, readFile, readdir, realpath, stat, writeFile } from "node:fs/promises";
13
13
  import { createRequire } from "node:module";
14
14
  import { findPackageJSON } from "node:module";
15
- import { dirname, join, relative, sep } from "node:path";
15
+ import { dirname, join, relative, resolve, sep } from "node:path";
16
16
  import { pathToFileURL } from "node:url";
17
17
  import { clientAssetPlugin, NODE_BUILTIN } from "@morlay/dsh-cordis-assets/assets";
18
18
  import { cssInlinePlugins } from "@morlay/dsh-cordis-assets/styles";
@@ -61,7 +61,7 @@ export function clientModulesAlias(options: {
61
61
  * 规则变了、源码没变时,旧产物必须作废,否则会继续复用带着老 URL 的字节。
62
62
  */
63
63
  /** 构建器**规则**版本:改解析 / 改写规则就 bump(见 {@link BUNDLER_REV})。 */
64
- const BUNDLER_RULES = "22";
64
+ const BUNDLER_RULES = "28";
65
65
 
66
66
  /**
67
67
  * 地址里的 `?v=`(与 vite 同一个写法):构建器**规则指纹**(`BUNDLER_RULES` 的哈希,形如 vite 的 `?v=`)。
@@ -322,6 +322,32 @@ function browserProcessPlugin(): PluginOption {
322
322
  };
323
323
  }
324
324
 
325
+ /**
326
+ * 入口是否按**它所属包的声明**是 CJS(Node 的 ESM 解析会做 `default = module.exports` 的那一侧)。
327
+ *
328
+ * 判据只看声明,不看源码内容:
329
+ * - `type: "module"` 的包 → 恒为 ESM(`.mjs` 同理);
330
+ * - 否则是"老约定":`module` 字段指向的那份视为 ESM(打包器约定,Node 不认),其余 `.js` 视为 CJS、
331
+ * `.cjs` 恒为 CJS;`.ts` 这类源码交给语法(rolldown 自己判)。
332
+ *
333
+ * 以前这里是"读源码找 `module.exports =`",那是猜:`exports.` 出现在注释里就会误判,而 `type: module` 的
334
+ * 包即使有 CJS 写法也不该被当 CJS。
335
+ */
336
+ async function declaresCjs(entry: string): Promise<boolean> {
337
+ if (entry.endsWith(".mjs")) return false;
338
+ if (entry.endsWith(".cjs")) return true;
339
+ if (!entry.endsWith(".js")) return false;
340
+ const manifestPath = await packageJsonNear(entry);
341
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8").catch(() => "{}")) as {
342
+ type?: string;
343
+ module?: string;
344
+ };
345
+ if (manifest.type === "module") return false;
346
+ const manifestModule = manifest.module;
347
+ if (manifestModule === undefined) return true;
348
+ return resolve(dirname(manifestPath), manifestModule) !== resolve(entry);
349
+ }
350
+
325
351
  /**
326
352
  * CJS 入口的**具名导出包装**(`react` 这类)。
327
353
  *
@@ -339,14 +365,7 @@ async function cjsShimOf(options: {
339
365
  outDir: string;
340
366
  log?: (message: string) => void;
341
367
  }): Promise<string | undefined> {
342
- const source = await readFile(options.entry, "utf8").catch(() => "");
343
- // 判据是**CJS 入口**:`.js`/`.cjs` 且源码里真的在赋值 `module.exports` / `exports.x`(`react` 的入口是
344
- // `module.exports = require('./cjs/…')`)。TS 源码不在此列——那是 ESM;按"含 `exports.` 子串"判断会把
345
- // 注释里的 `re-exports.` 也当成 CJS,反过来给一份纯 ESM 加 `import mod from …` 而构建失败。
346
- const isCjsEntry =
347
- /\.(?:cjs|js)$/u.test(options.entry) &&
348
- /(?:^|\n)\s*(?:module\.exports\s*=|exports\.[A-Za-z_$][A-Za-z0-9_$]*\s*=)/u.test(source);
349
- if (!isCjsEntry) return undefined;
368
+ if (!(await declaresCjs(options.entry))) return undefined;
350
369
  let keys: string[];
351
370
  try {
352
371
  // 求值才知道键:CJS 的导出面是**运行期**的(静态分析只能看到 `module.exports = require(…)`)。
@@ -381,6 +400,62 @@ async function cjsShimOf(options: {
381
400
  return file;
382
401
  }
383
402
 
403
+ /** 根出口的产物名(`urlOf` 对非 client 行写的就是它)。 */
404
+ const ROOT_EXPORT = "index";
405
+
406
+ /** 具名导出的合法标识符:补 default 时按它挑键(非标识符的导出名用不了简写)。 */
407
+ const EXPORT_IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/u;
408
+
409
+ /**
410
+ * 这个入口是否需要「CJS 包的 ESM 便利入口」的 default 补偿。判据**只看 package.json 的声明**:
411
+ *
412
+ * - `type: "module"` 的包:Node 与我们都按 `exports`(或 `main` 本身就是 ESM)解析,两边一致,不补;
413
+ * - 其余是 CJS 包:`module` 只是打包器约定(视为 ESM),`main` 才是 CJS。这条链选 `module`(更小、没有
414
+ * webpack 垫片),而 Node 的 `import pkg from 'pkg'` 落到 `main`、default 是它的 `module.exports`——
415
+ * 所以浏览器侧得补一个 default,否则消费方那句 default import 直接报
416
+ * `does not provide an export named 'default'`(`rrule` 就是这一例)。
417
+ * - 有 `exports` 的包两边都按条件解析,不涉及这条约定;入口不是 `module` 的(选了 `main`)由
418
+ * {@link cjsShimOf} 处理。`main`/`module` 只描述根出口,子路径不在此列。
419
+ */
420
+ async function needsCjsInterop(entry: string, exportName: string): Promise<boolean> {
421
+ if (exportName !== ROOT_EXPORT) return false;
422
+ const manifestPath = await packageJsonNear(entry);
423
+ const manifest = JSON.parse(await readFile(manifestPath, "utf8").catch(() => "{}")) as {
424
+ main?: string;
425
+ module?: string;
426
+ type?: string;
427
+ exports?: unknown;
428
+ };
429
+ if (manifest.type === "module" || manifest.exports !== undefined) return false;
430
+ const manifestModule = manifest.module;
431
+ if (manifestModule === undefined || manifest.main === undefined) return false;
432
+ const modulePath = resolve(dirname(manifestPath), manifestModule);
433
+ const moduleEntry = await realpath(modulePath).catch(() => modulePath);
434
+ const entryPath = await realpath(entry).catch(() => resolve(entry));
435
+ return moduleEntry === entryPath || modulePath === resolve(entry);
436
+ }
437
+
438
+ /**
439
+ * 给「CJS 包的 ESM 便利入口」补 default:**看产物的真实导出面**(`chunk.exports`),不猜源码。
440
+ * 已经有 default 的(`jsbi` 的 `export default JSBI`)原样保留——补成对象会把它顶掉,
441
+ * `@js-temporal/polyfill` 那句 `import e from "jsbi"` 于是拿到命名空间、运行期 `e.BigInt is not a function`;
442
+ * 没有的补成 `{ 具名导出… }`,与 CJS 的 `module.exports` 同形(`rrule` 的 `ALL_WEEKDAYS` / `rrulestr` 那批)。
443
+ */
444
+ function cjsInteropPlugin(exportName: string): Plugin {
445
+ return {
446
+ name: "dsh-cjs-interop",
447
+ generateBundle(_options, bundle) {
448
+ for (const chunk of Object.values(bundle)) {
449
+ if (chunk.type !== "chunk" || chunk.isEntry !== true || chunk.name !== exportName) continue;
450
+ if (chunk.exports.includes("default")) continue;
451
+ const named = chunk.exports.filter((name) => EXPORT_IDENTIFIER.test(name));
452
+ if (named.length === 0) continue;
453
+ chunk.code += `\nexport default { ${named.join(", ")} };\n`;
454
+ }
455
+ },
456
+ };
457
+ }
458
+
384
459
  /**
385
460
  * CSS 入口的包装:**样式文件不能直接当入口**——vite 的库模式固定 `cssCodeSplit: false`,遇到
386
461
  * `rolldownOptions.input` 里有 `.css` 直接报错。包一层 side-effect import,让样式插件按它一贯的方式
@@ -572,7 +647,10 @@ async function bundlePackageOnce(
572
647
  // 入口补偿:CJS 要带具名导出的包装;CSS 不能直接当入口(vite 的库模式 `cssCodeSplit: false`,
573
648
  // 明确拒绝 `rolldownOptions.input` 里出现 CSS 文件),包一层 side-effect import 让样式插件处理它。
574
649
  const input: Record<string, string> = {};
650
+ // 「CJS 包的 ESM 便利入口」这一形态要补一个 default(判据见 `needsCjsInterop`),补的时机在产物生成之后。
651
+ let interopExport: string | undefined;
575
652
  for (const [exportName, entry] of Object.entries(options.input)) {
653
+ if (await needsCjsInterop(entry, exportName)) interopExport = exportName;
576
654
  input[exportName] =
577
655
  (await cjsShimOf({
578
656
  name: options.name,
@@ -584,12 +662,25 @@ async function bundlePackageOnce(
584
662
  (await cssShimOf({ exportName, entry, outDir })) ??
585
663
  entry;
586
664
  }
665
+ if (interopExport !== undefined) plugins.push(cjsInteropPlugin(interopExport));
587
666
  // **直接跑 rolldown**(不经过 `vite build`):插件本来就是 rolldown 的接口,而 vite 在这条路上只提供了
588
667
  // lib 模式的入口/输出命名与 `worker.format`——那几项自己写更直接,且 `build.rolldownOptions` 里的
589
668
  // `transform` / `resolve` 也不会再被 vite 吞掉(对照表见债文档,那份债随这次改动一起销)。
590
669
  const bundle = await rolldown({
591
670
  input,
592
671
  plugins,
672
+ // 装饰器口径在两处声明(tsconfig 的 `experimentalDecorators` 与 `transform.decorator`)时,rolldown 会提
673
+ // 一条 `CONFIGURATION_FIELD_CONFLICT` 让人确认——那是"重复声明"级提示,两者语义等价,我们主动取
674
+ // transform 这一侧(见上面的 decorator 注释)。过滤掉这一条,别的日志照旧走默认处理。
675
+ onLog: (level, log, handler) => {
676
+ if (
677
+ log.code === "CONFIGURATION_FIELD_CONFLICT" &&
678
+ log.message?.includes("experimentalDecorators") === true
679
+ ) {
680
+ return;
681
+ }
682
+ handler(level, log);
683
+ },
593
684
  // **不指 tsconfig**:装饰器由下面的 `transform.decorator.legacy` 直接开(直出时它生效,见对照表)。
594
685
  // rolldown 自己会解析 tsconfig(含 `extends`,实测过),所以两种形态各指一处不如一处都不指。
595
686
  ...(options.tsconfig === undefined ? {} : { resolve: { tsconfigFilename: options.tsconfig } }),
@@ -598,7 +689,11 @@ async function bundlePackageOnce(
598
689
  // 直出后没有 `build.target: "baseline-widely-available"` 那层展开,这里自己写全。不要图省事写
599
690
  // `esnext` 或留空——目标一低,oxc 就给类字段、可选链这些逐个加降级 helper,产物里全是奇怪的垫片。
600
691
  target: ["chrome111", "edge111", "firefox114", "safari16.4"],
601
- // 装饰器:legacy(cordis 与上游用的就是这套)。有 tsconfig 时它会被那份覆盖,这里是默认。
692
+ // 装饰器:legacy(cordis 与上游用的就是这套)。**必须显式给**:闭包面(`node_modules`)里的包不在
693
+ // tsconfig 的 include 面里(tsc 默认排除 `node_modules`,oxc 同样不应用那份设置),只靠根 tsconfig 的
694
+ // `experimentalDecorators` 的话,上游的 `@Remote` 会原样留在产物里、浏览器整条入口不执行。
695
+ // 它与 tsconfig 那项**语义相同**,两处都声明时 rolldown 会提示"tsconfig 被 transform 覆盖"——这条提示
696
+ // 对我们没有决策价值(见下面的 `onLog`),不是需要处理的冲突。
602
697
  decorator: { legacy: true },
603
698
  // 浏览器没有 `process`:精确替换 `process.env`,裸 `process` 再给一个空壳——`immer` 这类包会在
604
699
  // 模块顶层读它,漏掉就是一个 `process is not defined` 把整份产物带下去。
package/src/index.ts CHANGED
@@ -97,10 +97,13 @@ export interface ClientRow {
97
97
  * 自己的 client 半里 import,不该出现在装配清单里;混进来就会多装配一次(`apply` 里那些"同 namespace 的
98
98
  * locale 已注册"的报错就是这么来的),也会在入口打出一串 `skip (no apply/default)`。
99
99
  */
100
- async function clientRowsOf(loader: { entries(): Generator<LoaderEntry> }): Promise<ClientRow[]> {
100
+ // 参数只声明"能迭代条目":上游 `Entry` 的类型面随版本演进(alpha.3 换了 `Generator` 的返回/next 参数),
101
+ // 我们只要下面那几个字段,边界收成 `unknown` 再窄化,别让版本演进每次都在这里报类型错。
102
+ async function clientRowsOf(loader: { entries(): Iterable<unknown> }): Promise<ClientRow[]> {
101
103
  const found = new Map<string, ClientRow>();
102
104
  // `entries()` 是生成器(cordis 的 Loader 就是这么给的),直接迭代即可。
103
- for (const entry of loader.entries()) {
105
+ for (const raw of loader.entries()) {
106
+ const entry = raw as LoaderEntry;
104
107
  if (entry.disabled === true || entry.fiber === undefined) continue;
105
108
  const name = entry.options?.name;
106
109
  if (typeof name !== "string" || name === "") continue;
package/src/serve-vite.ts CHANGED
@@ -142,17 +142,14 @@ export async function startViteDev(
142
142
  // 表现为一串"没被 vite 接手"的 404)。
143
143
  fs: { strict: false, allow: [repoRoot] },
144
144
  },
145
- // **不用** vite 的 `optimizeDeps`:裸说明符已由插件统一改写成 `@deps/`(我们自己预打包的产物),
146
- // vite 再打一份就会与聚合产物里的那份形成两个实例。
147
- // **不用** vite 的 `optimizeDeps`(关掉自动发现):非开发模块与三方都由我们的联邦链产出,裸说明符
148
- // 已在插件里改写成 `@client-modules/…`,这里没有任何东西需要预打包。开着只会多出第二条链(浏览器里
149
- // 同时出现 `.vite/deps/react.js` 与我们的 `@client-modules/react@…/index.js`)与一次全仓扫描。
150
- // **彻底不用** vite 的依赖优化器:裸说明符已由插件统一改写成 `@client-modules/…`(我们自己的联邦链产出),
151
- // 这里没有任何东西需要它预打包。`noDiscovery: true` 只关了"自动发现",扫描器仍会去解析那些包名——而行清单
152
- // 是**虚拟模块**、里面写的包名 vite 按 `package.json` 的去向解析,落到工作区解析面上就报
153
- // `Cannot find package … imported from …/package.json`(上游 workspace 内部包只存在于 vendor / 闭包)。
154
- // `disabled` 是官方给的"整个关掉"(标了 deprecated,但语义正是这个)。
155
- optimizeDeps: { disabled: true, noDiscovery: true, include: [] },
145
+ // **不用** vite 的依赖优化器:裸说明符已由插件统一改写成 `@client-modules/…`(我们自己的联邦链产出),
146
+ // 这里没有任何东西需要它预打包。开着只会多出第二条链(浏览器里同时出现 `.vite/deps/react.js` 与我们的
147
+ // `@client-modules/react@…/index.js`,两个实例)与一次全仓扫描。行清单是**虚拟模块**、里面写的包名 vite
148
+ // 会按 `package.json` 的去向解析,落到工作区解析面上就报
149
+ // `Cannot find package … imported from …/package.json`(上游 workspace 内部包只存在于 vendor / 闭包),
150
+ // 所以必须连"扫描"一起关掉。
151
+ // 官方口径的"整个关掉"就是这两个字段(`optimizeDeps.disabled` 在 Vite 5.1 已移除,传它只会换来一条告警)。
152
+ optimizeDeps: { noDiscovery: true, include: [] },
156
153
  plugins: [
157
154
  dshClientRowsPlugin({
158
155
  repoRoot,