@vitarx/plugin-vite 0.0.1-beta.7 → 1.0.0-beta.0

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 (41) hide show
  1. package/README.md +64 -4
  2. package/dist/constants/index.d.ts +1 -11
  3. package/dist/constants/index.js +1 -11
  4. package/dist/context.d.ts +14 -10
  5. package/dist/context.js +7 -5
  6. package/dist/hmr-client/index.d.ts +2 -1
  7. package/dist/hmr-client/index.js +3 -2
  8. package/dist/hmr-client/utils.d.ts +1 -1
  9. package/dist/hmr-client/utils.js +7 -4
  10. package/dist/index.js +10 -4
  11. package/dist/passes/components/ifBlock.js +2 -4
  12. package/dist/passes/components/switch.js +2 -4
  13. package/dist/passes/directives/processDirectives.js +1 -1
  14. package/dist/passes/directives/vIf.js +2 -5
  15. package/dist/passes/hmr/inject-imports.d.ts +19 -0
  16. package/dist/passes/hmr/inject-imports.js +26 -0
  17. package/dist/passes/hmr/inject-state.d.ts +29 -0
  18. package/dist/passes/hmr/inject-state.js +101 -0
  19. package/dist/passes/hmr/inject.d.ts +1 -1
  20. package/dist/passes/hmr/inject.js +13 -123
  21. package/dist/passes/imports/injectImports.js +2 -2
  22. package/dist/passes/jsx/processChildren.js +30 -51
  23. package/dist/passes/jsx/processJSXElement.js +3 -6
  24. package/dist/passes/jsx/processJSXFragment.js +4 -4
  25. package/dist/passes/props/index.js +1 -8
  26. package/dist/transform.d.ts +5 -8
  27. package/dist/transform.js +19 -20
  28. package/dist/types.d.ts +11 -0
  29. package/dist/utils/ast-builders.d.ts +18 -18
  30. package/dist/utils/ast-builders.js +27 -41
  31. package/dist/utils/branch-factory.d.ts +1 -4
  32. package/dist/utils/branch-factory.js +4 -7
  33. package/dist/utils/collect-names.d.ts +37 -0
  34. package/dist/utils/collect-names.js +129 -0
  35. package/dist/utils/component-collect.d.ts +9 -4
  36. package/dist/utils/component-collect.js +64 -74
  37. package/dist/utils/index.d.ts +2 -2
  38. package/dist/utils/index.js +2 -2
  39. package/dist/utils/jsx-helpers.d.ts +3 -2
  40. package/dist/utils/jsx-helpers.js +3 -2
  41. package/package.json +11 -4
package/README.md CHANGED
@@ -222,6 +222,30 @@ import.meta.hot.accept(mod => {
222
222
  2. **被导出** - 使用 `export` 导出
223
223
  3. **包含 JSX** - 函数体内包含 JSX 语法或返回编译宏组件
224
224
 
225
+ ### HMR 代码分离
226
+
227
+ HMR 在检测到组件更新时,会将组件代码分离为 **UI 代码** 和 **逻辑代码** 两部分:
228
+
229
+ - **UI 代码**:`createView`、`branch`、`expr`、`accessor`、`dynamic`、`withDirectives` 等调用
230
+ - **逻辑代码**:其余所有代码
231
+
232
+ 仅逻辑代码变化时,组件完全重新挂载;仅 UI 代码变化时,仅重建视图树。
233
+
234
+ ### 开发模式位置信息
235
+
236
+ 开发模式下,`createView` 调用会注入位置信息参数(用于调试),`branch`、`expr`、`accessor` 不注入:
237
+
238
+ ```javascript
239
+ // dev 模式
240
+ createView("div", { children: "hello" }, { fileName: "App.tsx", lineNumber: 5, columnNumber: 10 })
241
+ expr(() => a && b) // 无位置信息
242
+ branch(() => unref(show) ? 0 : 1) // 无位置信息
243
+ accessor(props, "value") // 无位置信息
244
+
245
+ // 生产模式
246
+ createView("div", { children: "hello" }) // 无位置信息
247
+ ```
248
+
225
249
  ## 子元素处理
226
250
 
227
251
  ### 响应式子元素
@@ -230,19 +254,55 @@ import.meta.hot.accept(mod => {
230
254
  // 标识符保持原样
231
255
  <div>{value}</div>
232
256
 
233
- // 成员表达式使用 access
257
+ // 成员表达式使用 accessor
234
258
  <div>{props.value}</div>
235
- // 编译为: access(props, 'value')
259
+ // 编译为: accessor(props, 'value')
236
260
 
237
261
  // 条件表达式使用 branch
238
262
  <div>{show ? 'yes' : 'no'}</div>
239
263
  // 编译为: branch(() => unref(show) ? 0 : 1, [...])
240
264
 
241
- // 逻辑表达式使用 dynamic
265
+ // 逻辑表达式使用 expr
242
266
  <div>{a && b}</div>
243
- // 编译为: dynamic(() => a && b)
267
+ // 编译为: expr(() => a && b)
268
+
269
+ // 二元表达式使用 expr
270
+ <div>{count + 1}</div>
271
+ // 编译为: expr(() => count + 1)
272
+
273
+ // 函数调用使用 expr
274
+ <div>{render()}</div>
275
+ // 编译为: expr(() => render())
244
276
  ```
245
277
 
278
+ ### expr 与数组返回值
279
+
280
+ `expr` 底层由 `DynamicView` 渲染。当 `expr` 中的表达式返回数组时(如 `arr.map()`),运行时会自动检测并原样返回数组(仅渲染一次,不跟踪响应式)。推荐使用 `For` 组件实现动态数组的响应式渲染。
281
+
282
+ ```jsx
283
+ // arr.map() 返回数组,expr 运行时原样返回(仅渲染一次)
284
+ <div>{items.map(item => <span>{item}</span>)}</div>
285
+ // 编译为: expr(() => items.map(item => createView("span", { children: item })))
286
+
287
+ // 推荐:使用 For 组件实现响应式数组渲染
288
+ <div>
289
+ <For each={items} key="id">{item => <span>{item}</span>}</For>
290
+ </div>
291
+ ```
292
+
293
+ ### 运行时 API 说明
294
+
295
+ | API | 用途 | 编译器注入 | HMR 识别为 UI 代码 |
296
+ |------------------|---------|-----------|---------------|
297
+ | `createView` | 创建视图节点 | ✅ | ✅ |
298
+ | `branch` | 条件分支渲染 | ✅ | ✅ |
299
+ | `expr` | 动态表达式包装 | ✅ | ✅ |
300
+ | `accessor` | 属性访问追踪 | ✅ | ✅ |
301
+ | `dynamic` | 动态值包装 | ❌(用户手动使用) | ✅ |
302
+ | `withDirectives` | 指令绑定 | ✅ | ✅ |
303
+
304
+ > **注意:** `expr` 是编译器插桩注入的辅助函数,用于包装逻辑/二元/调用表达式;`dynamic` 是运行时独立 API,供用户手动使用。两者在 HMR 代码分离时均被视为 UI 描述代码。
305
+
246
306
  ## API 参考
247
307
 
248
308
  ### 插件选项
@@ -2,21 +2,11 @@
2
2
  * Vitarx 运行时 API 名称
3
3
  * 这些 API 需要从 vitarx 包导入
4
4
  */
5
- export declare const VITARX_APIS: {
6
- readonly CREATE_VIEW: "createView";
7
- readonly FRAGMENT: "Fragment";
8
- readonly BRANCH: "branch";
9
- readonly DYNAMIC: "dynamic";
10
- readonly ACCESS: "access";
11
- readonly WITH_DIRECTIVES: "withDirectives";
12
- readonly UNREF: "unref";
13
- readonly IS_REF: "isRef";
14
- };
15
5
  /**
16
6
  * UI 相关的 API 名称列表
17
7
  * 这些 API 用于创建视图,在 HMR 时需要识别
18
8
  */
19
- export declare const UI_API_NAMES: readonly ["createView", "branch", "dynamic", "access", "withDirectives"];
9
+ export declare const UI_API_NAMES: readonly ["createView", "branch", "dynamic", "expr", "accessor", "withDirectives"];
20
10
  /**
21
11
  * Ref 相关 API 名称
22
12
  * 用于识别 ref 变量
@@ -2,21 +2,11 @@
2
2
  * Vitarx 运行时 API 名称
3
3
  * 这些 API 需要从 vitarx 包导入
4
4
  */
5
- export const VITARX_APIS = {
6
- CREATE_VIEW: 'createView',
7
- FRAGMENT: 'Fragment',
8
- BRANCH: 'branch',
9
- DYNAMIC: 'dynamic',
10
- ACCESS: 'access',
11
- WITH_DIRECTIVES: 'withDirectives',
12
- UNREF: 'unref',
13
- IS_REF: 'isRef'
14
- };
15
5
  /**
16
6
  * UI 相关的 API 名称列表
17
7
  * 这些 API 用于创建视图,在 HMR 时需要识别
18
8
  */
19
- export const UI_API_NAMES = ['createView', 'branch', 'dynamic', 'access', 'withDirectives'];
9
+ export const UI_API_NAMES = ['createView', 'branch', 'dynamic', 'expr', 'accessor', 'withDirectives'];
20
10
  /**
21
11
  * Ref 相关 API 名称
22
12
  * 用于识别 ref 变量
package/dist/context.d.ts CHANGED
@@ -3,9 +3,9 @@
3
3
  * 定义编译转换过程中的上下文数据结构
4
4
  * @module context
5
5
  */
6
- import type { File } from '@babel/types';
6
+ import type { CallExpression, File } from '@babel/types';
7
7
  import type { CompilerWarning } from './error.js';
8
- import type { CompileOptions } from './transform.js';
8
+ import type { CompileOptions } from './types.js';
9
9
  /**
10
10
  * 导入信息
11
11
  * 追踪需要注入的运行时 API
@@ -17,10 +17,10 @@ export interface ImportInfo {
17
17
  Fragment: boolean;
18
18
  /** branch - 条件分支函数 */
19
19
  branch: boolean;
20
- /** dynamic - 动态值包装函数 */
21
- dynamic: boolean;
22
- /** access - 属性访问函数 */
23
- access: boolean;
20
+ /** expr - 动态表达式包装函数 */
21
+ expr: boolean;
22
+ /** accessor - 属性访问函数 */
23
+ accessor: boolean;
24
24
  /** withDirectives - 指令包装函数 */
25
25
  withDirectives: boolean;
26
26
  /** unref - ref 解包函数 */
@@ -36,8 +36,8 @@ export interface VitarxImportAliases {
36
36
  createView: string | null;
37
37
  Fragment: string | null;
38
38
  branch: string | null;
39
- dynamic: string | null;
40
- access: string | null;
39
+ expr: string | null;
40
+ accessor: string | null;
41
41
  withDirectives: string | null;
42
42
  unref: string | null;
43
43
  isRef: string | null;
@@ -78,8 +78,12 @@ export interface TransformContext {
78
78
  refApiAliases: RefApiAliases;
79
79
  /** ref 变量名集合 */
80
80
  refVariables: Set<string>;
81
- /** UI API 别名集合(用于 HMR 代码分离识别) */
82
- uiApiAliases: Set<string>;
81
+ /** builder 函数的本地别名(用于识别纯构建组件) */
82
+ builderAlias: string | null;
83
+ /** 已处理的 AST 节点追踪集合 */
84
+ processedNodes: WeakSet<any>;
85
+ /** 已添加 PURE 注释的节点追踪集合 */
86
+ pureCommentedNodes: WeakSet<CallExpression>;
83
87
  /** 编译警告列表 */
84
88
  warnings: CompilerWarning[];
85
89
  }
package/dist/context.js CHANGED
@@ -18,8 +18,8 @@ export function createContext(code, id, options, ast) {
18
18
  createView: false,
19
19
  Fragment: false,
20
20
  branch: false,
21
- dynamic: false,
22
- access: false,
21
+ expr: false,
22
+ accessor: false,
23
23
  withDirectives: false,
24
24
  unref: false,
25
25
  isRef: false
@@ -29,8 +29,8 @@ export function createContext(code, id, options, ast) {
29
29
  createView: null,
30
30
  Fragment: null,
31
31
  branch: null,
32
- dynamic: null,
33
- access: null,
32
+ expr: null,
33
+ accessor: null,
34
34
  withDirectives: null,
35
35
  unref: null,
36
36
  isRef: null
@@ -43,7 +43,9 @@ export function createContext(code, id, options, ast) {
43
43
  computed: null
44
44
  },
45
45
  refVariables: new Set(),
46
- uiApiAliases: new Set(),
46
+ builderAlias: null,
47
+ processedNodes: new WeakSet(),
48
+ pureCommentedNodes: new WeakSet(),
47
49
  warnings: []
48
50
  };
49
51
  }
@@ -45,8 +45,9 @@ export default class HMRManager {
45
45
  * 注册节点
46
46
  *
47
47
  * @param view - 组件视图节点
48
+ * @param component - 组件构造函数
48
49
  */
49
- register(view: ComponentView): void;
50
+ register(view: ComponentView, component?: Component): void;
50
51
  /**
51
52
  * 获取模块id
52
53
  *
@@ -80,11 +80,12 @@ class HMRManager {
80
80
  * 注册节点
81
81
  *
82
82
  * @param view - 组件视图节点
83
+ * @param component - 组件构造函数
83
84
  */
84
- register(view) {
85
+ register(view, component) {
85
86
  if (!view)
86
87
  return;
87
- const component = view.component;
88
+ component ?? (component = view.component);
88
89
  const id = this.getId(component);
89
90
  if (__classPrivateFieldGet(this, _HMRManager_idMapToView, "f").has(id)) {
90
91
  __classPrivateFieldGet(this, _HMRManager_idMapToView, "f").get(id).add(view);
@@ -11,7 +11,7 @@ export interface ChangeCode {
11
11
  * 判断两个函数组件的代码差异
12
12
  *
13
13
  * 分析策略:
14
- * 1. 解析函数代码,提取所有 createView/branch/dynamic 等 UI API 调用
14
+ * 1. 解析函数代码,提取所有 createView/branch/expr 等 UI API 调用
15
15
  * 2. 将这些调用识别为 UI 描述代码
16
16
  * 3. 其余代码识别为非 UI 代码(逻辑代码)
17
17
  * 4. 分别比较 UI 代码和非 UI 代码是否变化
@@ -13,13 +13,16 @@ import { UI_API_NAMES } from '../constants/index.js';
13
13
  const UI_APIS = new Set([...UI_API_NAMES, 'jsxDEV', 'h']);
14
14
  /**
15
15
  * 判断标识符名称是否为 UI API
16
- * 支持原始名称和带后缀的名称(如 jsxDEV$1)
16
+ * 支持原始名称和带后缀的名称(如 jsxDEV$1、branch$2、expr$1)
17
17
  */
18
18
  function isUIApi(name) {
19
19
  if (UI_APIS.has(name))
20
20
  return true;
21
- // 支持带数字后缀的 createView
22
- return /^jsxDEV\$\d+$/.test(name) || /^createView\$\d+$/.test(name);
21
+ for (const api of UI_APIS) {
22
+ if (name.startsWith(api + '$'))
23
+ return true;
24
+ }
25
+ return false;
23
26
  }
24
27
  /**
25
28
  * 从函数代码中分离 UI 代码和非 UI 代码
@@ -100,7 +103,7 @@ function normalizeCode(code) {
100
103
  * 判断两个函数组件的代码差异
101
104
  *
102
105
  * 分析策略:
103
- * 1. 解析函数代码,提取所有 createView/branch/dynamic 等 UI API 调用
106
+ * 1. 解析函数代码,提取所有 createView/branch/expr 等 UI API 调用
104
107
  * 2. 将这些调用识别为 UI 描述代码
105
108
  * 3. 其余代码识别为非 UI 代码(逻辑代码)
106
109
  * 4. 分别比较 UI 代码和非 UI 代码是否变化
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { fileURLToPath } from 'node:url';
3
3
  import { version } from 'vite';
4
- import { transform } from './transform.js';
4
+ import { shouldTransform, transform } from './transform.js';
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
6
  const IS_V8 = version.startsWith('8');
7
7
  let viteTransform;
@@ -32,10 +32,10 @@ export default function vitarx(options) {
32
32
  let viteConfig;
33
33
  return {
34
34
  name: 'vite-plugin-vitarx',
35
+ enforce: 'pre',
35
36
  config(config, env) {
36
37
  isDEV = env.command === 'serve' && !env.isPreview;
37
- const configSSR = !!config.build?.ssr;
38
- isSSR = env.isSsrBuild === true || configSSR;
38
+ isSSR = env.isSsrBuild === true;
39
39
  return {
40
40
  define: {
41
41
  __VITARX_DEV__: JSON.stringify(isDEV),
@@ -52,7 +52,11 @@ export default function vitarx(options) {
52
52
  };
53
53
  },
54
54
  configResolved(config) {
55
- viteConfig = config;
55
+ // 根据最终的配置结果,确定是否是 SSR 模式
56
+ if (!isSSR) {
57
+ isSSR = !!config.build?.ssr;
58
+ config.define['__VITARX_SSR__'] = JSON.stringify(isSSR);
59
+ }
56
60
  compileOptions = {
57
61
  dev: isDEV,
58
62
  ssr: isSSR,
@@ -63,6 +67,8 @@ export default function vitarx(options) {
63
67
  };
64
68
  },
65
69
  async transform(code, id) {
70
+ if (!shouldTransform(id))
71
+ return null;
66
72
  const result = await transform(code, id, compileOptions);
67
73
  if (!result)
68
74
  return null;
@@ -1,6 +1,6 @@
1
1
  import { isJSXElement } from '@babel/types';
2
2
  import { createError } from '../../error.js';
3
- import { collectVIfChainInfo, createArrowFunction, createBranch, filterWhitespaceChildren, getDevLocInfo, hasDirective, removeVIfChainDirectives, validateVIfChain } from '../../utils/index.js';
3
+ import { collectVIfChainInfo, createArrowFunction, createBranch, filterWhitespaceChildren, hasDirective, removeVIfChainDirectives, validateVIfChain } from '../../utils/index.js';
4
4
  /**
5
5
  * 处理 IfBlock 组件
6
6
  */
@@ -15,9 +15,7 @@ export function processIfBlock(path, ctx) {
15
15
  const { nodes, conditions } = collectVIfChainInfo(jsxChildren);
16
16
  nodes.forEach(node => removeVIfChainDirectives(node));
17
17
  const branches = nodes.map(node => createArrowFunction(node));
18
- // 获取位置信息(仅开发环境)
19
- const locInfo = getDevLocInfo(ctx, path.node);
20
- const branchCall = createBranch({ conditions, branches, locInfo }, ctx);
18
+ const branchCall = createBranch({ conditions, branches }, ctx);
21
19
  if (path.node.loc) {
22
20
  branchCall.loc = path.node.loc;
23
21
  }
@@ -1,7 +1,7 @@
1
1
  import * as t from '@babel/types';
2
2
  import { isJSXElement, isJSXText } from '@babel/types';
3
3
  import { createError } from '../../error.js';
4
- import { createArrowFunction, createBranch, getDevLocInfo, getJSXAttributeByName, getJSXElementName } from '../../utils/index.js';
4
+ import { createArrowFunction, createBranch, getJSXAttributeByName, getJSXElementName } from '../../utils/index.js';
5
5
  /**
6
6
  * 处理 Switch 组件
7
7
  * @param path - JSX 元素路径
@@ -20,11 +20,9 @@ export function processSwitch(path, ctx) {
20
20
  conditions.push(t.booleanLiteral(true));
21
21
  branches.push(createArrowFunction(fallbackValue));
22
22
  }
23
- // 获取位置信息(仅开发环境)
24
- const locInfo = getDevLocInfo(ctx, path.node);
25
23
  // 生成 branch 调用
26
24
  // Switch 的 when 条件如果是标识符,需要 unref
27
- const branchCall = createBranch({ conditions, branches, locInfo }, ctx);
25
+ const branchCall = createBranch({ conditions, branches }, ctx);
28
26
  if (path.node.loc) {
29
27
  branchCall.loc = path.node.loc;
30
28
  }
@@ -27,7 +27,7 @@ export function processDirectives(viewCall, directives, ctx) {
27
27
  }
28
28
  markImport(ctx, 'withDirectives');
29
29
  const withDirectivesAlias = getAlias(ctx.vitarxAliases, 'withDirectives');
30
- return addPureComment(createWithDirectivesCall(viewCall, directiveArray, withDirectivesAlias));
30
+ return addPureComment(createWithDirectivesCall(viewCall, directiveArray, withDirectivesAlias), ctx);
31
31
  }
32
32
  /**
33
33
  * 构建指令值对象
@@ -1,6 +1,6 @@
1
1
  import * as t from '@babel/types';
2
2
  import { isJSXElement, isJSXText } from '@babel/types';
3
- import { collectFragmentVIfChains, createArrowFunction, createBranch, getDevLocInfo, isWhitespaceJSXText, removeVIfChainDirectives } from '../../utils/index.js';
3
+ import { collectFragmentVIfChains, createArrowFunction, createBranch, isWhitespaceJSXText, removeVIfChainDirectives } from '../../utils/index.js';
4
4
  /**
5
5
  * 处理 Fragment 中的 v-if 链
6
6
  * @param path - JSX Fragment 路径
@@ -42,11 +42,8 @@ function processVIfChainItem(chain, children, ctx, transformJSXElement) {
42
42
  branches.push(createArrowFunction(transformedNode));
43
43
  }
44
44
  }
45
- // 获取第一个节点的位置信息(仅开发环境)
46
- const firstNode = nodes[0];
47
- const locInfo = getDevLocInfo(ctx, firstNode);
48
45
  // 生成 branch 调用
49
- const branchCall = createBranch({ conditions, branches, locInfo }, ctx);
46
+ const branchCall = createBranch({ conditions, branches }, ctx);
50
47
  // 替换链的第一个节点,其他节点标记为 null
51
48
  if (start >= 0) {
52
49
  children[start] = branchCall;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * HMR 导入注入模块
3
+ * 负责注入 HMR 所需的导入语句
4
+ * @module passes/hmr/inject-imports
5
+ */
6
+ import * as t from '@babel/types';
7
+ /** getComponentView 的内部别名,避免与用户代码冲突 */
8
+ export declare const GET_COMPONENT_VIEW_ALIAS = "__$VITARX_GET_COMPONENT_VIEW$__";
9
+ /**
10
+ * 注入 HMR 客户端导入
11
+ * @param program - AST Program 节点
12
+ */
13
+ export declare function injectHMRImport(program: t.Program): void;
14
+ /**
15
+ * 注入 getComponentView 导入
16
+ * 使用唯一的别名避免与用户代码冲突
17
+ * @param program - AST Program 节点
18
+ */
19
+ export declare function injectGetComponentViewImport(program: t.Program): void;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * HMR 导入注入模块
3
+ * 负责注入 HMR 所需的导入语句
4
+ * @module passes/hmr/inject-imports
5
+ */
6
+ import * as t from '@babel/types';
7
+ import { HMR } from '../../constants/index.js';
8
+ /** getComponentView 的内部别名,避免与用户代码冲突 */
9
+ export const GET_COMPONENT_VIEW_ALIAS = '__$VITARX_GET_COMPONENT_VIEW$__';
10
+ /**
11
+ * 注入 HMR 客户端导入
12
+ * @param program - AST Program 节点
13
+ */
14
+ export function injectHMRImport(program) {
15
+ const importDecl = t.importDeclaration([t.importDefaultSpecifier(t.identifier(HMR.manager))], t.stringLiteral('@vitarx/vite-plugin/hmr-client'));
16
+ program.body.unshift(importDecl);
17
+ }
18
+ /**
19
+ * 注入 getComponentView 导入
20
+ * 使用唯一的别名避免与用户代码冲突
21
+ * @param program - AST Program 节点
22
+ */
23
+ export function injectGetComponentViewImport(program) {
24
+ const importDecl = t.importDeclaration([t.importSpecifier(t.identifier(GET_COMPONENT_VIEW_ALIAS), t.identifier('getComponentView'))], t.stringLiteral('vitarx'));
25
+ program.body.unshift(importDecl);
26
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * HMR 状态恢复模块
3
+ * 负责为组件函数注入状态保存和恢复代码
4
+ * @module passes/hmr/inject-state
5
+ */
6
+ import * as t from '@babel/types';
7
+ /**
8
+ * 创建组件函数体内的 HMR 注册代码
9
+ * @param variableNames - 需要追踪的变量名列表
10
+ * @returns HMR 注册语句数组
11
+ */
12
+ export declare function createHMRRegistrationStatements(variableNames: string[]): t.Statement[];
13
+ /**
14
+ * 创建包装组件的 HMR 注册代码
15
+ * 只注册节点,不保存状态,传入组件函数自身作为第二个参数
16
+ * @param componentName - 组件函数名称
17
+ * @returns HMR 注册语句数组
18
+ */
19
+ export declare function createWrappedComponentHMRStatements(componentName: string): t.Statement[];
20
+ /**
21
+ * 从函数体中收集局部变量名
22
+ * @param functionBody - 函数体语句块
23
+ * @returns 变量名数组
24
+ */
25
+ export declare function collectLocalVariableNames(functionBody: t.BlockStatement): string[];
26
+ /**
27
+ * 为函数体内的变量声明注入状态恢复代码
28
+ */
29
+ export declare function injectStatePreservation(functionBody: t.BlockStatement): void;
@@ -0,0 +1,101 @@
1
+ /**
2
+ * HMR 状态恢复模块
3
+ * 负责为组件函数注入状态保存和恢复代码
4
+ * @module passes/hmr/inject-state
5
+ */
6
+ import * as t from '@babel/types';
7
+ import { HMR } from '../../constants/index.js';
8
+ import { collectPatternBindings } from '../../utils/index.js';
9
+ import { GET_COMPONENT_VIEW_ALIAS } from './inject-imports.js';
10
+ /**
11
+ * 创建组件函数体内的 HMR 注册代码
12
+ * @param variableNames - 需要追踪的变量名列表
13
+ * @returns HMR 注册语句数组
14
+ */
15
+ export function createHMRRegistrationStatements(variableNames) {
16
+ const statements = [];
17
+ statements.push(t.variableDeclaration('const', [
18
+ t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), [t.booleanLiteral(true)]))
19
+ ]));
20
+ statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('register')), [t.identifier(HMR.view)])));
21
+ const stateProperties = variableNames.map(name => t.objectMethod('get', t.identifier(name), [], t.blockStatement([t.returnStatement(t.identifier(name))])));
22
+ statements.push(t.expressionStatement(t.logicalExpression('&&', t.identifier(HMR.view), t.callExpression(t.memberExpression(t.callExpression(t.memberExpression(t.identifier('Promise'), t.identifier('resolve')), []), t.identifier('then')), [
23
+ t.arrowFunctionExpression([], t.blockStatement([
24
+ t.expressionStatement(t.assignmentExpression('=', t.memberExpression(t.identifier(HMR.view), t.identifier(HMR.state)), t.objectExpression(stateProperties)))
25
+ ]))
26
+ ]))));
27
+ return statements;
28
+ }
29
+ /**
30
+ * 创建包装组件的 HMR 注册代码
31
+ * 只注册节点,不保存状态,传入组件函数自身作为第二个参数
32
+ * @param componentName - 组件函数名称
33
+ * @returns HMR 注册语句数组
34
+ */
35
+ export function createWrappedComponentHMRStatements(componentName) {
36
+ const statements = [];
37
+ statements.push(t.variableDeclaration('const', [
38
+ t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), [t.booleanLiteral(true)]))
39
+ ]));
40
+ statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('register')), [t.identifier(HMR.view), t.identifier(componentName)])));
41
+ return statements;
42
+ }
43
+ /**
44
+ * 从函数体中收集局部变量名
45
+ * @param functionBody - 函数体语句块
46
+ * @returns 变量名数组
47
+ */
48
+ export function collectLocalVariableNames(functionBody) {
49
+ const variableNames = new Set();
50
+ for (const stmt of functionBody.body) {
51
+ if (stmt.type === 'VariableDeclaration') {
52
+ for (const decl of stmt.declarations) {
53
+ if (decl.id.type !== 'VoidPattern') {
54
+ collectPatternBindings(decl.id, variableNames);
55
+ }
56
+ }
57
+ }
58
+ }
59
+ return Array.from(variableNames);
60
+ }
61
+ /**
62
+ * 判断表达式是否为函数类型
63
+ */
64
+ function isFunctionExpression(expr) {
65
+ if (!expr)
66
+ return false;
67
+ return (expr.type === 'ArrowFunctionExpression' ||
68
+ expr.type === 'FunctionExpression' ||
69
+ expr.type === 'ClassExpression');
70
+ }
71
+ /**
72
+ * 创建状态恢复表达式
73
+ * 格式:__$VITARX_HMR$__.instance.memo(__$VITARX_HMR_VIEW_NODE$__, '变量名') ?? 原始初始值
74
+ */
75
+ function createMemoExpression(variableName, originalInit) {
76
+ return t.logicalExpression('??', t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('memo')), [t.identifier(HMR.view), t.stringLiteral(variableName)]), originalInit);
77
+ }
78
+ /**
79
+ * 为单个变量声明注入状态恢复代码
80
+ */
81
+ function injectStatePreservationForDeclaration(decl) {
82
+ if (decl.id.type !== 'Identifier')
83
+ return;
84
+ if (!decl.init)
85
+ return;
86
+ if (isFunctionExpression(decl.init))
87
+ return;
88
+ decl.init = createMemoExpression(decl.id.name, decl.init);
89
+ }
90
+ /**
91
+ * 为函数体内的变量声明注入状态恢复代码
92
+ */
93
+ export function injectStatePreservation(functionBody) {
94
+ for (const stmt of functionBody.body) {
95
+ if (stmt.type === 'VariableDeclaration') {
96
+ for (const decl of stmt.declarations) {
97
+ injectStatePreservationForDeclaration(decl);
98
+ }
99
+ }
100
+ }
101
+ }
@@ -4,7 +4,7 @@
4
4
  * @module passes/hmr
5
5
  */
6
6
  import * as t from '@babel/types';
7
- import { type ComponentInfo } from '../../utils/index.js';
7
+ import type { ComponentInfo } from '../../utils/index.js';
8
8
  /**
9
9
  * 注入 HMR 支持
10
10
  * 主入口函数,为所有组件注入完整的 HMR 支持