@vitarx/plugin-vite 0.0.1-alpha.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 (42) hide show
  1. package/LICENSE +27 -0
  2. package/README.md +298 -0
  3. package/dist/components.js +1 -0
  4. package/dist/constants/index.js +59 -0
  5. package/dist/context.js +78 -0
  6. package/dist/error.js +156 -0
  7. package/dist/hmr-client/index.js +143 -0
  8. package/dist/hmr-client/update.js +53 -0
  9. package/dist/hmr-client/utils.js +125 -0
  10. package/dist/index.js +65 -0
  11. package/dist/passes/components/ifBlock.js +42 -0
  12. package/dist/passes/components/index.js +24 -0
  13. package/dist/passes/components/switch.js +102 -0
  14. package/dist/passes/directives/index.js +6 -0
  15. package/dist/passes/directives/processDirectives.js +46 -0
  16. package/dist/passes/directives/vIf.js +64 -0
  17. package/dist/passes/hmr/index.js +5 -0
  18. package/dist/passes/hmr/inject.js +189 -0
  19. package/dist/passes/imports/collectImports.js +56 -0
  20. package/dist/passes/imports/collectRefVariables.js +95 -0
  21. package/dist/passes/imports/index.js +7 -0
  22. package/dist/passes/imports/injectImports.js +96 -0
  23. package/dist/passes/index.js +16 -0
  24. package/dist/passes/jsx/index.js +7 -0
  25. package/dist/passes/jsx/processChildren.js +114 -0
  26. package/dist/passes/jsx/processJSXElement.js +173 -0
  27. package/dist/passes/jsx/processJSXFragment.js +37 -0
  28. package/dist/passes/props/attribute.js +165 -0
  29. package/dist/passes/props/index.js +94 -0
  30. package/dist/passes/props/types.js +1 -0
  31. package/dist/passes/props/vmodel.js +115 -0
  32. package/dist/transform.js +144 -0
  33. package/dist/utils/ast-builders.js +142 -0
  34. package/dist/utils/ast-guards.js +16 -0
  35. package/dist/utils/branch-factory.js +107 -0
  36. package/dist/utils/component-collect.js +233 -0
  37. package/dist/utils/generate.js +11 -0
  38. package/dist/utils/index.js +16 -0
  39. package/dist/utils/jsx-helpers.js +168 -0
  40. package/dist/utils/pattern-helpers.js +47 -0
  41. package/dist/utils/vif-helpers.js +127 -0
  42. package/package.json +63 -0
@@ -0,0 +1,173 @@
1
+ import * as t from '@babel/types';
2
+ import { isJSXElement, isJSXExpressionContainer, isJSXText } from '@babel/types';
3
+ import { addWarning, markImport } from '../../context.js';
4
+ import { createError, createWarning } from '../../error.js';
5
+ import { addPureComment, createArrowFunction, createBranch, createCreateViewCall, createLocationObject, getAlias, getDirectiveValue, getJSXAttributeByName, getJSXElementName, isNativeElement, isVElse, isVIf, isVIfChain, removeVDirectives } from '../../utils/index.js';
6
+ import { processDirectives } from '../directives/index.js';
7
+ import { processProps } from '../props/index.js';
8
+ import { processChildren } from './processChildren.js';
9
+ /**
10
+ * 处理 JSX 元素
11
+ * @param path - JSX 元素路径
12
+ * @param ctx - 转换上下文
13
+ */
14
+ export function processJSXElement(path, ctx) {
15
+ const name = getJSXElementName(path.node);
16
+ // 校验 Match 必须在 Switch 内使用
17
+ if (name === 'Match') {
18
+ validateMatchInSwitch(path);
19
+ }
20
+ const result = transformJSXElement(path.node, ctx, true);
21
+ if (result) {
22
+ path.replaceWith(result);
23
+ }
24
+ }
25
+ /**
26
+ * 校验 Match 组件必须在 Switch 内使用
27
+ */
28
+ function validateMatchInSwitch(path) {
29
+ const parent = path.parentPath;
30
+ if (!parent) {
31
+ throw createError('E012', path.node);
32
+ }
33
+ // 检查父元素是否是 Switch
34
+ if (parent.node.type === 'JSXElement') {
35
+ const parentName = getJSXElementName(parent.node);
36
+ if (parentName === 'Switch') {
37
+ return;
38
+ }
39
+ }
40
+ // 检查是否在 Fragment 内且 Fragment 的父元素是 Switch
41
+ if (parent.node.type === 'JSXFragment' || parent.node.type === 'JSXElement') {
42
+ const grandParent = parent.parentPath;
43
+ if (grandParent?.node.type === 'JSXElement') {
44
+ const grandParentName = getJSXElementName(grandParent.node);
45
+ if (grandParentName === 'Switch') {
46
+ return;
47
+ }
48
+ }
49
+ }
50
+ throw createError('E012', path.node);
51
+ }
52
+ /**
53
+ * 转换 JSX 元素为表达式
54
+ * @param node - JSX 元素节点
55
+ * @param ctx - 转换上下文
56
+ * @param handleVIf - 是否处理 v-if 指令
57
+ * @returns 转换后的表达式
58
+ */
59
+ export function transformJSXElement(node, ctx, handleVIf = false) {
60
+ const name = getJSXElementName(node);
61
+ if (!name)
62
+ return null;
63
+ // 处理 v-if 链
64
+ if (handleVIf && isVIfChain(node)) {
65
+ if (isVIf(node)) {
66
+ return transformSingleVIf(node, ctx);
67
+ }
68
+ else {
69
+ throw createError(isVElse(node) ? 'E003' : 'E004', node);
70
+ }
71
+ }
72
+ // 确定元素类型
73
+ const type = isNativeElement(name) ? t.stringLiteral(name) : t.identifier(name);
74
+ // 检测是否有有效子元素
75
+ const hasChildren = node.children.some(child => {
76
+ if (isJSXText(child)) {
77
+ return child.value.trim().length > 0;
78
+ }
79
+ if (isJSXExpressionContainer(child)) {
80
+ return child.expression.type !== 'JSXEmptyExpression';
81
+ }
82
+ return true;
83
+ });
84
+ // 处理属性(有子元素时跳过 children 属性)
85
+ const { props, directives } = processProps(node, ctx, hasChildren);
86
+ // 处理子元素
87
+ const finalProps = processElementChildren(node, props, ctx);
88
+ // 生成 createView 调用
89
+ markImport(ctx, 'createView');
90
+ const createViewAlias = getAlias(ctx.vitarxAliases, 'createView');
91
+ const locInfo = ctx.options.dev && node.loc ? createLocationObject(ctx.filename, node.loc) : null;
92
+ let viewCall = createCreateViewCall(type, finalProps, locInfo, createViewAlias);
93
+ // 处理指令
94
+ if (directives.size > 0) {
95
+ viewCall = processDirectives(viewCall, directives, ctx);
96
+ }
97
+ else {
98
+ viewCall = addPureComment(viewCall);
99
+ }
100
+ if (node.loc) {
101
+ viewCall.loc = node.loc;
102
+ }
103
+ return viewCall;
104
+ }
105
+ /**
106
+ * 处理元素的子元素
107
+ * 子元素优先于 children 属性,同时发出警告
108
+ */
109
+ function processElementChildren(node, props, ctx) {
110
+ // 过滤有效子元素
111
+ const children = node.children.filter(child => {
112
+ if (isJSXText(child)) {
113
+ return child.value.trim().length > 0;
114
+ }
115
+ if (isJSXExpressionContainer(child)) {
116
+ return child.expression.type !== 'JSXEmptyExpression';
117
+ }
118
+ return true;
119
+ });
120
+ if (children.length === 0) {
121
+ return props;
122
+ }
123
+ // 检查是否存在 children 属性
124
+ const childrenAttr = getJSXAttributeByName(node, 'children');
125
+ if (childrenAttr) {
126
+ // 发出警告:children 属性和子元素同时存在
127
+ addWarning(ctx, createWarning('W001', childrenAttr));
128
+ }
129
+ // 校验 Match 组件必须在 Switch 内使用
130
+ // 当前元素不是 Switch 时,检查子元素中是否有 Match
131
+ const currentName = getJSXElementName(node);
132
+ if (currentName !== 'Switch') {
133
+ for (const child of children) {
134
+ if (isJSXElement(child)) {
135
+ const childName = getJSXElementName(child);
136
+ if (childName === 'Match') {
137
+ throw createError('E012', child);
138
+ }
139
+ }
140
+ }
141
+ }
142
+ // 处理子元素
143
+ const processedChildren = processChildren(children, ctx);
144
+ const childrenValue = processedChildren.length === 1 ? processedChildren[0] : t.arrayExpression(processedChildren);
145
+ // 添加 children 属性
146
+ if (props) {
147
+ props.properties.push(t.objectProperty(t.identifier('children'), childrenValue));
148
+ return props;
149
+ }
150
+ return t.objectExpression([t.objectProperty(t.identifier('children'), childrenValue)]);
151
+ }
152
+ /**
153
+ * 转换单个 v-if 元素
154
+ */
155
+ function transformSingleVIf(node, ctx) {
156
+ if (!isVIf(node))
157
+ return null;
158
+ const condition = getDirectiveValue(node, 'v-if');
159
+ if (!condition)
160
+ return null;
161
+ // 移除 v- 指令
162
+ removeVDirectives(node);
163
+ // 转换元素
164
+ const transformedNode = transformJSXElement(node, ctx, false);
165
+ if (!transformedNode)
166
+ return null;
167
+ // 生成 branch 调用
168
+ const branchCall = createBranch({ conditions: [condition], branches: [createArrowFunction(transformedNode)] }, ctx);
169
+ if (node.loc) {
170
+ branchCall.loc = node.loc;
171
+ }
172
+ return branchCall;
173
+ }
@@ -0,0 +1,37 @@
1
+ import * as t from '@babel/types';
2
+ import { addPureComment, createCreateViewCall, createLocationObject, filterWhitespaceChildren, getAlias, validateMatchInSwitch } from '../../utils/index.js';
3
+ import { processChildren } from './processChildren.js';
4
+ /**
5
+ * 处理 JSX Fragment
6
+ * @param path - JSX Fragment 路径
7
+ * @param ctx - 转换上下文
8
+ */
9
+ export function processJSXFragment(path, ctx) {
10
+ const node = path.node;
11
+ // 过滤空白子节点
12
+ const children = filterWhitespaceChildren(node.children);
13
+ // 校验 Match 组件
14
+ validateMatchInSwitch(children);
15
+ // 标记需要的导入
16
+ ctx.imports.Fragment = true;
17
+ ctx.imports.createView = true;
18
+ const fragmentAlias = getAlias(ctx.vitarxAliases, 'Fragment');
19
+ const createViewAlias = getAlias(ctx.vitarxAliases, 'createView');
20
+ const locInfo = ctx.options.dev && node.loc ? createLocationObject(ctx.filename, node.loc) : null;
21
+ // 无子元素
22
+ if (children.length === 0) {
23
+ const viewCall = addPureComment(createCreateViewCall(t.identifier(fragmentAlias), null, locInfo, createViewAlias));
24
+ if (node.loc)
25
+ viewCall.loc = node.loc;
26
+ path.replaceWith(viewCall);
27
+ return;
28
+ }
29
+ // 处理子元素
30
+ const processedChildren = processChildren(children, ctx);
31
+ const childrenValue = processedChildren.length === 1 ? processedChildren[0] : t.arrayExpression(processedChildren);
32
+ const props = t.objectExpression([t.objectProperty(t.identifier('children'), childrenValue)]);
33
+ const viewCall = addPureComment(createCreateViewCall(t.identifier(fragmentAlias), props, locInfo, createViewAlias));
34
+ if (node.loc)
35
+ viewCall.loc = node.loc;
36
+ path.replaceWith(viewCall);
37
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * 属性处理模块
3
+ * 负责处理 JSX 元素的各类属性,包括普通属性、展开属性、命名空间属性等
4
+ * @module passes/props/attribute
5
+ */
6
+ import * as t from '@babel/types';
7
+ import { isBooleanLiteral, isIdentifier, isJSXExpressionContainer, isNumericLiteral, isStringLiteral } from '@babel/types';
8
+ import { markImport } from '../../context.js';
9
+ import { createError } from '../../error.js';
10
+ import { getAlias } from '../../utils/index.js';
11
+ /**
12
+ * 处理展开属性 {...props}
13
+ * 将展开属性转换为 v-bind 特殊属性格式
14
+ * @param attr - JSX 展开属性节点
15
+ * @param hasVBind - 是否已存在 v-bind
16
+ * @param node - JSX 元素节点(用于错误定位)
17
+ * @returns 转换后的属性对象,或 null(如果已存在 v-bind)
18
+ * @throws {Error} E001 - 当已存在 v-bind 时抛出
19
+ */
20
+ export function processSpreadAttribute(attr, hasVBind, node) {
21
+ // 不允许多个 v-bind
22
+ if (hasVBind) {
23
+ throw createError('E001', node);
24
+ }
25
+ return {
26
+ property: t.objectProperty(t.stringLiteral('v-bind'), attr.argument),
27
+ hasVBind: true
28
+ };
29
+ }
30
+ /**
31
+ * 处理单个 JSX 属性
32
+ * 根据属性名类型分发到不同的处理逻辑
33
+ * @param attr - JSX 属性节点
34
+ * @param existingPropNames - 已存在的属性名集合
35
+ * @param ctx - 转换上下文
36
+ * @returns 属性处理结果
37
+ */
38
+ export function processAttribute(attr, existingPropNames, ctx) {
39
+ const attrName = attr.name;
40
+ // 处理命名空间属性 (v:xxx)
41
+ if (attrName.type === 'JSXNamespacedName') {
42
+ return processNamespacedAttribute(attr, attrName, existingPropNames, ctx);
43
+ }
44
+ // 处理标识符属性
45
+ if (attrName.type === 'JSXIdentifier') {
46
+ const name = attrName.name;
47
+ // v-bind 指令
48
+ if (name === 'v-bind') {
49
+ const value = getAttributeValue(attr.value);
50
+ return { type: 'directive', name: 'v-bind', value, isVBind: true, isVModel: false };
51
+ }
52
+ // v-model 指令
53
+ if (name === 'v-model') {
54
+ const value = getAttributeValue(attr.value);
55
+ return { type: 'directive', name: 'v-model', value, isVBind: false, isVModel: true };
56
+ }
57
+ // 其他 v- 指令(排除 v-if 系列,它们在单独的处理流程中)
58
+ if (name.startsWith('v-') && !['v-if', 'v-else-if', 'v-else'].includes(name)) {
59
+ const value = getAttributeValue(attr.value);
60
+ return { type: 'directive', name, value, isVBind: false, isVModel: false };
61
+ }
62
+ // 普通属性
63
+ existingPropNames.add(name);
64
+ const value = getAttributeValue(attr.value);
65
+ const property = createProperty(name, value, ctx);
66
+ return { type: 'property', property };
67
+ }
68
+ // 未知属性类型,作为普通属性处理
69
+ const value = getAttributeValue(attr.value);
70
+ return { type: 'property', property: t.objectProperty(t.identifier('unknown'), value) };
71
+ }
72
+ /**
73
+ * 处理命名空间属性
74
+ * 支持 v:xxx 格式的指令语法
75
+ * @param attr - JSX 属性节点
76
+ * @param attrName - 命名空间名称
77
+ * @param existingPropNames - 已存在的属性名集合
78
+ * @param ctx - 转换上下文
79
+ * @returns 属性处理结果
80
+ */
81
+ function processNamespacedAttribute(attr, attrName, existingPropNames, ctx) {
82
+ const namespace = attrName.namespace.name;
83
+ const name = attrName.name.name;
84
+ const fullName = `${namespace}:${name}`;
85
+ // v:xxx 指令
86
+ if (namespace === 'v') {
87
+ const directiveName = `v-${name}`;
88
+ const value = getAttributeValue(attr.value);
89
+ // v:bind 等同于 v-bind
90
+ if (directiveName === 'v-bind') {
91
+ return { type: 'directive', name: 'v-bind', value, isVBind: true, isVModel: false };
92
+ }
93
+ // 其他指令(排除 v-if 系列和 v-model)
94
+ if (!['v-if', 'v-else-if', 'v-else', 'v-model'].includes(directiveName)) {
95
+ return { type: 'directive', name: directiveName, value, isVBind: false, isVModel: false };
96
+ }
97
+ }
98
+ // 其他命名空间属性作为普通属性处理
99
+ existingPropNames.add(fullName);
100
+ const value = getAttributeValue(attr.value);
101
+ const property = createProperty(fullName, value, ctx);
102
+ return { type: 'property', property };
103
+ }
104
+ /**
105
+ * 获取属性值表达式
106
+ * 处理不同类型的属性值,统一返回表达式
107
+ * @param value - JSX 属性值
108
+ * @returns 表达式节点
109
+ */
110
+ function getAttributeValue(value) {
111
+ // 无值属性默认为 true
112
+ if (!value) {
113
+ return t.booleanLiteral(true);
114
+ }
115
+ // 字符串字面量直接返回
116
+ if (isStringLiteral(value)) {
117
+ return value;
118
+ }
119
+ // JSX 表达式容器
120
+ if (isJSXExpressionContainer(value)) {
121
+ // 空表达式 {} 视为 true
122
+ if (value.expression.type === 'JSXEmptyExpression') {
123
+ return t.booleanLiteral(true);
124
+ }
125
+ return value.expression;
126
+ }
127
+ return value;
128
+ }
129
+ /**
130
+ * 创建对象属性节点
131
+ * 静态值直接赋值,动态值生成 getter 方法
132
+ *
133
+ * 特殊处理:
134
+ * - children 属性不使用 unref,因为 ref 作为 children 是合法的可变渲染源
135
+ * - 其他标识符属性使用 unref 解包
136
+ *
137
+ * @param key - 属性名
138
+ * @param value - 属性值表达式
139
+ * @param ctx - 转换上下文
140
+ * @returns 对象属性或方法节点
141
+ */
142
+ function createProperty(key, value, ctx) {
143
+ // 静态值:直接赋值
144
+ if (isStringLiteral(value) || isNumericLiteral(value) || isBooleanLiteral(value)) {
145
+ return t.objectProperty(t.identifier(key), value);
146
+ }
147
+ // Identifier: 检查是否为已知的 ref 变量
148
+ if (isIdentifier(value)) {
149
+ // 已知 ref 变量:直接访问 .value
150
+ if (ctx.refVariables.has(value.name)) {
151
+ return t.objectMethod('get', t.identifier(key), [], t.blockStatement([t.returnStatement(t.memberExpression(value, t.identifier('value')))]));
152
+ }
153
+ // children 属性:直接引用,不使用 unref
154
+ // 原因:children 中的 ref 是合法的可变渲染源
155
+ if (key === 'children') {
156
+ return t.objectProperty(t.identifier(key), value);
157
+ }
158
+ // 其他未知变量:使用 unref 进行解包
159
+ markImport(ctx, 'unref');
160
+ const unrefAlias = getAlias(ctx.vitarxAliases, 'unref');
161
+ return t.objectMethod('get', t.identifier(key), [], t.blockStatement([t.returnStatement(t.callExpression(t.identifier(unrefAlias), [value]))]));
162
+ }
163
+ // 其他表达式:直接返回(保持响应性)
164
+ return t.objectMethod('get', t.identifier(key), [], t.blockStatement([t.returnStatement(value)]));
165
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * Props 处理模块入口
3
+ * 负责处理 JSX 元素的属性,包括 v-model 特殊指令
4
+ * @module passes/props
5
+ */
6
+ import * as t from '@babel/types';
7
+ import { processAttribute, processSpreadAttribute } from './attribute.js';
8
+ import { createVModelProps, extractVModelState } from './vmodel.js';
9
+ /**
10
+ * 处理 JSX 元素的属性
11
+ * 将 JSX 属性转换为运行时 props 对象,并提取指令
12
+ *
13
+ * 处理流程:
14
+ * 1. 遍历所有属性,分类处理
15
+ * 2. 展开属性转换为 v-bind 格式
16
+ * 3. 普通属性转换为对象属性或 getter
17
+ * 4. 指令属性提取到 directives Map
18
+ * 5. v-model 特殊处理,生成 modelValue 和 onUpdate:modelValue
19
+ *
20
+ * @param node - JSX 元素节点
21
+ * @param ctx - 转换上下文
22
+ * @param hasChildren - 是否有子元素(有子元素时跳过 children 属性)
23
+ * @returns 处理结果,包含 props 对象、指令映射和 v-bind 标记
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * // 输入
28
+ * <Input v-model={value} placeholder="text" disabled />
29
+ *
30
+ * // 输出 props
31
+ * {
32
+ * modelValue: get() { return value.value },
33
+ * 'onUpdate:modelValue': v => value.value = v,
34
+ * placeholder: "text",
35
+ * disabled: true
36
+ * }
37
+ * ```
38
+ */
39
+ export function processProps(node, ctx, hasChildren = false) {
40
+ const attributes = node.openingElement.attributes;
41
+ const properties = [];
42
+ const directives = new Map();
43
+ let hasVBind = false;
44
+ // 追踪已存在的属性名(用于 v-model 冲突检测)
45
+ const existingPropNames = new Set();
46
+ // 提取初始 v-model 状态
47
+ let vModelState = extractVModelState(attributes);
48
+ // 遍历处理所有属性
49
+ for (const attr of attributes) {
50
+ // 处理展开属性 {...props}
51
+ if (attr.type === 'JSXSpreadAttribute') {
52
+ const result = processSpreadAttribute(attr, hasVBind, node);
53
+ if (result) {
54
+ properties.push(result.property);
55
+ hasVBind = result.hasVBind;
56
+ }
57
+ continue;
58
+ }
59
+ // 处理普通属性
60
+ if (attr.type === 'JSXAttribute') {
61
+ // 如果有子元素,跳过 children 属性
62
+ if (hasChildren && attr.name.type === 'JSXIdentifier' && attr.name.name === 'children') {
63
+ continue;
64
+ }
65
+ const result = processAttribute(attr, existingPropNames, ctx);
66
+ if (result.type === 'directive') {
67
+ // v-model 指令:更新状态
68
+ if (result.isVModel) {
69
+ vModelState = { hasVModel: true, value: result.value, node: attr };
70
+ }
71
+ // v-bind 指令:添加到属性列表
72
+ else if (result.isVBind) {
73
+ properties.push(t.objectProperty(t.stringLiteral('v-bind'), result.value));
74
+ hasVBind = true;
75
+ }
76
+ // 其他指令:添加到指令映射
77
+ else {
78
+ directives.set(result.name, result.value);
79
+ }
80
+ }
81
+ else if (result.type === 'property') {
82
+ properties.push(result.property);
83
+ }
84
+ }
85
+ }
86
+ // 处理 v-model:生成 modelValue 和 onUpdate:modelValue
87
+ if (vModelState.hasVModel && vModelState.value) {
88
+ const vModelProps = createVModelProps(vModelState.value, vModelState.node, existingPropNames, node, ctx);
89
+ properties.push(...vModelProps);
90
+ }
91
+ // 构建最终结果
92
+ const props = properties.length > 0 ? t.objectExpression(properties) : null;
93
+ return { props, directives, hasVBind };
94
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ /**
2
+ * v-model 指令处理模块
3
+ * 负责处理 JSX 元素的 v-model 双向绑定指令
4
+ * @module passes/props/vmodel
5
+ */
6
+ import * as t from '@babel/types';
7
+ import { isIdentifier, isMemberExpression } from '@babel/types';
8
+ import { markImport } from '../../context.js';
9
+ import { createError } from '../../error.js';
10
+ import { getAlias } from '../../utils/index.js';
11
+ /**
12
+ * 从属性列表中提取 v-model 状态
13
+ * @param attributes - JSX 元素属性列表
14
+ * @returns v-model 状态对象
15
+ */
16
+ export function extractVModelState(attributes) {
17
+ for (const attr of attributes) {
18
+ if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier') {
19
+ if (attr.name.name === 'v-model') {
20
+ const value = attr.value?.type === 'JSXExpressionContainer'
21
+ ? attr.value.expression
22
+ : null;
23
+ return { hasVModel: true, value, node: attr };
24
+ }
25
+ }
26
+ }
27
+ return { hasVModel: false, value: null, node: null };
28
+ }
29
+ /**
30
+ * 创建 v-model 相关的 props
31
+ * 生成 modelValue getter 和 onUpdate:modelValue 回调
32
+ * @param value - v-model 绑定的值表达式
33
+ * @param attrNode - v-model 属性节点(用于错误定位)
34
+ * @param existingPropNames - 已存在的属性名集合(用于冲突检测)
35
+ * @param node - JSX 元素节点
36
+ * @param ctx - 转换上下文
37
+ * @returns 包含 modelValue 和 onUpdate:modelValue 的属性数组
38
+ * @throws {Error} E009 - 当 modelValue 或 onUpdate:modelValue 已存在时
39
+ * @throws {Error} E010 - 当 v-model 绑定值不是 Identifier 或 MemberExpression 时
40
+ */
41
+ export function createVModelProps(value, attrNode, existingPropNames, node, ctx) {
42
+ // 检查属性名冲突
43
+ if (existingPropNames.has('modelValue') || existingPropNames.has('onUpdate:modelValue')) {
44
+ throw createError('E009', attrNode || node);
45
+ }
46
+ // v-model 只支持 Identifier 和 MemberExpression
47
+ if (!isIdentifier(value) && !isMemberExpression(value)) {
48
+ throw createError('E010', attrNode || node);
49
+ }
50
+ // Identifier: 可能是 ref 变量
51
+ if (isIdentifier(value)) {
52
+ return createIdentifierVModelProps(value, attrNode || node, ctx);
53
+ }
54
+ // MemberExpression: 直接访问属性
55
+ return createMemberExpressionVModelProps(value);
56
+ }
57
+ /**
58
+ * 为 Identifier 类型的 v-model 值创建 props
59
+ * 支持已知 ref 变量和未知变量两种情况
60
+ * @param value - Identifier 值
61
+ * @param node - AST 节点(用于错误定位)
62
+ * @param ctx - 转换上下文
63
+ * @returns 属性数组
64
+ */
65
+ function createIdentifierVModelProps(value, node, ctx) {
66
+ const isKnownRef = ctx.refVariables.has(value.name);
67
+ // 已知 ref 变量:直接访问 .value
68
+ if (isKnownRef) {
69
+ return [
70
+ // modelValue getter: 返回 value.value
71
+ t.objectMethod('get', t.identifier('modelValue'), [], t.blockStatement([t.returnStatement(t.memberExpression(value, t.identifier('value')))])),
72
+ // onUpdate:modelValue: v => value.value = v
73
+ t.objectProperty(t.stringLiteral('onUpdate:modelValue'), t.arrowFunctionExpression([t.identifier('v')], t.assignmentExpression('=', t.memberExpression(value, t.identifier('value')), t.identifier('v'))))
74
+ ];
75
+ }
76
+ // 未知变量:使用 unref 进行解包
77
+ markImport(ctx, 'unref');
78
+ const unrefAlias = getAlias(ctx.vitarxAliases, 'unref');
79
+ const updateBody = [];
80
+ // 开发模式:添加 isRef 检查
81
+ if (ctx.options.dev) {
82
+ markImport(ctx, 'isRef');
83
+ const isRefAlias = getAlias(ctx.vitarxAliases, 'isRef');
84
+ const locInfo = node.loc
85
+ ? ` at ${ctx.filename}:${node.loc.start.line}:${node.loc.start.column + 1}`
86
+ : '';
87
+ updateBody.push(t.ifStatement(t.unaryExpression('!', t.callExpression(t.identifier(isRefAlias), [value])), t.blockStatement([
88
+ t.throwStatement(t.newExpression(t.identifier('Error'), [
89
+ t.stringLiteral(`[v-model] Identifier must be a ref. Invalid usage${locInfo}`)
90
+ ]))
91
+ ])));
92
+ }
93
+ // 更新逻辑:value.value = v
94
+ updateBody.push(t.expressionStatement(t.assignmentExpression('=', t.memberExpression(value, t.identifier('value')), t.identifier('v'))));
95
+ return [
96
+ // modelValue getter: 返回 unref(value)
97
+ t.objectMethod('get', t.identifier('modelValue'), [], t.blockStatement([t.returnStatement(t.callExpression(t.identifier(unrefAlias), [value]))])),
98
+ // onUpdate:modelValue: v => { [isRef检查]; value.value = v }
99
+ t.objectProperty(t.stringLiteral('onUpdate:modelValue'), t.arrowFunctionExpression([t.identifier('v')], t.blockStatement(updateBody)))
100
+ ];
101
+ }
102
+ /**
103
+ * 为 MemberExpression 类型的 v-model 值创建 props
104
+ * 直接访问对象属性,无需 ref 处理
105
+ * @param value - MemberExpression 值
106
+ * @returns 属性数组
107
+ */
108
+ function createMemberExpressionVModelProps(value) {
109
+ return [
110
+ // modelValue getter: 直接返回表达式
111
+ t.objectMethod('get', t.identifier('modelValue'), [], t.blockStatement([t.returnStatement(value)])),
112
+ // onUpdate:modelValue: v => obj.prop = v
113
+ t.objectProperty(t.stringLiteral('onUpdate:modelValue'), t.arrowFunctionExpression([t.identifier('v')], t.assignmentExpression('=', value, t.identifier('v'))))
114
+ ];
115
+ }