@vitarx/plugin-vite 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/dist/constants/index.d.ts +0 -9
  2. package/dist/constants/index.js +0 -9
  3. package/dist/hmr-client/index.d.ts +27 -1
  4. package/dist/hmr-client/index.js +108 -12
  5. package/dist/hmr-client/update.d.ts +11 -2
  6. package/dist/hmr-client/update.js +25 -59
  7. package/dist/hmr-client/weak-refs.d.ts +37 -0
  8. package/dist/hmr-client/weak-refs.js +41 -0
  9. package/dist/passes/directives/processDirectives.d.ts +1 -1
  10. package/dist/passes/directives/processDirectives.js +15 -10
  11. package/dist/passes/hmr/index.d.ts +1 -1
  12. package/dist/passes/hmr/index.js +1 -1
  13. package/dist/passes/hmr/inject-state.d.ts +16 -10
  14. package/dist/passes/hmr/inject-state.js +141 -135
  15. package/dist/passes/hmr/inject.d.ts +10 -0
  16. package/dist/passes/hmr/inject.js +47 -10
  17. package/dist/passes/imports/collectRefVariables.d.ts +1 -0
  18. package/dist/passes/imports/collectRefVariables.js +52 -19
  19. package/dist/passes/index.d.ts +1 -1
  20. package/dist/passes/index.js +1 -1
  21. package/dist/passes/jsx/processJSXFragment.js +18 -1
  22. package/dist/passes/props/attribute.d.ts +3 -0
  23. package/dist/passes/props/attribute.js +13 -3
  24. package/dist/transform.js +16 -5
  25. package/dist/types.d.ts +1 -1
  26. package/dist/utils/branch-factory.js +26 -6
  27. package/dist/utils/component-collect.d.ts +7 -0
  28. package/dist/utils/component-collect.js +12 -236
  29. package/dist/utils/default-export.d.ts +38 -0
  30. package/dist/utils/default-export.js +119 -0
  31. package/dist/utils/forward-export.d.ts +28 -0
  32. package/dist/utils/forward-export.js +76 -0
  33. package/dist/utils/function-extract.d.ts +45 -0
  34. package/dist/utils/function-extract.js +98 -0
  35. package/dist/utils/generate.d.ts +7 -0
  36. package/dist/utils/generate.js +20 -0
  37. package/dist/utils/index.d.ts +3 -2
  38. package/dist/utils/index.js +4 -2
  39. package/dist/utils/jsx-helpers.d.ts +1 -4
  40. package/dist/utils/jsx-helpers.js +2 -15
  41. package/dist/utils/vif-helpers.js +6 -3
  42. package/package.json +5 -4
  43. package/dist/hmr-client/utils.d.ts +0 -23
  44. package/dist/hmr-client/utils.js +0 -137
@@ -0,0 +1,28 @@
1
+ /**
2
+ * 导入转发默认导出检测
3
+ *
4
+ * 识别「纯转发组件默认导出」的模块——典型场景是文件路由的 _layout 布局
5
+ * 转发文件(`export default AdminLayout` / `export { AdminLayout as default }`,
6
+ * AdminLayout 为 import 绑定)。此类模块本身不含组件函数定义,组件收集
7
+ * 结果为空,此前不会注入任何 HMR 代码,编辑该文件时 Vite 找不到自接受
8
+ * 边界而回落整页刷新。
9
+ *
10
+ * 注意只匹配组件形态(大写开头)的导入绑定:转发普通值(工具函数、常量)
11
+ * 的模块不具备组件热更新能力,保持整页刷新的默认行为。
12
+ * @module utils/forward-export
13
+ */
14
+ import * as t from '@babel/types';
15
+ /**
16
+ * 检测模块是否存在「导入转发形式的组件默认导出」
17
+ *
18
+ * 覆盖两种转发形态(转发目标均为本地 import 绑定且大写开头):
19
+ * 1. `export default AdminLayout`(含 `as T` 断言包装)
20
+ * 2. `export { AdminLayout as default }`
21
+ *
22
+ * 带 source 的 re-export(`export { X as default } from './x'`)没有本地
23
+ * 绑定,不在导入绑定集合中,天然被排除。
24
+ *
25
+ * @param program - AST Program 节点
26
+ * @returns 是否存在转发形式的组件默认导出
27
+ */
28
+ export declare function hasForwardedComponentDefaultExport(program: t.Program): boolean;
@@ -0,0 +1,76 @@
1
+ import { isValidComponentName } from './component-collect.js';
2
+ /**
3
+ * 收集模块内所有本地 import 绑定名称
4
+ * @param program - AST Program 节点
5
+ * @returns 导入绑定名称集合
6
+ */
7
+ function collectImportedBindingNames(program) {
8
+ const importedBindings = new Set();
9
+ for (const node of program.body) {
10
+ if (node.type !== 'ImportDeclaration')
11
+ continue;
12
+ for (const spec of node.specifiers) {
13
+ importedBindings.add(spec.local.name);
14
+ }
15
+ }
16
+ return importedBindings;
17
+ }
18
+ /**
19
+ * 解包 TS 类型包装表达式
20
+ *
21
+ * `export default X as T` 在 babel TS 插件下解析为 TSAsExpression
22
+ * (还有 satisfies / 非空断言变体),解包到真实表达式后再判定。
23
+ * @param node - 默认导出声明表达式
24
+ * @returns 解包后的表达式
25
+ */
26
+ function unwrapTsWrappers(node) {
27
+ while (node.type === 'TSAsExpression' ||
28
+ node.type === 'TSSatisfiesExpression' ||
29
+ node.type === 'TSNonNullExpression') {
30
+ node = node.expression;
31
+ }
32
+ return node;
33
+ }
34
+ /**
35
+ * 检测模块是否存在「导入转发形式的组件默认导出」
36
+ *
37
+ * 覆盖两种转发形态(转发目标均为本地 import 绑定且大写开头):
38
+ * 1. `export default AdminLayout`(含 `as T` 断言包装)
39
+ * 2. `export { AdminLayout as default }`
40
+ *
41
+ * 带 source 的 re-export(`export { X as default } from './x'`)没有本地
42
+ * 绑定,不在导入绑定集合中,天然被排除。
43
+ *
44
+ * @param program - AST Program 节点
45
+ * @returns 是否存在转发形式的组件默认导出
46
+ */
47
+ export function hasForwardedComponentDefaultExport(program) {
48
+ const importedBindings = collectImportedBindingNames(program);
49
+ if (importedBindings.size === 0)
50
+ return false;
51
+ for (const node of program.body) {
52
+ // 形态1:export default X
53
+ if (node.type === 'ExportDefaultDeclaration') {
54
+ const decl = unwrapTsWrappers(node.declaration);
55
+ if (decl.type === 'Identifier') {
56
+ const name = decl.name;
57
+ if (importedBindings.has(name) && isValidComponentName(name))
58
+ return true;
59
+ }
60
+ }
61
+ // 形态2:export { X as default }
62
+ if (node.type === 'ExportNamedDeclaration') {
63
+ for (const spec of node.specifiers) {
64
+ if (spec.type === 'ExportSpecifier' &&
65
+ spec.exported.type === 'Identifier' &&
66
+ spec.exported.name === 'default' &&
67
+ spec.local.type === 'Identifier' &&
68
+ importedBindings.has(spec.local.name) &&
69
+ isValidComponentName(spec.local.name)) {
70
+ return true;
71
+ }
72
+ }
73
+ }
74
+ }
75
+ return false;
76
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * 函数提取模块
3
+ * 从调用表达式中提取匿名函数,转换为命名函数声明
4
+ * @module utils/function-extract
5
+ */
6
+ import * as t from '@babel/types';
7
+ /**
8
+ * 将函数表达式或箭头函数转换为命名函数声明
9
+ * 用于将匿名函数转换为可追踪的命名函数
10
+ * @param func - 函数表达式或箭头函数节点
11
+ * @param name - 生成的函数名称
12
+ * @returns 命名函数声明节点
13
+ */
14
+ export declare function convertToNamedFunctionDeclaration(func: t.FunctionExpression | t.ArrowFunctionExpression, name: string): t.FunctionDeclaration;
15
+ /**
16
+ * 从调用表达式中提取函数参数
17
+ * 将 export default builder(()=><div/>) 转换为:
18
+ * const _defaultExport = ()=><div/>
19
+ * export default builder(_defaultExport)
20
+ * @param callExpr - 调用表达式
21
+ * @param exportedNames - 已存在的名称集合
22
+ * @param builderAlias - builder 的本地别名
23
+ * @returns 提取结果,包含名称、函数声明节点和是否是 builder 包装
24
+ */
25
+ export declare function extractFunctionFromCallExpression(callExpr: t.CallExpression, exportedNames: Set<string>, builderAlias: string | null): {
26
+ name: string;
27
+ funcNode: t.FunctionDeclaration;
28
+ isBuilderWrapped: boolean;
29
+ } | null;
30
+ /**
31
+ * 从变量声明的调用表达式中提取函数
32
+ * 将 const App = builder(() => <div/>) 转换为:
33
+ * function App$1() { return <div/> }
34
+ * const App = builder(App$1)
35
+ * @param callExpr - 调用表达式
36
+ * @param variableName - 变量名(作为内部函数名的基础)
37
+ * @param allBindingNames - 所有绑定名称集合(用于避免命名冲突)
38
+ * @param builderAlias - builder 的本地别名
39
+ * @returns 提取结果,包含名称、函数声明节点和是否是 builder 包装
40
+ */
41
+ export declare function extractFunctionFromVariableInit(callExpr: t.CallExpression, variableName: string, allBindingNames: Set<string>, builderAlias: string | null): {
42
+ name: string;
43
+ funcNode: t.FunctionDeclaration;
44
+ isBuilderWrapped: boolean;
45
+ } | null;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * 函数提取模块
3
+ * 从调用表达式中提取匿名函数,转换为命名函数声明
4
+ * @module utils/function-extract
5
+ */
6
+ import * as t from '@babel/types';
7
+ import { isBuilderCall } from './collect-names.js';
8
+ import { generateUniqueAlias, generateUniqueDefaultName } from './generate.js';
9
+ /**
10
+ * 将函数表达式或箭头函数转换为命名函数声明
11
+ * 用于将匿名函数转换为可追踪的命名函数
12
+ * @param func - 函数表达式或箭头函数节点
13
+ * @param name - 生成的函数名称
14
+ * @returns 命名函数声明节点
15
+ */
16
+ export function convertToNamedFunctionDeclaration(func, name) {
17
+ let body;
18
+ // 处理箭头函数的隐式返回:将表达式体包装为显式 return 语句
19
+ if (func.type === 'ArrowFunctionExpression' && func.body.type !== 'BlockStatement') {
20
+ body = t.blockStatement([t.returnStatement(func.body)]);
21
+ }
22
+ else {
23
+ body = func.body;
24
+ }
25
+ // 创建函数声明节点
26
+ const funcDecl = t.functionDeclaration(t.identifier(name), // 函数名称
27
+ func.params, // 参数列表
28
+ body, // 函数体
29
+ func.type === 'FunctionExpression' ? func.generator : false, // 是否为生成器
30
+ func.async // 是否为异步函数
31
+ );
32
+ // 保留原始位置信息用于源码映射
33
+ funcDecl.loc = func.loc;
34
+ return funcDecl;
35
+ }
36
+ /**
37
+ * 从调用表达式中提取函数参数
38
+ * 将 export default builder(()=><div/>) 转换为:
39
+ * const _defaultExport = ()=><div/>
40
+ * export default builder(_defaultExport)
41
+ * @param callExpr - 调用表达式
42
+ * @param exportedNames - 已存在的名称集合
43
+ * @param builderAlias - builder 的本地别名
44
+ * @returns 提取结果,包含名称、函数声明节点和是否是 builder 包装
45
+ */
46
+ export function extractFunctionFromCallExpression(callExpr, exportedNames, builderAlias) {
47
+ // 判断是否为 builder 函数调用
48
+ const isBuilder = isBuilderCall(callExpr, builderAlias);
49
+ // 遍历调用表达式的所有参数
50
+ for (let i = 0; i < callExpr.arguments.length; i++) {
51
+ const arg = callExpr.arguments[i];
52
+ // 找到作为参数传入的匿名函数
53
+ if (arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression') {
54
+ // 生成唯一名称
55
+ const name = generateUniqueDefaultName(exportedNames);
56
+ // 转换为命名函数声明
57
+ const funcNode = convertToNamedFunctionDeclaration(arg, name);
58
+ // 将原参数替换为标识符引用
59
+ callExpr.arguments[i] = t.identifier(name);
60
+ return { name, funcNode, isBuilderWrapped: isBuilder };
61
+ }
62
+ }
63
+ // 未找到可提取的函数参数
64
+ return null;
65
+ }
66
+ /**
67
+ * 从变量声明的调用表达式中提取函数
68
+ * 将 const App = builder(() => <div/>) 转换为:
69
+ * function App$1() { return <div/> }
70
+ * const App = builder(App$1)
71
+ * @param callExpr - 调用表达式
72
+ * @param variableName - 变量名(作为内部函数名的基础)
73
+ * @param allBindingNames - 所有绑定名称集合(用于避免命名冲突)
74
+ * @param builderAlias - builder 的本地别名
75
+ * @returns 提取结果,包含名称、函数声明节点和是否是 builder 包装
76
+ */
77
+ export function extractFunctionFromVariableInit(callExpr, variableName, allBindingNames, builderAlias) {
78
+ // 判断是否为 builder 函数调用
79
+ const isBuilder = isBuilderCall(callExpr, builderAlias);
80
+ // 遍历调用表达式的所有参数
81
+ for (let i = 0; i < callExpr.arguments.length; i++) {
82
+ const arg = callExpr.arguments[i];
83
+ // 找到作为参数传入的匿名函数
84
+ if (arg.type === 'ArrowFunctionExpression' || arg.type === 'FunctionExpression') {
85
+ // 以变量名为基础生成唯一别名,避免与已有名称冲突
86
+ const name = generateUniqueAlias(variableName, allBindingNames);
87
+ // 将新名称加入集合,防止后续生成重复名称
88
+ allBindingNames.add(name);
89
+ // 转换为命名函数声明
90
+ const funcNode = convertToNamedFunctionDeclaration(arg, name);
91
+ // 将原参数替换为标识符引用
92
+ callExpr.arguments[i] = t.identifier(name);
93
+ return { name, funcNode, isBuilderWrapped: isBuilder };
94
+ }
95
+ }
96
+ // 未找到可提取的函数参数
97
+ return null;
98
+ }
@@ -6,3 +6,10 @@
6
6
  * @returns 唯一的别名
7
7
  */
8
8
  export declare function generateUniqueAlias(baseName: string, existingNames: Set<string>): string;
9
+ /**
10
+ * 生成唯一的默认导出组件名称
11
+ * 优先使用基础名称,若冲突则生成唯一别名
12
+ * @param existingNames - 已存在的名称集合
13
+ * @returns 唯一的默认导出名称
14
+ */
15
+ export declare function generateUniqueDefaultName(existingNames: Set<string>): string;
@@ -1,3 +1,9 @@
1
+ /**
2
+ * 名称生成工具模块
3
+ * 提供唯一别名和默认导出名称的生成功能
4
+ * @module utils/generate
5
+ */
6
+ import { DEFAULT_EXPORT_BASE_NAME } from '../constants/index.js';
1
7
  /**
2
8
  * 生成唯一的别名
3
9
  * 从 $1 开始递增,直到找到不冲突的名称
@@ -13,3 +19,17 @@ export function generateUniqueAlias(baseName, existingNames) {
13
19
  }
14
20
  return `${baseName}$${index}`;
15
21
  }
22
+ /**
23
+ * 生成唯一的默认导出组件名称
24
+ * 优先使用基础名称,若冲突则生成唯一别名
25
+ * @param existingNames - 已存在的名称集合
26
+ * @returns 唯一的默认导出名称
27
+ */
28
+ export function generateUniqueDefaultName(existingNames) {
29
+ // 如果默认名称未被占用,直接返回
30
+ if (!existingNames.has(DEFAULT_EXPORT_BASE_NAME)) {
31
+ return DEFAULT_EXPORT_BASE_NAME;
32
+ }
33
+ // 否则生成唯一别名
34
+ return generateUniqueAlias(DEFAULT_EXPORT_BASE_NAME, existingNames);
35
+ }
@@ -1,8 +1,9 @@
1
1
  export { isWhitespaceJSXText, isJSXComment } from './ast-guards.js';
2
- export { getJSXElementName, resolveJSXElementType, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren, validateNoDirectMatchChild } from './jsx-helpers.js';
2
+ export { getJSXElementName, resolveJSXElementType, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren } from './jsx-helpers.js';
3
3
  export { createUnrefCall, createAccessorCall, createExprCall, createBranchCall, createCreateViewCall, createWithDirectivesCall, createArrowFunction, createLocationObject, addPureComment, getAlias, getDevLocInfo } from './ast-builders.js';
4
4
  export { collectPatternBindings, collectObjectPatternBindings } from './pattern-helpers.js';
5
5
  export { createBranch, createBinaryBranch, buildNestedCondition, type BranchConfig } from './branch-factory.js';
6
6
  export { validateVIfChain, collectVIfChainInfo, collectFragmentVIfChains, type VIfChainInfo } from './vif-helpers.js';
7
7
  export { collectComponentFunctions, type ComponentInfo } from './component-collect.js';
8
- export { generateUniqueAlias } from './generate.js';
8
+ export { hasForwardedComponentDefaultExport } from './forward-export.js';
9
+ export { generateUniqueAlias, generateUniqueDefaultName } from './generate.js';
@@ -1,7 +1,7 @@
1
1
  // 类型守卫
2
2
  export { isWhitespaceJSXText, isJSXComment } from './ast-guards.js';
3
3
  // JSX 辅助函数
4
- export { getJSXElementName, resolveJSXElementType, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren, validateNoDirectMatchChild } from './jsx-helpers.js';
4
+ export { getJSXElementName, resolveJSXElementType, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren } from './jsx-helpers.js';
5
5
  // AST 构建函数
6
6
  export { createUnrefCall, createAccessorCall, createExprCall, createBranchCall, createCreateViewCall, createWithDirectivesCall, createArrowFunction, createLocationObject, addPureComment, getAlias, getDevLocInfo } from './ast-builders.js';
7
7
  // 模式处理辅助函数
@@ -12,5 +12,7 @@ export { createBranch, createBinaryBranch, buildNestedCondition } from './branch
12
12
  export { validateVIfChain, collectVIfChainInfo, collectFragmentVIfChains } from './vif-helpers.js';
13
13
  // 组件收集
14
14
  export { collectComponentFunctions } from './component-collect.js';
15
+ // 导入转发默认导出检测
16
+ export { hasForwardedComponentDefaultExport } from './forward-export.js';
15
17
  // 生成器
16
- export { generateUniqueAlias } from './generate.js';
18
+ export { generateUniqueAlias, generateUniqueDefaultName } from './generate.js';
@@ -101,8 +101,5 @@ export declare function hasEffectiveChildren(node: JSXElement): boolean;
101
101
  */
102
102
  export declare function filterEffectiveChildren(node: JSXElement): t.Node[];
103
103
  /**
104
- * 校验子节点中不能直接包含 Match 组件
105
- * Match 必须在 Switch 内使用,非 Switch 元素的子节点中不允许出现 Match
106
- * @param children - 子节点数组
104
+ * 过滤掉空白文本子节点
107
105
  */
108
- export declare function validateNoDirectMatchChild(children: t.Node[]): void;
@@ -4,9 +4,8 @@
4
4
  * @module utils/jsx-helpers
5
5
  */
6
6
  import * as t from '@babel/types';
7
- import { isJSXElement, isJSXText } from '@babel/types';
7
+ import { isJSXText } from '@babel/types';
8
8
  import { DIRECTIVE_PREFIX, PURE_COMPILE_COMPONENTS, V_IF_CHAIN_DIRECTIVES } from '../constants/index.js';
9
- import { createError } from '../error.js';
10
9
  import { isWhitespaceJSXText } from './ast-guards.js';
11
10
  /**
12
11
  * 获取 JSX 元素的名称
@@ -253,17 +252,5 @@ export function filterEffectiveChildren(node) {
253
252
  return node.children.filter(isEffectiveChild);
254
253
  }
255
254
  /**
256
- * 校验子节点中不能直接包含 Match 组件
257
- * Match 必须在 Switch 内使用,非 Switch 元素的子节点中不允许出现 Match
258
- * @param children - 子节点数组
255
+ * 过滤掉空白文本子节点
259
256
  */
260
- export function validateNoDirectMatchChild(children) {
261
- for (const child of children) {
262
- if (isJSXElement(child)) {
263
- const childName = getJSXElementName(child);
264
- if (childName === 'Match') {
265
- throw createError('E012', child);
266
- }
267
- }
268
- }
269
- }
@@ -125,7 +125,9 @@ export function collectFragmentVIfChains(children) {
125
125
  */
126
126
  function collectSingleChainFromFragment(children, startIndex) {
127
127
  const nodes = [children[startIndex]];
128
- const conditions = [getDirectiveValue(nodes[0], 'v-if')];
128
+ // v-if 指令值已由 validateVIfChain 保证存在,fallback 为 true(无表达式时)
129
+ const vIfValue = getDirectiveValue(nodes[0], 'v-if') ?? t.booleanLiteral(true);
130
+ const conditions = [vIfValue];
129
131
  let j = startIndex + 1;
130
132
  // 继续收集后续的 v-else-if 和 v-else
131
133
  while (j < children.length) {
@@ -147,9 +149,10 @@ function collectSingleChainFromFragment(children, startIndex) {
147
149
  if (!isVIfChain(nextChild))
148
150
  break;
149
151
  if (isVElseIf(nextChild)) {
150
- // 收集 v-else-if
152
+ // 收集 v-else-if,值已由 validateVIfChain 保证存在
151
153
  nodes.push(nextChild);
152
- conditions.push(getDirectiveValue(nextChild, 'v-else-if'));
154
+ const vElseIfValue = getDirectiveValue(nextChild, 'v-else-if') ?? t.booleanLiteral(true);
155
+ conditions.push(vElseIfValue);
153
156
  j++;
154
157
  }
155
158
  else if (isVElse(nextChild)) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vitarx/plugin-vite",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "The official plugin for Vitarx support in Vite.",
5
5
  "author": "ZhuChongLin <8210856@qq.com>",
6
6
  "license": "MIT",
@@ -47,7 +47,6 @@
47
47
  "acorn-walk": "^8.3.5"
48
48
  },
49
49
  "devDependencies": {
50
- "typescript": "^6.0.3",
51
50
  "@commitlint/cli": "^20.4.2",
52
51
  "@commitlint/config-conventional": "^20.4.2",
53
52
  "@commitlint/types": "^20.4.0",
@@ -55,13 +54,15 @@
55
54
  "@types/babel__generator": "^7.27.0",
56
55
  "@types/babel__traverse": "^7.28.0",
57
56
  "@types/node": "^25.3.2",
57
+ "happy-dom": "^20.14.5",
58
58
  "husky": "^9.1.7",
59
59
  "node": "^25.7.0",
60
60
  "prettier": "^3.8.1",
61
61
  "rimraf": "^6.1.3",
62
- "vitest": "^4.0.18",
62
+ "typescript": "^6.0.3",
63
+ "vitarx": "^4.0.6",
63
64
  "vite": "^8.0.5",
64
- "vitarx": "^4.0.0"
65
+ "vitest": "^4.0.18"
65
66
  },
66
67
  "scripts": {
67
68
  "build": "vitest run && rimraf dist && tsc -p tsconfig.build.json",
@@ -1,23 +0,0 @@
1
- /**
2
- * 代码变更类型
3
- */
4
- export interface ChangeCode {
5
- /** UI 描述代码是否变化(需要重新构建视图) */
6
- build: boolean;
7
- /** 非 UI 代码是否变化(需要完全重新挂载) */
8
- logic: boolean;
9
- }
10
- /**
11
- * 判断两个函数组件的代码差异
12
- *
13
- * 分析策略:
14
- * 1. 解析函数代码,提取所有 createView/branch/expr 等 UI API 调用
15
- * 2. 将这些调用识别为 UI 描述代码
16
- * 3. 其余代码识别为非 UI 代码(逻辑代码)
17
- * 4. 分别比较 UI 代码和非 UI 代码是否变化
18
- *
19
- * @param newCode 新函数代码
20
- * @param oldCode 旧函数代码
21
- * @returns {ChangeCode} 变更检测结果
22
- */
23
- export declare function diffComponentChange(newCode: string, oldCode: string): ChangeCode;
@@ -1,137 +0,0 @@
1
- /**
2
- * HMR 代码变更检测工具
3
- * 用于判断组件代码中 UI 描述部分和非 UI 部分的变更
4
- * @module hmr-client/utils
5
- */
6
- import { parse } from 'acorn';
7
- import { simple as walkSimple } from 'acorn-walk';
8
- import { UI_API_NAMES } from '../constants/index.js';
9
- /**
10
- * UI 相关的运行时 API 名称
11
- * 这些 API 调用代表 UI 描述代码
12
- */
13
- const UI_APIS = new Set([...UI_API_NAMES, 'jsxDEV', 'h']);
14
- /**
15
- * 判断标识符名称是否为 UI API
16
- * 支持原始名称和带后缀的名称(如 jsxDEV$1、branch$2、expr$1)
17
- */
18
- function isUIApi(name) {
19
- if (UI_APIS.has(name))
20
- return true;
21
- for (const api of UI_APIS) {
22
- if (name.startsWith(api + '$'))
23
- return true;
24
- }
25
- return false;
26
- }
27
- /**
28
- * 从函数代码中分离 UI 代码和非 UI 代码
29
- * @param functionCode 完整函数代码
30
- * @returns 包含逻辑代码和渲染代码的对象
31
- */
32
- function separateLogicAndRender(functionCode) {
33
- const uiNodes = [];
34
- // 包装代码以便解析
35
- const wrappedCode = `(${functionCode})`;
36
- try {
37
- const ast = parse(wrappedCode, {
38
- ecmaVersion: 'latest',
39
- sourceType: 'module',
40
- allowReturnOutsideFunction: true,
41
- allowAwaitOutsideFunction: true,
42
- allowImportExportEverywhere: true,
43
- allowHashBang: true,
44
- allowReserved: true
45
- });
46
- // 遍历 AST 收集所有 UI API 调用节点
47
- walkSimple(ast, {
48
- CallExpression(node) {
49
- const callee = node.callee;
50
- // 直接调用:createView(...)
51
- if (callee.type === 'Identifier' && isUIApi(callee.name)) {
52
- uiNodes.push({ start: node.start, end: node.end });
53
- return;
54
- }
55
- // 成员调用:exports.createView(...)
56
- if (callee.type === 'MemberExpression' &&
57
- callee.property.type === 'Identifier' &&
58
- isUIApi(callee.property.name)) {
59
- uiNodes.push({ start: node.start, end: node.end });
60
- }
61
- }
62
- });
63
- }
64
- catch {
65
- // 解析失败时,返回原始代码作为逻辑代码
66
- return {
67
- logicCode: functionCode.trim(),
68
- renderCode: ''
69
- };
70
- }
71
- // 按位置排序,从后向前替换以保持索引正确
72
- uiNodes.sort((a, b) => a.start - b.start);
73
- const filteredNodes = [];
74
- for (const node of uiNodes) {
75
- const isContained = filteredNodes.some(parent => node.start >= parent.start && node.end <= parent.end);
76
- if (!isContained) {
77
- filteredNodes.push(node);
78
- }
79
- }
80
- filteredNodes.sort((a, b) => b.start - a.start);
81
- // 提取 UI 代码
82
- const renderCode = filteredNodes
83
- .map(n => wrappedCode.slice(n.start, n.end))
84
- .reverse()
85
- .join('\n');
86
- // 移除 UI 代码后的逻辑代码
87
- let logicCode = wrappedCode;
88
- for (const node of filteredNodes) {
89
- logicCode = logicCode.slice(0, node.start) + logicCode.slice(node.end);
90
- }
91
- return {
92
- logicCode: logicCode.trim(),
93
- renderCode
94
- };
95
- }
96
- /**
97
- * 规范化代码字符串
98
- * 移除多余空白、注释等,用于比较
99
- */
100
- function normalizeCode(code) {
101
- // 移除多行注释
102
- let result = code.replace(/\/\*[\s\S]*?\*\//g, '');
103
- // 移除单行注释
104
- result = result.replace(/\/\/.*$/gm, '');
105
- // 移除多余空白
106
- result = result.replace(/\s+/g, ' ');
107
- // 移除首尾空白
108
- return result.trim();
109
- }
110
- /**
111
- * 判断两个函数组件的代码差异
112
- *
113
- * 分析策略:
114
- * 1. 解析函数代码,提取所有 createView/branch/expr 等 UI API 调用
115
- * 2. 将这些调用识别为 UI 描述代码
116
- * 3. 其余代码识别为非 UI 代码(逻辑代码)
117
- * 4. 分别比较 UI 代码和非 UI 代码是否变化
118
- *
119
- * @param newCode 新函数代码
120
- * @param oldCode 旧函数代码
121
- * @returns {ChangeCode} 变更检测结果
122
- */
123
- export function diffComponentChange(newCode, oldCode) {
124
- const { renderCode: newRenderCode, logicCode: newLogicCode } = separateLogicAndRender(newCode);
125
- const { renderCode: oldRenderCode, logicCode: oldLogicCode } = separateLogicAndRender(oldCode);
126
- // 规范化后比较,避免格式差异导致误判
127
- const normalizedNewLogic = normalizeCode(newLogicCode);
128
- const normalizedOldLogic = normalizeCode(oldLogicCode);
129
- const normalizedNewRender = normalizeCode(newRenderCode);
130
- const normalizedOldRender = normalizeCode(oldRenderCode);
131
- return {
132
- // UI 代码变化:需要重新构建视图
133
- build: normalizedNewRender !== normalizedOldRender,
134
- // 非 UI 代码变化:需要完全重新挂载组件
135
- logic: normalizedNewLogic !== normalizedOldLogic
136
- };
137
- }