@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.
- package/dist/constants/index.d.ts +0 -9
- package/dist/constants/index.js +0 -9
- package/dist/hmr-client/index.d.ts +27 -1
- package/dist/hmr-client/index.js +108 -12
- package/dist/hmr-client/update.d.ts +11 -2
- package/dist/hmr-client/update.js +25 -59
- package/dist/hmr-client/weak-refs.d.ts +37 -0
- package/dist/hmr-client/weak-refs.js +41 -0
- package/dist/passes/directives/processDirectives.d.ts +1 -1
- package/dist/passes/directives/processDirectives.js +15 -10
- package/dist/passes/hmr/index.d.ts +1 -1
- package/dist/passes/hmr/index.js +1 -1
- package/dist/passes/hmr/inject-state.d.ts +16 -10
- package/dist/passes/hmr/inject-state.js +141 -135
- package/dist/passes/hmr/inject.d.ts +10 -0
- package/dist/passes/hmr/inject.js +47 -10
- package/dist/passes/imports/collectRefVariables.d.ts +1 -0
- package/dist/passes/imports/collectRefVariables.js +52 -19
- package/dist/passes/index.d.ts +1 -1
- package/dist/passes/index.js +1 -1
- package/dist/passes/jsx/processJSXFragment.js +18 -1
- package/dist/passes/props/attribute.d.ts +3 -0
- package/dist/passes/props/attribute.js +13 -3
- package/dist/transform.js +16 -5
- package/dist/types.d.ts +1 -1
- package/dist/utils/branch-factory.js +26 -6
- package/dist/utils/component-collect.d.ts +7 -0
- package/dist/utils/component-collect.js +12 -236
- package/dist/utils/default-export.d.ts +38 -0
- package/dist/utils/default-export.js +119 -0
- package/dist/utils/forward-export.d.ts +28 -0
- package/dist/utils/forward-export.js +76 -0
- package/dist/utils/function-extract.d.ts +45 -0
- package/dist/utils/function-extract.js +98 -0
- package/dist/utils/generate.d.ts +7 -0
- package/dist/utils/generate.js +20 -0
- package/dist/utils/index.d.ts +3 -2
- package/dist/utils/index.js +4 -2
- package/dist/utils/jsx-helpers.d.ts +1 -4
- package/dist/utils/jsx-helpers.js +2 -15
- package/dist/utils/vif-helpers.js +6 -3
- package/package.json +5 -4
- package/dist/hmr-client/utils.d.ts +0 -23
- package/dist/hmr-client/utils.js +0 -137
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
7
|
import { HMR } from '../../constants/index.js';
|
|
8
|
-
import { collectPatternBindings } from '../../utils/index.js';
|
|
9
8
|
import { GET_COMPONENT_VIEW_ALIAS } from './inject-imports.js';
|
|
10
9
|
/**
|
|
11
10
|
* 创建组件函数体内的 HMR 注册代码
|
|
@@ -25,15 +24,13 @@ export function createHMRRegistrationStatements(variableNames) {
|
|
|
25
24
|
statements.push(t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('register')), [t.identifier(HMR.view)])));
|
|
26
25
|
// 构建状态对象的 getter 属性,延迟求值避免不必要的计算
|
|
27
26
|
const stateProperties = variableNames.map(name => t.objectMethod('get', t.identifier(name), [], t.blockStatement([t.returnStatement(t.identifier(name))])));
|
|
28
|
-
// 语句3: __$VITARX_HMR_VIEW_NODE$__ &&
|
|
29
|
-
// __$VITARX_HMR_VIEW_NODE$__
|
|
30
|
-
// }
|
|
31
|
-
//
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
]))
|
|
36
|
-
]))));
|
|
27
|
+
// 语句3: __$VITARX_HMR_VIEW_NODE$__ && __$VITARX_HMR$__.instance.trackState(
|
|
28
|
+
// __$VITARX_HMR_VIEW_NODE$__,
|
|
29
|
+
// { get 变量名() { return 变量名 } }
|
|
30
|
+
// )
|
|
31
|
+
// 惰性登记状态 getter:只在热更新时由管理器读取快照写入视图,
|
|
32
|
+
// 正常「卸载→重挂载」不会写入状态,memo 永远不命中,避免旧状态泄漏
|
|
33
|
+
statements.push(t.expressionStatement(t.logicalExpression('&&', t.identifier(HMR.view), t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('trackState')), [t.identifier(HMR.view), t.objectExpression(stateProperties)]))));
|
|
37
34
|
return statements;
|
|
38
35
|
}
|
|
39
36
|
/**
|
|
@@ -54,105 +51,10 @@ export function createWrappedComponentHMRStatements(componentName) {
|
|
|
54
51
|
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)])));
|
|
55
52
|
return statements;
|
|
56
53
|
}
|
|
57
|
-
/**
|
|
58
|
-
* 从函数体中收集局部变量名
|
|
59
|
-
* 递归遍历函数体中的所有变量声明,用于状态恢复
|
|
60
|
-
* @param functionBody - 函数体语句块
|
|
61
|
-
* @returns 变量名数组
|
|
62
|
-
*/
|
|
63
|
-
export function collectLocalVariableNames(functionBody) {
|
|
64
|
-
const variableNames = new Set();
|
|
65
|
-
collectFromBlock(functionBody, variableNames);
|
|
66
|
-
return Array.from(variableNames);
|
|
67
|
-
}
|
|
68
|
-
/**
|
|
69
|
-
* 从语句块中收集变量名
|
|
70
|
-
* @param block - 语句块节点
|
|
71
|
-
* @param variableNames - 存储变量名的集合
|
|
72
|
-
*/
|
|
73
|
-
function collectFromBlock(block, variableNames) {
|
|
74
|
-
// 遍历语句块中的每个语句
|
|
75
|
-
for (const stmt of block.body) {
|
|
76
|
-
collectFromStatement(stmt, variableNames);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* 从单个语句中收集变量名
|
|
81
|
-
* 支持多种语句类型:变量声明、条件语句、循环语句、switch、try-catch 等
|
|
82
|
-
* @param stmt - 语句节点
|
|
83
|
-
* @param variableNames - 存储变量名的集合
|
|
84
|
-
*/
|
|
85
|
-
function collectFromStatement(stmt, variableNames) {
|
|
86
|
-
// 处理变量声明语句:const/let/var
|
|
87
|
-
if (stmt.type === 'VariableDeclaration') {
|
|
88
|
-
for (const decl of stmt.declarations) {
|
|
89
|
-
// 跳过 void 模式(如 void 0)
|
|
90
|
-
if (decl.id.type !== 'VoidPattern') {
|
|
91
|
-
collectPatternBindings(decl.id, variableNames);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
else if (stmt.type === 'IfStatement') {
|
|
96
|
-
// 递归处理 if 语句的两个分支
|
|
97
|
-
if (stmt.consequent.type === 'BlockStatement') {
|
|
98
|
-
collectFromBlock(stmt.consequent, variableNames);
|
|
99
|
-
}
|
|
100
|
-
else {
|
|
101
|
-
collectFromStatement(stmt.consequent, variableNames);
|
|
102
|
-
}
|
|
103
|
-
if (stmt.alternate) {
|
|
104
|
-
if (stmt.alternate.type === 'BlockStatement') {
|
|
105
|
-
collectFromBlock(stmt.alternate, variableNames);
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
collectFromStatement(stmt.alternate, variableNames);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
else if (stmt.type === 'ForStatement' || stmt.type === 'WhileStatement' || stmt.type === 'DoWhileStatement') {
|
|
113
|
-
// 处理 for/while/do-while 循环
|
|
114
|
-
if (stmt.body.type === 'BlockStatement') {
|
|
115
|
-
collectFromBlock(stmt.body, variableNames);
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
collectFromStatement(stmt.body, variableNames);
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else if (stmt.type === 'ForInStatement' || stmt.type === 'ForOfStatement') {
|
|
122
|
-
// 处理 for...in/for...of 循环
|
|
123
|
-
if (stmt.body.type === 'BlockStatement') {
|
|
124
|
-
collectFromBlock(stmt.body, variableNames);
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
collectFromStatement(stmt.body, variableNames);
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
else if (stmt.type === 'BlockStatement') {
|
|
131
|
-
// 处理嵌套语句块
|
|
132
|
-
collectFromBlock(stmt, variableNames);
|
|
133
|
-
}
|
|
134
|
-
else if (stmt.type === 'SwitchStatement') {
|
|
135
|
-
// 处理 switch 语句,遍历所有 case
|
|
136
|
-
for (const c of stmt.cases) {
|
|
137
|
-
for (const s of c.consequent) {
|
|
138
|
-
collectFromStatement(s, variableNames);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
else if (stmt.type === 'TryStatement') {
|
|
143
|
-
// 处理 try-catch-finally
|
|
144
|
-
collectFromBlock(stmt.block, variableNames);
|
|
145
|
-
if (stmt.handler) {
|
|
146
|
-
collectFromBlock(stmt.handler.body, variableNames);
|
|
147
|
-
}
|
|
148
|
-
if (stmt.finalizer) {
|
|
149
|
-
collectFromBlock(stmt.finalizer, variableNames);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
54
|
/**
|
|
154
55
|
* 判断表达式是否为函数类型
|
|
155
|
-
*
|
|
56
|
+
* 函数类型不需要状态恢复(函数定义本身不会改变);且快照读取 getter 会
|
|
57
|
+
* 拿到函数值,一旦被误调用将执行组件内的业务函数
|
|
156
58
|
* @param expr - 表达式节点
|
|
157
59
|
* @returns 是否为函数类型
|
|
158
60
|
*/
|
|
@@ -164,48 +66,152 @@ function isFunctionExpression(expr) {
|
|
|
164
66
|
expr.type === 'ClassExpression' // 类表达式
|
|
165
67
|
);
|
|
166
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* 判断是否为 useModel 调用
|
|
71
|
+
*
|
|
72
|
+
* useModel 返回的 ModelRef 是父组件状态的双向桥接(派生视图)而非独立
|
|
73
|
+
* 状态:其内部以 flush:'sync' 的 watch 同步 props→镜像,该 watch 绑定在
|
|
74
|
+
* 执行时的作用域上。若被 memo 复用,旧 watch 会随旧作用域销毁,表现为
|
|
75
|
+
* props→镜像同步断裂(v-model 显示残留)。因此每次执行都重建 ModelRef
|
|
76
|
+
* ——镜像从当前 props 重新初始化,语义正确。
|
|
77
|
+
* @param expr - 表达式节点
|
|
78
|
+
* @returns 是否为 useModel 调用
|
|
79
|
+
*/
|
|
80
|
+
function isUseModelCall(expr) {
|
|
81
|
+
return (!!expr &&
|
|
82
|
+
expr.type === 'CallExpression' &&
|
|
83
|
+
expr.callee.type === 'Identifier' &&
|
|
84
|
+
expr.callee.name === 'useModel');
|
|
85
|
+
}
|
|
167
86
|
/**
|
|
168
87
|
* 创建状态恢复表达式
|
|
169
|
-
* 格式:__$VITARX_HMR$__.instance.memo(
|
|
170
|
-
* 如果 memo 方法返回保存的状态则使用,否则使用原始初始值
|
|
88
|
+
* 格式:__$VITARX_HMR$__.instance.memo(view, '变量名') ?? 原始初始值
|
|
171
89
|
* @param variableName - 变量名
|
|
172
|
-
* @param originalInit -
|
|
90
|
+
* @param originalInit - 原始初始值表达式
|
|
91
|
+
* @param raw - 是否绕过响应式门禁(memoRaw):解构声明的隐藏变量保存的是
|
|
92
|
+
* composable 返回的普通对象(内含 Ref 与函数),memo 的门禁会拒收,
|
|
93
|
+
* 必须走无条件恢复
|
|
173
94
|
* @returns 状态恢复表达式
|
|
174
95
|
*/
|
|
175
|
-
function createMemoExpression(variableName, originalInit) {
|
|
96
|
+
function createMemoExpression(variableName, originalInit, raw = false) {
|
|
176
97
|
return t.logicalExpression('??', // 使用空值合并运算符
|
|
177
|
-
t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('memo')), [t.identifier(HMR.view), t.stringLiteral(variableName)]), originalInit // 原始初始值作为 fallback
|
|
98
|
+
t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier(raw ? 'memoRaw' : 'memo')), [t.identifier(HMR.view), t.stringLiteral(variableName)]), originalInit // 原始初始值作为 fallback
|
|
178
99
|
);
|
|
179
100
|
}
|
|
180
101
|
/**
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
// 替换初始值为状态恢复表达式
|
|
196
|
-
decl.init = createMemoExpression(decl.id.name, decl.init);
|
|
197
|
-
}
|
|
198
|
-
/**
|
|
199
|
-
* 为函数体内的变量声明注入状态恢复代码
|
|
200
|
-
* 遍历函数体中的所有变量声明语句
|
|
102
|
+
* 为函数体注入状态恢复代码并收集需要登记(trackState)的变量名
|
|
103
|
+
*
|
|
104
|
+
* 处理规则:
|
|
105
|
+
* - 标识符声明(非函数初始值):初始值包装为 memo(view, '变量名') ?? 原始初始值
|
|
106
|
+
* - useModel 调用:豁免(桥接状态每次重建,见 isUseModelCall)
|
|
107
|
+
* - 解构声明(如 `const { a } = useFoo()`):拆分为隐藏变量整包 memo +
|
|
108
|
+
* 原声明解构隐藏变量——composable 返回的状态对象得以跨重执行保留,
|
|
109
|
+
* 其内部创建的响应式状态与已挂载的副作用保持一致
|
|
110
|
+
* - 函数类型初始值 / 无初始值:不注入
|
|
111
|
+
* - for 循环头部声明:循环局部绑定,不注入(仅递归循环体)
|
|
112
|
+
*
|
|
113
|
+
* 单趟完成注入与收集,返回按执行顺序排列的登记名列表(标识符名 +
|
|
114
|
+
* 隐藏变量名),供 trackState 生成 getter。隐藏变量采用用户标识符不可能
|
|
115
|
+
* 命中的 `__$VITARX_D<N>$__` 命名,按组件函数内确定性计数。
|
|
201
116
|
* @param functionBody - 函数体语句块
|
|
117
|
+
* @returns 需要状态登记的变量名数组
|
|
202
118
|
*/
|
|
203
119
|
export function injectStatePreservation(functionBody) {
|
|
204
|
-
|
|
120
|
+
const trackedNames = [];
|
|
121
|
+
let destructureCounter = 0;
|
|
122
|
+
processBlock(functionBody);
|
|
123
|
+
return trackedNames;
|
|
124
|
+
/** 处理语句块:注入可能把一条声明拆成多条,需重建语句数组 */
|
|
125
|
+
function processBlock(block) {
|
|
126
|
+
const newBody = [];
|
|
127
|
+
for (const stmt of block.body) {
|
|
128
|
+
newBody.push(...processStatement(stmt));
|
|
129
|
+
}
|
|
130
|
+
block.body = newBody;
|
|
131
|
+
}
|
|
132
|
+
/** 处理单条语句,返回替换它的语句列表(1~n 条) */
|
|
133
|
+
function processStatement(stmt) {
|
|
205
134
|
if (stmt.type === 'VariableDeclaration') {
|
|
206
|
-
|
|
207
|
-
|
|
135
|
+
return processVariableDeclaration(stmt);
|
|
136
|
+
}
|
|
137
|
+
if (stmt.type === 'IfStatement') {
|
|
138
|
+
stmt.consequent = wrapBody(stmt.consequent);
|
|
139
|
+
if (stmt.alternate)
|
|
140
|
+
stmt.alternate = wrapBody(stmt.alternate);
|
|
141
|
+
}
|
|
142
|
+
else if (stmt.type === 'ForStatement' ||
|
|
143
|
+
stmt.type === 'WhileStatement' ||
|
|
144
|
+
stmt.type === 'DoWhileStatement') {
|
|
145
|
+
// for 头部声明是循环局部绑定不注入;仅递归循环体
|
|
146
|
+
stmt.body = wrapBody(stmt.body);
|
|
147
|
+
}
|
|
148
|
+
else if (stmt.type === 'ForInStatement' || stmt.type === 'ForOfStatement') {
|
|
149
|
+
// 同上:for-of/for-in 的 left 绑定按迭代取值,不做状态保留
|
|
150
|
+
stmt.body = wrapBody(stmt.body);
|
|
151
|
+
}
|
|
152
|
+
else if (stmt.type === 'BlockStatement') {
|
|
153
|
+
processBlock(stmt);
|
|
154
|
+
}
|
|
155
|
+
else if (stmt.type === 'SwitchStatement') {
|
|
156
|
+
for (const switchCase of stmt.cases) {
|
|
157
|
+
switchCase.consequent = processStatementList(switchCase.consequent);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (stmt.type === 'TryStatement') {
|
|
161
|
+
processBlock(stmt.block);
|
|
162
|
+
if (stmt.handler)
|
|
163
|
+
processBlock(stmt.handler.body);
|
|
164
|
+
if (stmt.finalizer)
|
|
165
|
+
processBlock(stmt.finalizer);
|
|
166
|
+
}
|
|
167
|
+
return [stmt];
|
|
168
|
+
}
|
|
169
|
+
function processStatementList(list) {
|
|
170
|
+
const results = [];
|
|
171
|
+
for (const stmt of list) {
|
|
172
|
+
results.push(...processStatement(stmt));
|
|
173
|
+
}
|
|
174
|
+
return results;
|
|
175
|
+
}
|
|
176
|
+
/** 语句体若因注入产生多条语句,需要块化包装保持语法合法 */
|
|
177
|
+
function wrapBody(body) {
|
|
178
|
+
if (body.type === 'BlockStatement') {
|
|
179
|
+
processBlock(body);
|
|
180
|
+
return body;
|
|
181
|
+
}
|
|
182
|
+
const results = processStatement(body);
|
|
183
|
+
return results.length === 1 ? results[0] : t.blockStatement(results);
|
|
184
|
+
}
|
|
185
|
+
/** 逐 declarator 处理变量声明(多 declarator 时拆分为多条声明) */
|
|
186
|
+
function processVariableDeclaration(stmt) {
|
|
187
|
+
const results = [];
|
|
188
|
+
for (const decl of stmt.declarations) {
|
|
189
|
+
const init = decl.init ?? null;
|
|
190
|
+
// 无初始值 / VoidPattern / 函数初始值:原样保留
|
|
191
|
+
if (decl.id.type === 'VoidPattern' || !init || isFunctionExpression(init)) {
|
|
192
|
+
results.push(t.variableDeclaration(stmt.kind, [decl]));
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
if (decl.id.type === 'Identifier') {
|
|
196
|
+
if (isUseModelCall(init)) {
|
|
197
|
+
// 桥接状态豁免:不 memo、不登记(见 isUseModelCall 注释)
|
|
198
|
+
results.push(t.variableDeclaration(stmt.kind, [decl]));
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
trackedNames.push(decl.id.name);
|
|
202
|
+
decl.init = createMemoExpression(decl.id.name, init);
|
|
203
|
+
results.push(t.variableDeclaration(stmt.kind, [decl]));
|
|
204
|
+
continue;
|
|
208
205
|
}
|
|
206
|
+
// 解构声明:隐藏变量整包 memo(raw 恢复)+ 原声明解构隐藏变量
|
|
207
|
+
const hiddenName = `__$VITARX_D${destructureCounter++}$__`;
|
|
208
|
+
trackedNames.push(hiddenName);
|
|
209
|
+
results.push(t.variableDeclaration('const', [
|
|
210
|
+
t.variableDeclarator(t.identifier(hiddenName), createMemoExpression(hiddenName, init, true))
|
|
211
|
+
]), t.variableDeclaration(stmt.kind, [
|
|
212
|
+
t.variableDeclarator(decl.id, t.identifier(hiddenName))
|
|
213
|
+
]));
|
|
209
214
|
}
|
|
215
|
+
return results;
|
|
210
216
|
}
|
|
211
217
|
}
|
|
@@ -13,3 +13,13 @@ import type { ComponentInfo } from '../../utils/index.js';
|
|
|
13
13
|
* @param filename - 文件名(用于生成组件唯一 ID)
|
|
14
14
|
*/
|
|
15
15
|
export declare function injectHMRSupport(program: t.Program, components: ComponentInfo[], filename: string): void;
|
|
16
|
+
/**
|
|
17
|
+
* 为「导入转发组件默认导出」的模块注入 accept-only HMR 支持
|
|
18
|
+
*
|
|
19
|
+
* 典型场景:文件路由的 _layout 转发文件(export default AdminLayout)。
|
|
20
|
+
* 模块本身不含组件函数定义,无需 bindId 与函数体注入(函数体属于
|
|
21
|
+
* 源模块,其自身已有完整 HMR);仅需自接受模块更新并把新导出交给
|
|
22
|
+
* HMRManager.update 按组件 id 热替换。
|
|
23
|
+
* @param program - AST Program 节点
|
|
24
|
+
*/
|
|
25
|
+
export declare function injectHMRAcceptOnly(program: t.Program): void;
|
|
@@ -6,15 +6,14 @@
|
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
7
|
import { HMR } from '../../constants/index.js';
|
|
8
8
|
import { injectHMRImport, injectGetComponentViewImport } from './inject-imports.js';
|
|
9
|
-
import { createHMRRegistrationStatements, createWrappedComponentHMRStatements,
|
|
9
|
+
import { createHMRRegistrationStatements, createWrappedComponentHMRStatements, injectStatePreservation } from './inject-state.js';
|
|
10
10
|
/**
|
|
11
11
|
* 为组件函数注入 HMR 注册代码
|
|
12
12
|
* @param func - 函数声明/表达式/箭头函数
|
|
13
|
-
* @param variableNames - 需要追踪的变量名列表
|
|
14
13
|
* @param isWrapped - 是否为包装组件(defineComponent/builder)
|
|
15
14
|
* @param componentName - 组件名称(包装组件需要)
|
|
16
15
|
*/
|
|
17
|
-
function injectHMRIntoFunction(func,
|
|
16
|
+
function injectHMRIntoFunction(func, isWrapped = false, componentName) {
|
|
18
17
|
// 如果是箭头函数且函数体不是块语句(单行箭头函数),转换为块语句
|
|
19
18
|
if (func.type === 'ArrowFunctionExpression' && func.body.type !== 'BlockStatement') {
|
|
20
19
|
const returnStmt = t.returnStatement(func.body);
|
|
@@ -23,10 +22,8 @@ function injectHMRIntoFunction(func, variableNames, isWrapped = false, component
|
|
|
23
22
|
// 确保函数体是块语句
|
|
24
23
|
if (!func.body || func.body.type !== 'BlockStatement')
|
|
25
24
|
return;
|
|
26
|
-
//
|
|
27
|
-
|
|
28
|
-
injectStatePreservation(func.body);
|
|
29
|
-
}
|
|
25
|
+
// 非包装组件:注入状态恢复代码并收集登记名(单趟完成)
|
|
26
|
+
const variableNames = isWrapped ? [] : injectStatePreservation(func.body);
|
|
30
27
|
// 根据组件类型创建对应的 HMR 注册语句
|
|
31
28
|
const statements = isWrapped
|
|
32
29
|
? createWrappedComponentHMRStatements(componentName)
|
|
@@ -58,6 +55,35 @@ function createHotAcceptStatement() {
|
|
|
58
55
|
]))
|
|
59
56
|
]));
|
|
60
57
|
}
|
|
58
|
+
/**
|
|
59
|
+
* 创建导入转发模块的 hot.accept 语句(带 invalidate 兜底)
|
|
60
|
+
*
|
|
61
|
+
* 生成:import.meta.hot.accept(mod => {
|
|
62
|
+
* if (!__$VITARX_HMR$__.instance.update(mod)) {
|
|
63
|
+
* import.meta.hot.invalidate('[VitarxHMR]: ...')
|
|
64
|
+
* }
|
|
65
|
+
* })
|
|
66
|
+
*
|
|
67
|
+
* 转发模块本身不含组件定义,update 的返回值表示新模块导出中是否
|
|
68
|
+
* 含已绑定 HMR id 的组件——为 false 说明转发目标不可识别(如换成
|
|
69
|
+
* 未转换模块的组件),必须 invalidate 整页刷新,避免界面静默 stale。
|
|
70
|
+
*
|
|
71
|
+
* 已知边界:转发目标在两个均有 HMR id 的组件间切换(export default A
|
|
72
|
+
* 改为 export default B)时,update 只登记 B 的 id 并返回 true,不会
|
|
73
|
+
* invalidate,挂在旧 id A 名下的视图不会被更新——文件路由场景由
|
|
74
|
+
* vitarx-router 虚拟路由模块自身的 hot accept 重建路由树兜底;非路由
|
|
75
|
+
* 场景需手动刷新。此为接受的降级路径,不做运行时检测。
|
|
76
|
+
* @returns hot.accept 调用语句
|
|
77
|
+
*/
|
|
78
|
+
function createForwardingHotAcceptStatement() {
|
|
79
|
+
return t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.identifier('import'), t.identifier('meta')), t.identifier('hot')), t.identifier('accept')), [
|
|
80
|
+
t.arrowFunctionExpression([t.identifier('mod')], t.blockStatement([
|
|
81
|
+
t.ifStatement(t.unaryExpression('!', t.callExpression(t.memberExpression(t.memberExpression(t.identifier(HMR.manager), t.identifier('instance')), t.identifier('update')), [t.identifier('mod')])), t.blockStatement([
|
|
82
|
+
t.expressionStatement(t.callExpression(t.memberExpression(t.memberExpression(t.memberExpression(t.identifier('import'), t.identifier('meta')), t.identifier('hot')), t.identifier('invalidate')), [t.stringLiteral('[VitarxHMR]: 转发的默认导出不是可热更新的组件,回退整页刷新')]))
|
|
83
|
+
]))
|
|
84
|
+
]))
|
|
85
|
+
]));
|
|
86
|
+
}
|
|
61
87
|
/**
|
|
62
88
|
* 生成组件唯一 ID(文件路径 + 组件名称)
|
|
63
89
|
* 使用 FNV-1a 哈希算法生成短哈希值
|
|
@@ -92,9 +118,7 @@ export function injectHMRSupport(program, components, filename) {
|
|
|
92
118
|
injectGetComponentViewImport(program);
|
|
93
119
|
// 步骤2: 为每个组件函数注入 HMR 注册代码和状态恢复
|
|
94
120
|
for (const { node, isWrapped, name } of components) {
|
|
95
|
-
|
|
96
|
-
const variableNames = node.body?.type === 'BlockStatement' ? collectLocalVariableNames(node.body) : [];
|
|
97
|
-
injectHMRIntoFunction(node, variableNames, isWrapped, name);
|
|
121
|
+
injectHMRIntoFunction(node, isWrapped, name);
|
|
98
122
|
}
|
|
99
123
|
// 步骤3: 为每个组件创建 bindId 语句(用于热更新时识别)
|
|
100
124
|
for (const { name } of components) {
|
|
@@ -103,3 +127,16 @@ export function injectHMRSupport(program, components, filename) {
|
|
|
103
127
|
// 步骤4: 创建 import.meta.hot.accept 语句(监听热更新事件)
|
|
104
128
|
program.body.push(createHotAcceptStatement());
|
|
105
129
|
}
|
|
130
|
+
/**
|
|
131
|
+
* 为「导入转发组件默认导出」的模块注入 accept-only HMR 支持
|
|
132
|
+
*
|
|
133
|
+
* 典型场景:文件路由的 _layout 转发文件(export default AdminLayout)。
|
|
134
|
+
* 模块本身不含组件函数定义,无需 bindId 与函数体注入(函数体属于
|
|
135
|
+
* 源模块,其自身已有完整 HMR);仅需自接受模块更新并把新导出交给
|
|
136
|
+
* HMRManager.update 按组件 id 热替换。
|
|
137
|
+
* @param program - AST Program 节点
|
|
138
|
+
*/
|
|
139
|
+
export function injectHMRAcceptOnly(program) {
|
|
140
|
+
injectHMRImport(program);
|
|
141
|
+
program.body.push(createForwardingHotAcceptStatement());
|
|
142
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { REF_APIS, RESPONSIVE_MODULES } from '../../constants/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import { collectObjectPatternBindings, collectPatternBindings } from '../../utils/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* 收集 ref API 的别名
|
|
5
5
|
* 识别从 vitarx 或 @vitarx/responsive 导入的 ref/toRef/toRefs/shallowRef/computed
|
|
@@ -76,6 +76,15 @@ function collectRefVariablesFromNode(node, refVariables, refApiLocalNames, toRef
|
|
|
76
76
|
if (decl.id.type === 'VoidPattern')
|
|
77
77
|
continue;
|
|
78
78
|
const init = decl.init;
|
|
79
|
+
// 如果 init 是箭头函数或函数表达式,递归处理其函数体内的 ref 声明
|
|
80
|
+
// 确保组件函数(如 const App = () => { const count = ref(0) })内的 ref 被正确收集
|
|
81
|
+
if ((init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression') &&
|
|
82
|
+
init.body.type === 'BlockStatement') {
|
|
83
|
+
for (const stmt of init.body.body) {
|
|
84
|
+
collectRefVariablesFromNode(stmt, refVariables, refApiLocalNames, toRefsLocalNames);
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
79
88
|
// 检查是否为函数调用表达式
|
|
80
89
|
if (init.type !== 'CallExpression' || init.callee.type !== 'Identifier') {
|
|
81
90
|
continue;
|
|
@@ -102,24 +111,32 @@ function collectRefVariablesFromNode(node, refVariables, refApiLocalNames, toRef
|
|
|
102
111
|
collectRefVariablesFromNode(node.declaration, refVariables, refApiLocalNames, toRefsLocalNames);
|
|
103
112
|
}
|
|
104
113
|
else if (node.type === 'ExportDefaultDeclaration') {
|
|
105
|
-
//
|
|
114
|
+
// 处理默认导出(支持函数声明、箭头函数、函数表达式)
|
|
106
115
|
const decl = node.declaration;
|
|
107
116
|
if (decl.type === 'FunctionDeclaration' && decl.body) {
|
|
108
117
|
for (const stmt of decl.body.body) {
|
|
109
118
|
collectRefVariablesFromNode(stmt, refVariables, refApiLocalNames, toRefsLocalNames);
|
|
110
119
|
}
|
|
111
120
|
}
|
|
121
|
+
else if ((decl.type === 'ArrowFunctionExpression' || decl.type === 'FunctionExpression') &&
|
|
122
|
+
decl.body.type === 'BlockStatement') {
|
|
123
|
+
for (const stmt of decl.body.body) {
|
|
124
|
+
collectRefVariablesFromNode(stmt, refVariables, refApiLocalNames, toRefsLocalNames);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
112
127
|
}
|
|
128
|
+
// 控制流语句(if/for/while/switch/try)中的变量声明不递归收集
|
|
129
|
+
// 函数控制流中通常不会声明 ref 变量,仅收集模块顶层和函数体顶层
|
|
113
130
|
}
|
|
114
131
|
/**
|
|
115
132
|
* 收集确定不是 ref 的变量
|
|
116
133
|
* 包括函数声明、箭头函数和函数表达式变量(这些不可能是 ref)
|
|
134
|
+
* 静态分析能明确变量类型时,跳过 unref 包装以提升性能
|
|
117
135
|
* @param program - AST Program 节点
|
|
118
136
|
* @returns 非 ref 变量名集合
|
|
119
137
|
*/
|
|
120
138
|
export function collectNonRefVariables(program) {
|
|
121
139
|
const nonRefVariables = new Set();
|
|
122
|
-
// 遍历程序体中的所有节点
|
|
123
140
|
for (const node of program.body) {
|
|
124
141
|
collectNonRefFromNode(node, nonRefVariables);
|
|
125
142
|
}
|
|
@@ -127,6 +144,7 @@ export function collectNonRefVariables(program) {
|
|
|
127
144
|
}
|
|
128
145
|
/**
|
|
129
146
|
* 从节点中递归收集非 ref 变量
|
|
147
|
+
* 仅处理模块顶层和函数体顶层的声明,不递归控制流语句
|
|
130
148
|
* @param node - AST 节点
|
|
131
149
|
* @param nonRefVariables - 存储非 ref 变量名的集合
|
|
132
150
|
*/
|
|
@@ -134,7 +152,6 @@ function collectNonRefFromNode(node, nonRefVariables) {
|
|
|
134
152
|
// 函数声明一定不是 ref
|
|
135
153
|
if (node.type === 'FunctionDeclaration' && node.id) {
|
|
136
154
|
nonRefVariables.add(node.id.name);
|
|
137
|
-
// 递归处理函数体内的变量
|
|
138
155
|
if (node.body) {
|
|
139
156
|
for (const stmt of node.body.body) {
|
|
140
157
|
collectNonRefFromNode(stmt, nonRefVariables);
|
|
@@ -142,33 +159,49 @@ function collectNonRefFromNode(node, nonRefVariables) {
|
|
|
142
159
|
}
|
|
143
160
|
}
|
|
144
161
|
else if (node.type === 'VariableDeclaration') {
|
|
145
|
-
//
|
|
162
|
+
// 变量声明:箭头函数或函数表达式赋值一定不是 ref
|
|
146
163
|
for (const decl of node.declarations) {
|
|
147
164
|
if (!decl.init)
|
|
148
165
|
continue;
|
|
149
|
-
// 只处理简单标识符(不处理解构)
|
|
150
|
-
if (decl.id.type !== 'Identifier')
|
|
151
|
-
continue;
|
|
152
166
|
const init = decl.init;
|
|
153
|
-
if (init.type === 'ArrowFunctionExpression' ||
|
|
154
|
-
|
|
155
|
-
|
|
167
|
+
if (init.type === 'ArrowFunctionExpression' || init.type === 'FunctionExpression') {
|
|
168
|
+
if (decl.id.type === 'Identifier') {
|
|
169
|
+
nonRefVariables.add(decl.id.name);
|
|
170
|
+
}
|
|
171
|
+
// 递归处理函数体内的变量声明
|
|
172
|
+
if (init.body.type === 'BlockStatement') {
|
|
173
|
+
for (const stmt of init.body.body) {
|
|
174
|
+
collectNonRefFromNode(stmt, nonRefVariables);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
156
177
|
}
|
|
157
178
|
}
|
|
158
179
|
}
|
|
159
|
-
else if (node.type === 'ExportNamedDeclaration') {
|
|
160
|
-
|
|
161
|
-
if (node.declaration) {
|
|
162
|
-
collectNonRefFromNode(node.declaration, nonRefVariables);
|
|
163
|
-
}
|
|
180
|
+
else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
|
181
|
+
collectNonRefFromNode(node.declaration, nonRefVariables);
|
|
164
182
|
}
|
|
165
183
|
else if (node.type === 'ExportDefaultDeclaration') {
|
|
166
|
-
// 处理默认导出
|
|
167
184
|
const decl = node.declaration;
|
|
168
|
-
if (decl.type === 'FunctionDeclaration'
|
|
169
|
-
|
|
185
|
+
if (decl.type === 'FunctionDeclaration') {
|
|
186
|
+
if (decl.id) {
|
|
187
|
+
nonRefVariables.add(decl.id.name);
|
|
188
|
+
}
|
|
189
|
+
// 递归处理函数体,收集内部非 ref 变量(与 collectRefVariablesFromNode 一致)
|
|
190
|
+
if (decl.body) {
|
|
191
|
+
for (const stmt of decl.body.body) {
|
|
192
|
+
collectNonRefFromNode(stmt, nonRefVariables);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
else if ((decl.type === 'ArrowFunctionExpression' || decl.type === 'FunctionExpression') &&
|
|
197
|
+
decl.body.type === 'BlockStatement') {
|
|
198
|
+
// 处理未被转换的箭头函数/函数表达式默认导出
|
|
199
|
+
for (const stmt of decl.body.body) {
|
|
200
|
+
collectNonRefFromNode(stmt, nonRefVariables);
|
|
201
|
+
}
|
|
170
202
|
}
|
|
171
203
|
}
|
|
204
|
+
// 控制流语句(if/for/while/switch/try)中的变量声明不递归收集
|
|
172
205
|
}
|
|
173
206
|
/**
|
|
174
207
|
* 构建 ref API 的本地名称集合
|
package/dist/passes/index.d.ts
CHANGED
|
@@ -7,4 +7,4 @@ export { processPureCompileComponent, processSwitch, processIfBlock } from './co
|
|
|
7
7
|
export { processVIfChain, processDirectives } from './directives/index.js';
|
|
8
8
|
export { processChildren, processJSXElement, transformJSXElement, processJSXFragment } from './jsx/index.js';
|
|
9
9
|
export { processProps, type PropsResult } from './props/index.js';
|
|
10
|
-
export { injectHMRSupport } from './hmr/index.js';
|
|
10
|
+
export { injectHMRSupport, injectHMRAcceptOnly } from './hmr/index.js';
|
package/dist/passes/index.js
CHANGED
|
@@ -13,4 +13,4 @@ export { processChildren, processJSXElement, transformJSXElement, processJSXFrag
|
|
|
13
13
|
// Props 处理
|
|
14
14
|
export { processProps } from './props/index.js';
|
|
15
15
|
// HMR 注入
|
|
16
|
-
export { injectHMRSupport } from './hmr/index.js';
|
|
16
|
+
export { injectHMRSupport, injectHMRAcceptOnly } from './hmr/index.js';
|
|
@@ -1,7 +1,24 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
|
+
import { isJSXElement } from '@babel/types';
|
|
2
3
|
import { markImport } from '../../context.js';
|
|
3
|
-
import {
|
|
4
|
+
import { createError } from '../../error.js';
|
|
5
|
+
import { addPureComment, createCreateViewCall, filterWhitespaceChildren, getAlias, getDevLocInfo, getJSXElementName } from '../../utils/index.js';
|
|
4
6
|
import { processChildren } from './processChildren.js';
|
|
7
|
+
/**
|
|
8
|
+
* 校验子节点中不能直接包含 Match 组件
|
|
9
|
+
* Match 必须在 Switch 内使用,非 Switch 元素的子节点中不允许出现 Match
|
|
10
|
+
* @param children - 子节点数组
|
|
11
|
+
*/
|
|
12
|
+
function validateNoDirectMatchChild(children) {
|
|
13
|
+
for (const child of children) {
|
|
14
|
+
if (isJSXElement(child)) {
|
|
15
|
+
const childName = getJSXElementName(child);
|
|
16
|
+
if (childName === 'Match') {
|
|
17
|
+
throw createError('E012', child);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
5
22
|
/**
|
|
6
23
|
* 处理 JSX Fragment
|
|
7
24
|
* @param path - JSX Fragment 路径
|
|
@@ -32,6 +32,9 @@ export declare function getAttributeValue(value: t.JSXAttribute['value']): t.Exp
|
|
|
32
32
|
*
|
|
33
33
|
* 特殊处理:
|
|
34
34
|
* - children 属性不使用 unref,因为 ref 作为 children 是合法的可变渲染源
|
|
35
|
+
* - ref 属性不使用 unref/.value,因为 ref 绑定需要 ref 对象本身
|
|
36
|
+
* 运行时 resolveProps() 通过 isRef() 判定 ref 属性值,若此处解包,
|
|
37
|
+
* popProperty 取到的是解包后的值(如 null),isRef 判定失败导致绑定失效
|
|
35
38
|
* - 其他标识符属性使用 unref 解包
|
|
36
39
|
*
|
|
37
40
|
* @param key - 属性名
|