| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091 |
- /**
- * gen-shims.cjs —— 重新生成 shims/*.ts 的转发清单(框架升级五步流程的辅助脚本,README §1.5)。
- *
- * 用法:node scripts/gen-shims.cjs
- * 原理:从 node_modules 安装的框架包(与 vendor 版本表同版本)读运行时公开 API,
- * 逐一生成为 `export const X = V.X;` 静态转发行。
- *
- * 类型策略(关键,决定 V 的静态类型):
- * V 收窄为 ESM 命名空间类型(import type * as XNS from 'pkg')。
- * 原因与限制:vue 3.5 起 **CJS 运行时导出 ≠ ESM 类型声明**——
- * ssrUtils / withAsyncContext / mergeDefaults 等键在运行时(require/UMD 全局)存在,
- * 但 ESM 声明文件未导出它们。若按 `typeof globalThis.Vue`(= ESM 类型)收窄,
- * 这些键的转发行会 TS 报"Property does not exist"。
- * 处理:对"运行时存在但 ESM 类型未声明"的键,用下方 EXCLUDE_* 清单显式跳过——
- * 这些键是 SSR/compat 边缘工具,纯浏览器客户端业务用不到,SFC 编译器产物也不会
- * import 它们(编译器只 import openBlock/createVNode 等 runtime-core 导出,全在 ESM 类型里)。
- * 跳过它们 = shims 只转发"ESM 类型已声明的公开 API",每个转发行类型精确、vue-tsc 零报错。
- * 维护纪律:升级框架(README §1.5)后重跑 pnpm typecheck,若出现新的
- * "Property 'X' does not exist"报错,把 X 补进对应 EXCLUDE 清单再重新生成本脚本。
- *
- * 静态逐一转发(而非 export * / default namespace):
- * ① 每个导出的值在模块顶层从 UMD 全局取值——dist 不含框架本体;
- * ② 模板编译器产物 import 的任何具名导出都能命中。
- *
- * 纪律:shims/ 是全项目唯一允许出现框架模块名的目录;本脚本是 shims 的唯一事实来源。
- */
- const fs = require('fs');
- const path = require('path');
- /**
- * 运行时导出但 ESM 类型未声明的键(按包名)。
- * vue 3.5:以下 10 个为 SSR/compat 边缘工具,浏览器客户端用不到,跳过。
- * 升级框架后按 pnpm typecheck 报错增量维护本清单。
- */
- const EXCLUDE = {
- vue: new Set([
- 'ssrUtils',
- 'withAsyncContext',
- 'mergeDefaults',
- 'mergeModels',
- 'resolveFilter',
- 'compatUtils',
- 'ErrorTypeStrings',
- 'assertNumber',
- 'createPropsRestProxy',
- 'initDirectivesForSSR',
- ]),
- 'vue-router': new Set(),
- pinia: new Set(),
- };
- function gen(pkgName, globalName, outFile) {
- const ns = require(pkgName);
- const keys = Object.keys(ns);
- const validId = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
- const excluded = EXCLUDE[pkgName] ?? new Set();
- const exportable = keys.filter((k) => validId.test(k) && !k.startsWith('__') && !excluded.has(k));
- const header = `/**
- * shims/${path.basename(outFile)} —— '${pkgName}' 模块名 → UMD 全局 ${globalName} 的唯一胶水层(README §3.3-1)。
- *
- * 存在理由:SFC 模板编译器(@vitejs/plugin-vue)产物内部会生成
- * 'import { X } from "${pkgName}"' 这类模块名 import,vite define 无法改写模块名(只处理裸标识符),
- * 故 vite.config 的 alias 将 '${pkgName}' 重定向到本文件。
- *
- * 关键实现(保证 dist 不含框架运行时):
- * - import type 仅取 ESM 命名空间类型(编译期擦除,不进产物);
- * - 运行时值全部取自 globalThis.${globalName}(index.html 引入的 UMD 全局);
- * - 编译后只剩"取全局 + 转发"代码,框架本体不进 dist。
- *
- * 本文件由 scripts/gen-shims.cjs 生成(导出清单来自当前安装版本,跳过 ESM 类型未声明的 SSR/compat 边缘键);
- * 框架升级(README §1.5 五步流程)时须重新生成并按 typecheck 报错维护排除清单。
- * 纪律:shims/ 是全项目唯一允许出现框架模块名的目录。
- */
- import type * as ${globalName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_NS from '${pkgName}';
- /** 运行时框架全局(index.html 按序注入;经 unknown 收窄为 ESM 命名空间类型) */
- const V: typeof ${globalName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_NS = (globalThis as unknown as { ${globalName}: typeof ${globalName.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_NS }).${globalName};
- `;
- const body = exportable
- .map((k) => `// eslint-disable-next-line import/no-mutable-exports\nexport const ${k} = V.${k};`)
- .join('\n');
- fs.writeFileSync(outFile, header + body + '\n');
- console.log(`${outFile}: ${exportable.length} exports (excluded ${excluded.size})`);
- }
- const root = path.join(__dirname, '..');
- gen('vue', 'Vue', path.join(root, 'shims', 'vue.ts'));
- gen('vue-router', 'VueRouter', path.join(root, 'shims', 'vue-router.ts'));
- gen('pinia', 'Pinia', path.join(root, 'shims', 'pinia.ts'));
|