@vitarx/plugin-vite 0.0.1-beta.8 → 1.0.0-beta.1
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/README.md +64 -4
- package/dist/constants/index.d.ts +1 -11
- package/dist/constants/index.js +1 -11
- package/dist/context.d.ts +6 -8
- package/dist/context.js +4 -5
- package/dist/hmr-client/utils.d.ts +1 -1
- package/dist/hmr-client/utils.js +7 -4
- package/dist/index.js +7 -3
- package/dist/passes/components/ifBlock.js +2 -4
- package/dist/passes/components/switch.js +2 -4
- package/dist/passes/directives/index.d.ts +1 -1
- package/dist/passes/directives/index.js +1 -1
- package/dist/passes/directives/vIf.d.ts +13 -6
- package/dist/passes/directives/vIf.js +26 -27
- package/dist/passes/hmr/inject-state.js +2 -2
- package/dist/passes/imports/injectImports.js +2 -2
- package/dist/passes/jsx/processChildren.js +30 -51
- package/dist/passes/jsx/processJSXElement.js +6 -7
- package/dist/transform.js +5 -2
- package/dist/utils/ast-builders.d.ts +16 -17
- package/dist/utils/ast-builders.js +23 -36
- package/dist/utils/branch-factory.d.ts +1 -4
- package/dist/utils/branch-factory.js +4 -7
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +1 -1
- package/dist/utils/jsx-helpers.d.ts +4 -0
- package/dist/utils/jsx-helpers.js +12 -9
- package/package.json +2 -2
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
|
-
// 成员表达式使用
|
|
257
|
+
// 成员表达式使用 accessor
|
|
234
258
|
<div>{props.value}</div>
|
|
235
|
-
// 编译为:
|
|
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
|
-
// 逻辑表达式使用
|
|
265
|
+
// 逻辑表达式使用 expr
|
|
242
266
|
<div>{a && b}</div>
|
|
243
|
-
// 编译为:
|
|
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", "
|
|
9
|
+
export declare const UI_API_NAMES: readonly ["createView", "branch", "dynamic", "expr", "accessor", "withDirectives"];
|
|
20
10
|
/**
|
|
21
11
|
* Ref 相关 API 名称
|
|
22
12
|
* 用于识别 ref 变量
|
package/dist/constants/index.js
CHANGED
|
@@ -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', '
|
|
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
|
@@ -17,10 +17,10 @@ export interface ImportInfo {
|
|
|
17
17
|
Fragment: boolean;
|
|
18
18
|
/** branch - 条件分支函数 */
|
|
19
19
|
branch: boolean;
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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,6 @@ export interface TransformContext {
|
|
|
78
78
|
refApiAliases: RefApiAliases;
|
|
79
79
|
/** ref 变量名集合 */
|
|
80
80
|
refVariables: Set<string>;
|
|
81
|
-
/** UI API 别名集合(用于 HMR 代码分离识别) */
|
|
82
|
-
uiApiAliases: Set<string>;
|
|
83
81
|
/** builder 函数的本地别名(用于识别纯构建组件) */
|
|
84
82
|
builderAlias: string | null;
|
|
85
83
|
/** 已处理的 AST 节点追踪集合 */
|
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
|
-
|
|
22
|
-
|
|
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
|
-
|
|
33
|
-
|
|
32
|
+
expr: null,
|
|
33
|
+
accessor: null,
|
|
34
34
|
withDirectives: null,
|
|
35
35
|
unref: null,
|
|
36
36
|
isRef: null
|
|
@@ -43,7 +43,6 @@ export function createContext(code, id, options, ast) {
|
|
|
43
43
|
computed: null
|
|
44
44
|
},
|
|
45
45
|
refVariables: new Set(),
|
|
46
|
-
uiApiAliases: new Set(),
|
|
47
46
|
builderAlias: null,
|
|
48
47
|
processedNodes: new WeakSet(),
|
|
49
48
|
pureCommentedNodes: new WeakSet(),
|
|
@@ -11,7 +11,7 @@ export interface ChangeCode {
|
|
|
11
11
|
* 判断两个函数组件的代码差异
|
|
12
12
|
*
|
|
13
13
|
* 分析策略:
|
|
14
|
-
* 1. 解析函数代码,提取所有 createView/branch/
|
|
14
|
+
* 1. 解析函数代码,提取所有 createView/branch/expr 等 UI API 调用
|
|
15
15
|
* 2. 将这些调用识别为 UI 描述代码
|
|
16
16
|
* 3. 其余代码识别为非 UI 代码(逻辑代码)
|
|
17
17
|
* 4. 分别比较 UI 代码和非 UI 代码是否变化
|
package/dist/hmr-client/utils.js
CHANGED
|
@@ -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
|
-
|
|
22
|
-
|
|
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/
|
|
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
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isJSXElement } from '@babel/types';
|
|
2
2
|
import { createError } from '../../error.js';
|
|
3
|
-
import { collectVIfChainInfo, createArrowFunction, createBranch, filterWhitespaceChildren,
|
|
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,
|
|
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
|
|
25
|
+
const branchCall = createBranch({ conditions, branches }, ctx);
|
|
28
26
|
if (path.node.loc) {
|
|
29
27
|
branchCall.loc = path.node.loc;
|
|
30
28
|
}
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* v-if 链处理器
|
|
3
|
-
* 处理 Fragment 中的 v-if/v-else-if/v-else 链
|
|
3
|
+
* 处理 Fragment/Element 中的 v-if/v-else-if/v-else 链
|
|
4
4
|
* @module passes/directives/vIf
|
|
5
5
|
*/
|
|
6
6
|
import type { NodePath } from '@babel/traverse';
|
|
7
7
|
import * as t from '@babel/types';
|
|
8
8
|
import { TransformContext } from '../../context.js';
|
|
9
|
+
type TransformJSXElementFn = (node: t.JSXElement, ctx: TransformContext, handleVIf: boolean) => t.Expression | null;
|
|
10
|
+
type JSXContainerPath = NodePath<t.JSXFragment | t.JSXElement>;
|
|
9
11
|
/**
|
|
10
|
-
*
|
|
12
|
+
* 处理子元素数组中的 v-if 链
|
|
13
|
+
* 收集并转换 v-if/v-else-if/v-else 链为 branch 调用
|
|
14
|
+
* @param children - 子元素数组(会被原地修改)
|
|
15
|
+
* @param ctx - 转换上下文
|
|
16
|
+
* @param transformJSXElement - JSX 元素转换函数
|
|
17
|
+
* @returns 处理后的子元素数组(链节点已替换为 branch 调用,nullLiteral 已移除)
|
|
11
18
|
*/
|
|
12
|
-
|
|
19
|
+
export declare function transformChildrenVIfChains(children: t.Node[], ctx: TransformContext, transformJSXElement: TransformJSXElementFn): t.Node[];
|
|
13
20
|
/**
|
|
14
|
-
* 处理 Fragment 中的 v-if 链
|
|
15
|
-
* @param path - JSX Fragment 路径
|
|
21
|
+
* 处理 Fragment/Element 中的 v-if 链
|
|
22
|
+
* @param path - JSX Fragment 或 Element 路径
|
|
16
23
|
* @param ctx - 转换上下文
|
|
17
24
|
* @param transformJSXElement - JSX 元素转换函数
|
|
18
25
|
*/
|
|
19
|
-
export declare function processVIfChain(path:
|
|
26
|
+
export declare function processVIfChain(path: JSXContainerPath, ctx: TransformContext, transformJSXElement: TransformJSXElementFn): void;
|
|
20
27
|
export {};
|
|
@@ -1,30 +1,45 @@
|
|
|
1
1
|
import * as t from '@babel/types';
|
|
2
2
|
import { isJSXElement, isJSXText } from '@babel/types';
|
|
3
|
-
import { collectFragmentVIfChains, createArrowFunction, createBranch,
|
|
3
|
+
import { collectFragmentVIfChains, createArrowFunction, createBranch, isWhitespaceJSXText, removeVIfChainDirectives } from '../../utils/index.js';
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* 处理子元素数组中的 v-if 链
|
|
6
|
+
* 收集并转换 v-if/v-else-if/v-else 链为 branch 调用
|
|
7
|
+
* @param children - 子元素数组(会被原地修改)
|
|
7
8
|
* @param ctx - 转换上下文
|
|
8
9
|
* @param transformJSXElement - JSX 元素转换函数
|
|
10
|
+
* @returns 处理后的子元素数组(链节点已替换为 branch 调用,nullLiteral 已移除)
|
|
9
11
|
*/
|
|
10
|
-
export function
|
|
11
|
-
const children = path.node.children;
|
|
12
|
+
export function transformChildrenVIfChains(children, ctx, transformJSXElement) {
|
|
12
13
|
const chains = collectFragmentVIfChains(children);
|
|
13
14
|
if (chains.length === 0)
|
|
14
|
-
return;
|
|
15
|
-
// 从后向前处理链,避免索引偏移问题
|
|
15
|
+
return children;
|
|
16
16
|
for (let c = chains.length - 1; c >= 0; c--) {
|
|
17
17
|
processVIfChainItem(chains[c], children, ctx, transformJSXElement);
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
return children.filter(child => {
|
|
20
|
+
if (t.isNullLiteral(child))
|
|
21
|
+
return false;
|
|
22
|
+
return !(isJSXText(child) && isWhitespaceJSXText(child));
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 处理 Fragment/Element 中的 v-if 链
|
|
27
|
+
* @param path - JSX Fragment 或 Element 路径
|
|
28
|
+
* @param ctx - 转换上下文
|
|
29
|
+
* @param transformJSXElement - JSX 元素转换函数
|
|
30
|
+
*/
|
|
31
|
+
export function processVIfChain(path, ctx, transformJSXElement) {
|
|
32
|
+
const children = path.node.children;
|
|
33
|
+
const processed = transformChildrenVIfChains(children, ctx, transformJSXElement);
|
|
34
|
+
if (processed !== children) {
|
|
35
|
+
path.node.children = processed;
|
|
36
|
+
}
|
|
21
37
|
}
|
|
22
38
|
/**
|
|
23
39
|
* 处理单个 v-if 链
|
|
24
40
|
*/
|
|
25
41
|
function processVIfChainItem(chain, children, ctx, transformJSXElement) {
|
|
26
42
|
const { nodes, conditions, endIndex } = chain;
|
|
27
|
-
// 计算链在 children 中的起始位置
|
|
28
43
|
let start = -1;
|
|
29
44
|
for (let i = 0; i < children.length; i++) {
|
|
30
45
|
const child = children[i];
|
|
@@ -33,7 +48,6 @@ function processVIfChainItem(chain, children, ctx, transformJSXElement) {
|
|
|
33
48
|
break;
|
|
34
49
|
}
|
|
35
50
|
}
|
|
36
|
-
// 移除指令并转换节点
|
|
37
51
|
const branches = [];
|
|
38
52
|
for (const node of nodes) {
|
|
39
53
|
removeVIfChainDirectives(node);
|
|
@@ -42,12 +56,7 @@ function processVIfChainItem(chain, children, ctx, transformJSXElement) {
|
|
|
42
56
|
branches.push(createArrowFunction(transformedNode));
|
|
43
57
|
}
|
|
44
58
|
}
|
|
45
|
-
|
|
46
|
-
const firstNode = nodes[0];
|
|
47
|
-
const locInfo = getDevLocInfo(ctx, firstNode);
|
|
48
|
-
// 生成 branch 调用
|
|
49
|
-
const branchCall = createBranch({ conditions, branches, locInfo }, ctx);
|
|
50
|
-
// 替换链的第一个节点,其他节点标记为 null
|
|
59
|
+
const branchCall = createBranch({ conditions, branches }, ctx);
|
|
51
60
|
if (start >= 0) {
|
|
52
61
|
children[start] = branchCall;
|
|
53
62
|
for (let k = start + 1; k <= endIndex; k++) {
|
|
@@ -55,13 +64,3 @@ function processVIfChainItem(chain, children, ctx, transformJSXElement) {
|
|
|
55
64
|
}
|
|
56
65
|
}
|
|
57
66
|
}
|
|
58
|
-
/**
|
|
59
|
-
* 清理已处理的子节点
|
|
60
|
-
*/
|
|
61
|
-
function cleanupProcessedChildren(path, children) {
|
|
62
|
-
path.node.children = children.filter(child => {
|
|
63
|
-
if (t.isNullLiteral(child))
|
|
64
|
-
return false;
|
|
65
|
-
return !(isJSXText(child) && isWhitespaceJSXText(child));
|
|
66
|
-
});
|
|
67
|
-
}
|
|
@@ -15,7 +15,7 @@ import { GET_COMPONENT_VIEW_ALIAS } from './inject-imports.js';
|
|
|
15
15
|
export function createHMRRegistrationStatements(variableNames) {
|
|
16
16
|
const statements = [];
|
|
17
17
|
statements.push(t.variableDeclaration('const', [
|
|
18
|
-
t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), []))
|
|
18
|
+
t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), [t.booleanLiteral(true)]))
|
|
19
19
|
]));
|
|
20
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
21
|
const stateProperties = variableNames.map(name => t.objectMethod('get', t.identifier(name), [], t.blockStatement([t.returnStatement(t.identifier(name))])));
|
|
@@ -35,7 +35,7 @@ export function createHMRRegistrationStatements(variableNames) {
|
|
|
35
35
|
export function createWrappedComponentHMRStatements(componentName) {
|
|
36
36
|
const statements = [];
|
|
37
37
|
statements.push(t.variableDeclaration('const', [
|
|
38
|
-
t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), []))
|
|
38
|
+
t.variableDeclarator(t.identifier(HMR.view), t.callExpression(t.identifier(GET_COMPONENT_VIEW_ALIAS), [t.booleanLiteral(true)]))
|
|
39
39
|
]));
|
|
40
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
41
|
return statements;
|
|
@@ -13,8 +13,8 @@ const API_IMPORT_CONFIG = [
|
|
|
13
13
|
{ name: 'createView', importKey: 'createView' },
|
|
14
14
|
{ name: 'Fragment', importKey: 'Fragment' },
|
|
15
15
|
{ name: 'branch', importKey: 'branch' },
|
|
16
|
-
{ name: '
|
|
17
|
-
{ name: '
|
|
16
|
+
{ name: 'expr', importKey: 'expr' },
|
|
17
|
+
{ name: 'accessor', importKey: 'accessor' },
|
|
18
18
|
{ name: 'withDirectives', importKey: 'withDirectives' },
|
|
19
19
|
{ name: 'unref', importKey: 'unref' },
|
|
20
20
|
{ name: 'isRef', importKey: 'isRef' }
|
|
@@ -4,25 +4,28 @@
|
|
|
4
4
|
* @module passes/jsx/processChildren
|
|
5
5
|
*/
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
|
-
import {
|
|
7
|
+
import { isBinaryExpression, isConditionalExpression, isIdentifier, isJSXElement, isJSXExpressionContainer, isJSXFragment, isJSXText, isLogicalExpression, isMemberExpression } from '@babel/types';
|
|
8
8
|
import { markImport } from '../../context.js';
|
|
9
|
-
import { addPureComment,
|
|
9
|
+
import { addPureComment, createAccessorCall, createBinaryBranch, createExprCall, getAlias } from '../../utils/index.js';
|
|
10
10
|
/**
|
|
11
|
-
*
|
|
11
|
+
* 处理成员表达式,生成 accessor 调用
|
|
12
12
|
*/
|
|
13
13
|
function handleMemberExpression(expr, ctx) {
|
|
14
|
-
markImport(ctx, '
|
|
15
|
-
const
|
|
16
|
-
return
|
|
14
|
+
markImport(ctx, 'accessor');
|
|
15
|
+
const accessorAlias = getAlias(ctx.vitarxAliases, 'accessor');
|
|
16
|
+
return createAccessorCall(expr.object, expr.property, accessorAlias);
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
19
|
-
*
|
|
19
|
+
* 使用 expr 包装表达式
|
|
20
|
+
* 用于逻辑表达式、二元表达式和调用表达式的统一包装
|
|
21
|
+
* 运行时根据返回值类型决定是否跟踪响应式:
|
|
22
|
+
* - 数组返回值:原样返回(仅渲染一次)
|
|
23
|
+
* - 其他返回值:创建 DynamicView 追踪
|
|
20
24
|
*/
|
|
21
|
-
function
|
|
22
|
-
markImport(ctx, '
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
return addPureComment(createDynamicCall(expr, dynamicAlias, locInfo), ctx);
|
|
25
|
+
function wrapWithExpr(expr, ctx) {
|
|
26
|
+
markImport(ctx, 'expr');
|
|
27
|
+
const exprAlias = getAlias(ctx.vitarxAliases, 'expr');
|
|
28
|
+
return addPureComment(createExprCall(expr, exprAlias), ctx);
|
|
26
29
|
}
|
|
27
30
|
/**
|
|
28
31
|
* 处理子节点数组
|
|
@@ -42,59 +45,40 @@ export function processChildren(children, ctx) {
|
|
|
42
45
|
}
|
|
43
46
|
/**
|
|
44
47
|
* 处理单个子节点
|
|
48
|
+
* JSX 容器节点在此拆包后委托给 processChildExpression 处理
|
|
49
|
+
* 已转换的 API 调用(createView/branch 等 CallExpression)直接透传
|
|
45
50
|
*/
|
|
46
51
|
function processChildNode(node, ctx) {
|
|
47
|
-
// JSX 文本
|
|
48
52
|
if (isJSXText(node)) {
|
|
49
53
|
const trimmed = node.value.trim();
|
|
50
54
|
if (!trimmed)
|
|
51
55
|
return null;
|
|
52
56
|
return t.stringLiteral(trimmed);
|
|
53
57
|
}
|
|
54
|
-
// JSX 表达式容器
|
|
55
58
|
if (isJSXExpressionContainer(node)) {
|
|
56
59
|
if (node.expression.type === 'JSXEmptyExpression')
|
|
57
60
|
return null;
|
|
58
|
-
return processChildExpression(node.expression, ctx
|
|
61
|
+
return processChildExpression(node.expression, ctx);
|
|
59
62
|
}
|
|
60
|
-
// JSX 展开子元素
|
|
61
63
|
if (node.type === 'JSXSpreadChild') {
|
|
62
|
-
return processChildExpression(node.expression, ctx
|
|
64
|
+
return processChildExpression(node.expression, ctx);
|
|
63
65
|
}
|
|
64
|
-
// JSX 元素或片段
|
|
65
66
|
if (isJSXElement(node) || isJSXFragment(node)) {
|
|
66
67
|
return node;
|
|
67
68
|
}
|
|
68
|
-
//
|
|
69
|
-
if (isStringLiteral(node) || isNumericLiteral(node) || isBooleanLiteral(node)) {
|
|
70
|
-
return node;
|
|
71
|
-
}
|
|
72
|
-
// 标识符
|
|
73
|
-
if (isIdentifier(node)) {
|
|
74
|
-
return node;
|
|
75
|
-
}
|
|
76
|
-
// 成员表达式
|
|
77
|
-
if (isMemberExpression(node)) {
|
|
78
|
-
return handleMemberExpression(node, ctx);
|
|
79
|
-
}
|
|
80
|
-
// 条件表达式
|
|
81
|
-
if (isConditionalExpression(node)) {
|
|
82
|
-
return processConditionalExpression(node, ctx);
|
|
83
|
-
}
|
|
84
|
-
// 逻辑表达式
|
|
85
|
-
if (isLogicalExpression(node)) {
|
|
86
|
-
return handleLogicalExpression(node, ctx);
|
|
87
|
-
}
|
|
88
|
-
// 调用表达式
|
|
69
|
+
// 已转换的 API 调用(createView/branch 等)直接透传,不包装 expr
|
|
89
70
|
if (node.type === 'CallExpression') {
|
|
90
71
|
return node;
|
|
91
72
|
}
|
|
92
|
-
|
|
73
|
+
// 其他表达式类型委托给 processChildExpression
|
|
74
|
+
return processChildExpression(node, ctx);
|
|
93
75
|
}
|
|
94
76
|
/**
|
|
95
|
-
*
|
|
77
|
+
* 处理子表达式(来自 JSX 表达式容器或条件分支)
|
|
78
|
+
* 根据表达式类型生成对应的运行时 API 调用
|
|
79
|
+
* CallExpression 在此用 expr 包装(用户函数调用需要响应式追踪)
|
|
96
80
|
*/
|
|
97
|
-
function processChildExpression(expr, ctx
|
|
81
|
+
function processChildExpression(expr, ctx) {
|
|
98
82
|
if (isIdentifier(expr)) {
|
|
99
83
|
return expr;
|
|
100
84
|
}
|
|
@@ -104,22 +88,17 @@ function processChildExpression(expr, ctx, loc) {
|
|
|
104
88
|
if (isConditionalExpression(expr)) {
|
|
105
89
|
return processConditionalExpression(expr, ctx);
|
|
106
90
|
}
|
|
107
|
-
if (isLogicalExpression(expr)) {
|
|
108
|
-
return
|
|
91
|
+
if (isLogicalExpression(expr) || isBinaryExpression(expr) || expr.type === 'CallExpression') {
|
|
92
|
+
return wrapWithExpr(expr, ctx);
|
|
109
93
|
}
|
|
110
94
|
return expr;
|
|
111
95
|
}
|
|
112
96
|
/**
|
|
113
|
-
*
|
|
114
|
-
* 转换为 branch 调用
|
|
97
|
+
* 处理条件表达式,转换为 branch 调用
|
|
115
98
|
*/
|
|
116
99
|
function processConditionalExpression(node, ctx) {
|
|
117
100
|
const { test, consequent, alternate } = node;
|
|
118
|
-
// 处理分支
|
|
119
101
|
const processedConsequent = processChildNode(consequent, ctx) || t.nullLiteral();
|
|
120
102
|
const processedAlternate = processChildNode(alternate, ctx) || t.nullLiteral();
|
|
121
|
-
|
|
122
|
-
const locInfo = getDevLocInfo(ctx, node);
|
|
123
|
-
// 使用公共的 createBinaryBranch
|
|
124
|
-
return createBinaryBranch(test, processedConsequent, processedAlternate, ctx, locInfo);
|
|
103
|
+
return createBinaryBranch(test, processedConsequent, processedAlternate, ctx);
|
|
125
104
|
}
|
|
@@ -2,8 +2,8 @@ import * as t from '@babel/types';
|
|
|
2
2
|
import { isJSXElement } from '@babel/types';
|
|
3
3
|
import { addWarning, markImport } from '../../context.js';
|
|
4
4
|
import { createError, createWarning } from '../../error.js';
|
|
5
|
-
import { addPureComment, createArrowFunction, createBranch, createCreateViewCall, getAlias, getDevLocInfo, getDirectiveValue, getJSXAttributeByName, getJSXElementName, hasEffectiveChildren,
|
|
6
|
-
import { processDirectives } from '../directives/index.js';
|
|
5
|
+
import { addPureComment, createArrowFunction, createBranch, createCreateViewCall, filterEffectiveChildren, getAlias, getDevLocInfo, getDirectiveValue, getJSXAttributeByName, getJSXElementName, hasEffectiveChildren, isNativeElement, isVElse, isVIf, isVIfChain, removeVIfChainDirectives } from '../../utils/index.js';
|
|
6
|
+
import { processDirectives, transformChildrenVIfChains } from '../directives/index.js';
|
|
7
7
|
import { processProps } from '../props/index.js';
|
|
8
8
|
import { processChildren } from './processChildren.js';
|
|
9
9
|
/**
|
|
@@ -123,8 +123,10 @@ function processElementChildren(node, props, ctx) {
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
}
|
|
126
|
+
// 处理子元素中的 v-if 链(确保嵌套链在 transformJSXElement 递归调用时被正确处理)
|
|
127
|
+
const chainProcessed = transformChildrenVIfChains(children, ctx, transformJSXElement);
|
|
126
128
|
// 处理子元素
|
|
127
|
-
const processedChildren = processChildren(
|
|
129
|
+
const processedChildren = processChildren(chainProcessed, ctx);
|
|
128
130
|
const childrenValue = processedChildren.length === 1 ? processedChildren[0] : t.arrayExpression(processedChildren);
|
|
129
131
|
// 添加 children 属性
|
|
130
132
|
if (props) {
|
|
@@ -146,10 +148,7 @@ function transformSingleVIf(node, ctx) {
|
|
|
146
148
|
const transformedNode = transformJSXElement(node, ctx, false);
|
|
147
149
|
if (!transformedNode)
|
|
148
150
|
return null;
|
|
149
|
-
|
|
150
|
-
const locInfo = getDevLocInfo(ctx, node);
|
|
151
|
-
// 生成 branch 调用
|
|
152
|
-
const branchCall = createBranch({ conditions: [condition], branches: [createArrowFunction(transformedNode)], locInfo }, ctx);
|
|
151
|
+
const branchCall = createBranch({ conditions: [condition], branches: [createArrowFunction(transformedNode)] }, ctx);
|
|
153
152
|
if (node.loc) {
|
|
154
153
|
branchCall.loc = node.loc;
|
|
155
154
|
}
|
package/dist/transform.js
CHANGED
|
@@ -40,8 +40,8 @@ function setupAliases(ctx, program) {
|
|
|
40
40
|
'createView',
|
|
41
41
|
'Fragment',
|
|
42
42
|
'branch',
|
|
43
|
-
'
|
|
44
|
-
'
|
|
43
|
+
'expr',
|
|
44
|
+
'accessor',
|
|
45
45
|
'withDirectives',
|
|
46
46
|
'unref',
|
|
47
47
|
'isRef'
|
|
@@ -84,6 +84,9 @@ function transformAST(ast, ctx) {
|
|
|
84
84
|
ctx.processedNodes.add(path.node);
|
|
85
85
|
processPureCompileComponent(path, ctx);
|
|
86
86
|
}
|
|
87
|
+
else {
|
|
88
|
+
processVIfChain(path, ctx, transformJSXElement);
|
|
89
|
+
}
|
|
87
90
|
},
|
|
88
91
|
exit(path) {
|
|
89
92
|
if (ctx.processedNodes.has(path.node))
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
* @module utils/ast-builders
|
|
4
4
|
*/
|
|
5
5
|
import * as t from '@babel/types';
|
|
6
|
-
import { type ArrowFunctionExpression, type CallExpression, type Expression, type ObjectExpression
|
|
6
|
+
import { type ArrowFunctionExpression, type CallExpression, type Expression, type ObjectExpression } from '@babel/types';
|
|
7
|
+
import type { SourceLocation } from 'acorn';
|
|
7
8
|
import type { TransformContext, VitarxImportAliases } from '../context.js';
|
|
8
9
|
/**
|
|
9
10
|
* 创建 unref 调用
|
|
@@ -13,30 +14,28 @@ import type { TransformContext, VitarxImportAliases } from '../context.js';
|
|
|
13
14
|
*/
|
|
14
15
|
export declare function createUnrefCall(argument: Expression, alias?: string): CallExpression;
|
|
15
16
|
/**
|
|
16
|
-
* 创建
|
|
17
|
+
* 创建 accessor 调用
|
|
17
18
|
* @param object - 对象表达式
|
|
18
19
|
* @param key - 键表达式
|
|
19
|
-
* @param alias -
|
|
20
|
+
* @param alias - accessor 别名
|
|
20
21
|
* @returns CallExpression
|
|
21
22
|
*/
|
|
22
|
-
export declare function
|
|
23
|
+
export declare function createAccessorCall(object: Expression, key: Expression, alias?: string): CallExpression;
|
|
23
24
|
/**
|
|
24
|
-
* 创建
|
|
25
|
+
* 创建 expr 调用
|
|
25
26
|
* @param argument - 参数表达式
|
|
26
|
-
* @param alias -
|
|
27
|
-
* @param locInfo - 位置信息对象
|
|
27
|
+
* @param alias - expr 别名
|
|
28
28
|
* @returns CallExpression
|
|
29
29
|
*/
|
|
30
|
-
export declare function
|
|
30
|
+
export declare function createExprCall(argument: Expression, alias?: string): CallExpression;
|
|
31
31
|
/**
|
|
32
32
|
* 创建 branch 调用
|
|
33
33
|
* @param condition - 条件函数
|
|
34
34
|
* @param branches - 分支函数数组
|
|
35
35
|
* @param alias - branch 别名
|
|
36
|
-
* @param locInfo - 位置信息对象
|
|
37
36
|
* @returns CallExpression
|
|
38
37
|
*/
|
|
39
|
-
export declare function createBranchCall(condition: ArrowFunctionExpression, branches: ArrowFunctionExpression[], alias?: string
|
|
38
|
+
export declare function createBranchCall(condition: ArrowFunctionExpression, branches: ArrowFunctionExpression[], alias?: string): CallExpression;
|
|
40
39
|
/**
|
|
41
40
|
* 创建 createView 调用
|
|
42
41
|
* @param type - 元素类型
|
|
@@ -60,13 +59,6 @@ export declare function createWithDirectivesCall(view: Expression, directives: A
|
|
|
60
59
|
* @returns ArrowFunctionExpression
|
|
61
60
|
*/
|
|
62
61
|
export declare function createArrowFunction(body: Expression): ArrowFunctionExpression;
|
|
63
|
-
/**
|
|
64
|
-
* 创建位置信息对象
|
|
65
|
-
* @param filename - 文件名
|
|
66
|
-
* @param loc - 源码位置
|
|
67
|
-
* @returns ObjectExpression
|
|
68
|
-
*/
|
|
69
|
-
export declare function createLocationObject(filename: string, loc: SourceLocation): ObjectExpression;
|
|
70
62
|
/**
|
|
71
63
|
* 为调用表达式添加 @__PURE__ 注释
|
|
72
64
|
* @param node - 调用表达式节点
|
|
@@ -81,6 +73,13 @@ export declare function addPureComment<T extends CallExpression>(node: T, ctx: T
|
|
|
81
73
|
* @returns 别名或原名
|
|
82
74
|
*/
|
|
83
75
|
export declare function getAlias(aliases: VitarxImportAliases, name: keyof VitarxImportAliases): string;
|
|
76
|
+
/**
|
|
77
|
+
* 创建位置信息对象
|
|
78
|
+
* @param filename - 文件名
|
|
79
|
+
* @param loc - 源码位置
|
|
80
|
+
* @returns ObjectExpression
|
|
81
|
+
*/
|
|
82
|
+
export declare function createLocationObject(filename: string, loc: SourceLocation): ObjectExpression;
|
|
84
83
|
/**
|
|
85
84
|
* 获取开发环境下的位置信息对象
|
|
86
85
|
* 仅在开发环境下返回位置信息,生产环境返回 null
|
|
@@ -15,13 +15,13 @@ export function createUnrefCall(argument, alias) {
|
|
|
15
15
|
return t.callExpression(t.identifier(alias || 'unref'), [argument]);
|
|
16
16
|
}
|
|
17
17
|
/**
|
|
18
|
-
* 创建
|
|
18
|
+
* 创建 accessor 调用
|
|
19
19
|
* @param object - 对象表达式
|
|
20
20
|
* @param key - 键表达式
|
|
21
|
-
* @param alias -
|
|
21
|
+
* @param alias - accessor 别名
|
|
22
22
|
* @returns CallExpression
|
|
23
23
|
*/
|
|
24
|
-
export function
|
|
24
|
+
export function createAccessorCall(object, key, alias) {
|
|
25
25
|
let keyArg;
|
|
26
26
|
if (isIdentifier(key)) {
|
|
27
27
|
keyArg = t.stringLiteral(key.name);
|
|
@@ -32,36 +32,26 @@ export function createAccessCall(object, key, alias) {
|
|
|
32
32
|
else {
|
|
33
33
|
keyArg = key;
|
|
34
34
|
}
|
|
35
|
-
return t.callExpression(t.identifier(alias || '
|
|
35
|
+
return t.callExpression(t.identifier(alias || 'accessor'), [object, keyArg]);
|
|
36
36
|
}
|
|
37
37
|
/**
|
|
38
|
-
* 创建
|
|
38
|
+
* 创建 expr 调用
|
|
39
39
|
* @param argument - 参数表达式
|
|
40
|
-
* @param alias -
|
|
41
|
-
* @param locInfo - 位置信息对象
|
|
40
|
+
* @param alias - expr 别名
|
|
42
41
|
* @returns CallExpression
|
|
43
42
|
*/
|
|
44
|
-
export function
|
|
45
|
-
|
|
46
|
-
if (locInfo) {
|
|
47
|
-
args.push(locInfo);
|
|
48
|
-
}
|
|
49
|
-
return t.callExpression(t.identifier(alias || 'dynamic'), args);
|
|
43
|
+
export function createExprCall(argument, alias) {
|
|
44
|
+
return t.callExpression(t.identifier(alias || 'expr'), [t.arrowFunctionExpression([], argument)]);
|
|
50
45
|
}
|
|
51
46
|
/**
|
|
52
47
|
* 创建 branch 调用
|
|
53
48
|
* @param condition - 条件函数
|
|
54
49
|
* @param branches - 分支函数数组
|
|
55
50
|
* @param alias - branch 别名
|
|
56
|
-
* @param locInfo - 位置信息对象
|
|
57
51
|
* @returns CallExpression
|
|
58
52
|
*/
|
|
59
|
-
export function createBranchCall(condition, branches, alias
|
|
60
|
-
|
|
61
|
-
if (locInfo) {
|
|
62
|
-
args.push(locInfo);
|
|
63
|
-
}
|
|
64
|
-
return t.callExpression(t.identifier(alias || 'branch'), args);
|
|
53
|
+
export function createBranchCall(condition, branches, alias) {
|
|
54
|
+
return t.callExpression(t.identifier(alias || 'branch'), [condition, t.arrayExpression(branches)]);
|
|
65
55
|
}
|
|
66
56
|
/**
|
|
67
57
|
* 创建 createView 调用
|
|
@@ -73,15 +63,12 @@ export function createBranchCall(condition, branches, alias, locInfo) {
|
|
|
73
63
|
*/
|
|
74
64
|
export function createCreateViewCall(type, props, locInfo, alias) {
|
|
75
65
|
const args = [type];
|
|
76
|
-
// props 参数
|
|
77
66
|
if (props) {
|
|
78
67
|
args.push(props);
|
|
79
68
|
}
|
|
80
69
|
else if (locInfo) {
|
|
81
|
-
// 如果有 locInfo 但没有 props,需要传 null
|
|
82
70
|
args.push(t.nullLiteral());
|
|
83
71
|
}
|
|
84
|
-
// locInfo 参数
|
|
85
72
|
if (locInfo) {
|
|
86
73
|
args.push(locInfo);
|
|
87
74
|
}
|
|
@@ -111,19 +98,6 @@ export function createWithDirectivesCall(view, directives, alias) {
|
|
|
111
98
|
export function createArrowFunction(body) {
|
|
112
99
|
return t.arrowFunctionExpression([], body);
|
|
113
100
|
}
|
|
114
|
-
/**
|
|
115
|
-
* 创建位置信息对象
|
|
116
|
-
* @param filename - 文件名
|
|
117
|
-
* @param loc - 源码位置
|
|
118
|
-
* @returns ObjectExpression
|
|
119
|
-
*/
|
|
120
|
-
export function createLocationObject(filename, loc) {
|
|
121
|
-
return t.objectExpression([
|
|
122
|
-
t.objectProperty(t.identifier('fileName'), t.stringLiteral(filename)),
|
|
123
|
-
t.objectProperty(t.identifier('lineNumber'), t.numericLiteral(loc.start.line)),
|
|
124
|
-
t.objectProperty(t.identifier('columnNumber'), t.numericLiteral(loc.start.column + 1))
|
|
125
|
-
]);
|
|
126
|
-
}
|
|
127
101
|
/**
|
|
128
102
|
* 为调用表达式添加 @__PURE__ 注释
|
|
129
103
|
* @param node - 调用表达式节点
|
|
@@ -147,6 +121,19 @@ export function addPureComment(node, ctx) {
|
|
|
147
121
|
export function getAlias(aliases, name) {
|
|
148
122
|
return aliases[name] || name;
|
|
149
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* 创建位置信息对象
|
|
126
|
+
* @param filename - 文件名
|
|
127
|
+
* @param loc - 源码位置
|
|
128
|
+
* @returns ObjectExpression
|
|
129
|
+
*/
|
|
130
|
+
export function createLocationObject(filename, loc) {
|
|
131
|
+
return t.objectExpression([
|
|
132
|
+
t.objectProperty(t.identifier('fileName'), t.stringLiteral(filename)),
|
|
133
|
+
t.objectProperty(t.identifier('lineNumber'), t.numericLiteral(loc.start.line)),
|
|
134
|
+
t.objectProperty(t.identifier('columnNumber'), t.numericLiteral(loc.start.column + 1))
|
|
135
|
+
]);
|
|
136
|
+
}
|
|
150
137
|
/**
|
|
151
138
|
* 获取开发环境下的位置信息对象
|
|
152
139
|
* 仅在开发环境下返回位置信息,生产环境返回 null
|
|
@@ -15,8 +15,6 @@ export interface BranchConfig {
|
|
|
15
15
|
branches: t.ArrowFunctionExpression[];
|
|
16
16
|
/** 是否使用 unref 包装标识符条件 */
|
|
17
17
|
useRef?: boolean;
|
|
18
|
-
/** 位置信息对象 */
|
|
19
|
-
locInfo?: t.ObjectExpression | null;
|
|
20
18
|
}
|
|
21
19
|
/**
|
|
22
20
|
* 创建 branch 调用
|
|
@@ -36,7 +34,6 @@ export declare function buildNestedCondition(conditions: t.Expression[], ctx: Tr
|
|
|
36
34
|
* @param consequent - 真值分支
|
|
37
35
|
* @param alternate - 假值分支
|
|
38
36
|
* @param ctx - 转换上下文
|
|
39
|
-
* @param locInfo - 位置信息对象
|
|
40
37
|
* @returns branch 调用表达式
|
|
41
38
|
*/
|
|
42
|
-
export declare function createBinaryBranch(condition: t.Expression, consequent: t.Expression, alternate: t.Expression, ctx: TransformContext
|
|
39
|
+
export declare function createBinaryBranch(condition: t.Expression, consequent: t.Expression, alternate: t.Expression, ctx: TransformContext): t.CallExpression;
|
|
@@ -14,15 +14,14 @@ import { addPureComment, createArrowFunction, createBranchCall, createUnrefCall,
|
|
|
14
14
|
* @returns branch 调用表达式
|
|
15
15
|
*/
|
|
16
16
|
export function createBranch(config, ctx) {
|
|
17
|
-
const { conditions, branches, useRef = true
|
|
17
|
+
const { conditions, branches, useRef = true } = config;
|
|
18
18
|
markImport(ctx, 'branch');
|
|
19
|
-
// 只有在需要 unref 且条件中有标识符时才标记导入
|
|
20
19
|
if (useRef && conditions.some(c => isIdentifier(c))) {
|
|
21
20
|
markImport(ctx, 'unref');
|
|
22
21
|
}
|
|
23
22
|
const conditionExpr = buildNestedCondition(conditions, ctx, useRef);
|
|
24
23
|
const branchAlias = getAlias(ctx.vitarxAliases, 'branch');
|
|
25
|
-
return addPureComment(createBranchCall(createArrowFunction(conditionExpr), branches, branchAlias
|
|
24
|
+
return addPureComment(createBranchCall(createArrowFunction(conditionExpr), branches, branchAlias), ctx);
|
|
26
25
|
}
|
|
27
26
|
/**
|
|
28
27
|
* 判断表达式是否需要括号包裹
|
|
@@ -85,13 +84,11 @@ export function buildNestedCondition(conditions, ctx, useRef = true) {
|
|
|
85
84
|
* @param consequent - 真值分支
|
|
86
85
|
* @param alternate - 假值分支
|
|
87
86
|
* @param ctx - 转换上下文
|
|
88
|
-
* @param locInfo - 位置信息对象
|
|
89
87
|
* @returns branch 调用表达式
|
|
90
88
|
*/
|
|
91
|
-
export function createBinaryBranch(condition, consequent, alternate, ctx
|
|
89
|
+
export function createBinaryBranch(condition, consequent, alternate, ctx) {
|
|
92
90
|
const conditions = [condition, t.booleanLiteral(true)];
|
|
93
91
|
const branches = [createArrowFunction(consequent), createArrowFunction(alternate)];
|
|
94
|
-
// 只有条件是 Identifier 时才需要 unref
|
|
95
92
|
const useRef = isIdentifier(condition);
|
|
96
|
-
return createBranch({ conditions, branches, useRef
|
|
93
|
+
return createBranch({ conditions, branches, useRef }, ctx);
|
|
97
94
|
}
|
package/dist/utils/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { isWhitespaceJSXText } from './ast-guards.js';
|
|
2
2
|
export { getJSXElementName, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren, validateNoDirectMatchChild } from './jsx-helpers.js';
|
|
3
|
-
export { createUnrefCall,
|
|
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';
|
package/dist/utils/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { isWhitespaceJSXText } from './ast-guards.js';
|
|
|
3
3
|
// JSX 辅助函数
|
|
4
4
|
export { getJSXElementName, isPureCompileComponent, isComponent, isNativeElement, getJSXAttributeByName, hasDirective, getDirectiveValue, isVIfChain, isVIf, isVElseIf, isVElse, removeVDirectives, removeVIfChainDirectives, removeAttribute, filterWhitespaceChildren, hasEffectiveChildren, filterEffectiveChildren, validateNoDirectMatchChild } from './jsx-helpers.js';
|
|
5
5
|
// AST 构建函数
|
|
6
|
-
export { createUnrefCall,
|
|
6
|
+
export { createUnrefCall, createAccessorCall, createExprCall, createBranchCall, createCreateViewCall, createWithDirectivesCall, createArrowFunction, createLocationObject, addPureComment, getAlias, getDevLocInfo } from './ast-builders.js';
|
|
7
7
|
// 模式处理辅助函数
|
|
8
8
|
export { collectPatternBindings, collectObjectPatternBindings } from './pattern-helpers.js';
|
|
9
9
|
// Branch 工厂
|
|
@@ -26,10 +26,13 @@ export declare function isNativeElement(name: string): boolean;
|
|
|
26
26
|
export declare function getJSXAttributeByName(node: JSXElement, name: string): JSXAttribute | undefined;
|
|
27
27
|
/**
|
|
28
28
|
* 检查元素是否具有指定指令
|
|
29
|
+
* v-if/v-else-if/v-else 仅支持 JSXIdentifier 格式(v-if),不支持命名空间格式(v:if)
|
|
30
|
+
* 其他指令(如 v-show)支持 v-xxx:arg 命名空间格式
|
|
29
31
|
*/
|
|
30
32
|
export declare function hasDirective(node: JSXElement, directiveName: string): boolean;
|
|
31
33
|
/**
|
|
32
34
|
* 获取指令的值
|
|
35
|
+
* v-if/v-else-if/v-else 仅支持 JSXIdentifier 格式(v-if),不支持命名空间格式(v:if)
|
|
33
36
|
*/
|
|
34
37
|
export declare function getDirectiveValue(node: JSXElement, directiveName: string): Expression | null;
|
|
35
38
|
/**
|
|
@@ -54,6 +57,7 @@ export declare function isVElse(node: JSXElement): boolean;
|
|
|
54
57
|
export declare function removeVDirectives(node: JSXElement): void;
|
|
55
58
|
/**
|
|
56
59
|
* 移除元素上的 v-if 链指令(v-if、v-else-if、v-else)
|
|
60
|
+
* 仅处理 JSXIdentifier 格式,不支持命名空间格式(v:if)
|
|
57
61
|
*/
|
|
58
62
|
export declare function removeVIfChainDirectives(node: JSXElement): void;
|
|
59
63
|
/**
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
import * as t from '@babel/types';
|
|
6
6
|
import { isJSXElement, isJSXText } from '@babel/types';
|
|
7
7
|
import { DIRECTIVE_PREFIX, PURE_COMPILE_COMPONENTS } from '../constants/index.js';
|
|
8
|
-
import { isWhitespaceJSXText } from './ast-guards.js';
|
|
9
8
|
import { createError } from '../error.js';
|
|
9
|
+
import { isWhitespaceJSXText } from './ast-guards.js';
|
|
10
10
|
/**
|
|
11
11
|
* 获取 JSX 元素的名称
|
|
12
12
|
*/
|
|
@@ -45,14 +45,19 @@ export function getJSXAttributeByName(node, name) {
|
|
|
45
45
|
}
|
|
46
46
|
return undefined;
|
|
47
47
|
}
|
|
48
|
+
const V_IF_CHAIN_DIRECTIVES = new Set(['v-if', 'v-else-if', 'v-else']);
|
|
48
49
|
/**
|
|
49
50
|
* 检查元素是否具有指定指令
|
|
51
|
+
* v-if/v-else-if/v-else 仅支持 JSXIdentifier 格式(v-if),不支持命名空间格式(v:if)
|
|
52
|
+
* 其他指令(如 v-show)支持 v-xxx:arg 命名空间格式
|
|
50
53
|
*/
|
|
51
54
|
export function hasDirective(node, directiveName) {
|
|
52
55
|
for (const attr of node.openingElement.attributes) {
|
|
53
56
|
if (attr.type === 'JSXAttribute') {
|
|
54
57
|
const attrName = attr.name;
|
|
55
58
|
if (attrName.type === 'JSXNamespacedName') {
|
|
59
|
+
if (V_IF_CHAIN_DIRECTIVES.has(directiveName))
|
|
60
|
+
continue;
|
|
56
61
|
if (attrName.namespace.name === 'v' && attrName.name.name === directiveName.slice(2)) {
|
|
57
62
|
return true;
|
|
58
63
|
}
|
|
@@ -66,6 +71,7 @@ export function hasDirective(node, directiveName) {
|
|
|
66
71
|
}
|
|
67
72
|
/**
|
|
68
73
|
* 获取指令的值
|
|
74
|
+
* v-if/v-else-if/v-else 仅支持 JSXIdentifier 格式(v-if),不支持命名空间格式(v:if)
|
|
69
75
|
*/
|
|
70
76
|
export function getDirectiveValue(node, directiveName) {
|
|
71
77
|
for (const attr of node.openingElement.attributes) {
|
|
@@ -73,6 +79,8 @@ export function getDirectiveValue(node, directiveName) {
|
|
|
73
79
|
const attrName = attr.name;
|
|
74
80
|
let matches = false;
|
|
75
81
|
if (attrName.type === 'JSXNamespacedName') {
|
|
82
|
+
if (V_IF_CHAIN_DIRECTIVES.has(directiveName))
|
|
83
|
+
continue;
|
|
76
84
|
matches = attrName.namespace.name === 'v' && attrName.name.name === directiveName.slice(2);
|
|
77
85
|
}
|
|
78
86
|
else if (attrName.type === 'JSXIdentifier') {
|
|
@@ -129,19 +137,14 @@ export function removeVDirectives(node) {
|
|
|
129
137
|
}
|
|
130
138
|
/**
|
|
131
139
|
* 移除元素上的 v-if 链指令(v-if、v-else-if、v-else)
|
|
140
|
+
* 仅处理 JSXIdentifier 格式,不支持命名空间格式(v:if)
|
|
132
141
|
*/
|
|
133
142
|
export function removeVIfChainDirectives(node) {
|
|
134
|
-
const vIfChainDirectives = ['v-if', 'v-else-if', 'v-else'];
|
|
135
143
|
node.openingElement.attributes = node.openingElement.attributes.filter(attr => {
|
|
136
144
|
if (attr.type === 'JSXAttribute') {
|
|
137
145
|
const attrName = attr.name;
|
|
138
|
-
if (attrName.type === '
|
|
139
|
-
|
|
140
|
-
return !vIfChainDirectives.includes(`v-${attrName.name.name}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
else if (attrName.type === 'JSXIdentifier') {
|
|
144
|
-
return !vIfChainDirectives.includes(attrName.name);
|
|
146
|
+
if (attrName.type === 'JSXIdentifier') {
|
|
147
|
+
return !V_IF_CHAIN_DIRECTIVES.has(attrName.name);
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
150
|
return true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitarx/plugin-vite",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "The official plugin for Vitarx support in Vite.",
|
|
5
5
|
"author": "ZhuChongLin <8210856@qq.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"rimraf": "^6.1.3",
|
|
61
61
|
"vitest": "^4.0.18",
|
|
62
62
|
"vite": "^8.0.5",
|
|
63
|
-
"vitarx": "^4.0.0-beta.
|
|
63
|
+
"vitarx": "^4.0.0-beta.16"
|
|
64
64
|
},
|
|
65
65
|
"scripts": {
|
|
66
66
|
"build": "vitest run && rimraf dist && tsc -p tsconfig.build.json",
|