style: format

This commit is contained in:
Kevin Deng
2025-11-26 16:58:43 +08:00
parent 85d98b5b6f
commit 620450b5ba
28 changed files with 1297 additions and 1302 deletions

View File

@@ -1,4 +1,4 @@
{
"singleQuote": true,
"trailingComma": "es5"
"semi": false,
"singleQuote": true
}

View File

@@ -1,9 +1,9 @@
// @ts-check
import { builtinModules } from 'node:module';
import tseslint from 'typescript-eslint';
import importX from 'eslint-plugin-import-x';
import eslint from '@eslint/js';
import eslintConfigPrettier from 'eslint-config-prettier';
import { builtinModules } from 'node:module'
import tseslint from 'typescript-eslint'
import importX from 'eslint-plugin-import-x'
import eslint from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier'
export default tseslint.config(
eslint.configs.recommended,
@@ -63,5 +63,5 @@ export default tseslint.config(
eslintConfigPrettier,
{
ignores: ['**/dist/', '**/coverage/'],
}
);
},
)

View File

@@ -1,5 +1,5 @@
declare function transformOn(
obj: Record<string, any>
): Record<`on${string}`, any>;
obj: Record<string, any>,
): Record<`on${string}`, any>
export { transformOn as default, transformOn as 'module.exports' };
export { transformOn as default, transformOn as 'module.exports' }

View File

@@ -1,9 +1,9 @@
function transformOn(obj) {
const result = {};
const result = {}
Object.keys(obj).forEach((evt) => {
result[`on${evt[0].toUpperCase()}${evt.slice(1)}`] = obj[evt];
});
return result;
result[`on${evt[0].toUpperCase()}${evt.slice(1)}`] = obj[evt]
})
return result
}
export { transformOn as default, transformOn as 'module.exports' };
export { transformOn as default, transformOn as 'module.exports' }

View File

@@ -88,7 +88,7 @@ Default: `false`
函数式组件
```jsx
const App = () => <div></div>;
const App = () => <div></div>
```
在 render 中使用
@@ -96,27 +96,25 @@ const App = () => <div></div>;
```jsx
const App = {
render() {
return <div>Vue 3.0</div>;
return <div>Vue 3.0</div>
},
};
}
```
```jsx
import { withModifiers, defineComponent } from 'vue';
import { withModifiers, defineComponent } from 'vue'
const App = defineComponent({
setup() {
const count = ref(0);
const count = ref(0)
const inc = () => {
count.value++;
};
count.value++
}
return () => (
<div onClick={withModifiers(inc, ['self'])}>{count.value}</div>
);
return () => <div onClick={withModifiers(inc, ['self'])}>{count.value}</div>
},
});
})
```
Fragment
@@ -127,20 +125,20 @@ const App = () => (
<span>I'm</span>
<span>Fragment</span>
</>
);
)
```
### Attributes / Props
```jsx
const App = () => <input type="email" />;
const App = () => <input type="email" />
```
动态绑定:
```jsx
const placeholderText = 'email';
const App = () => <input type="email" placeholder={placeholderText} />;
const placeholderText = 'email'
const App = () => <input type="email" placeholder={placeholderText} />
```
### 指令
@@ -150,12 +148,12 @@ const App = () => <input type="email" placeholder={placeholderText} />;
```jsx
const App = {
data() {
return { visible: true };
return { visible: true }
},
render() {
return <input v-show={this.visible} />;
return <input v-show={this.visible} />
},
};
}
```
#### v-model
@@ -191,7 +189,7 @@ h(A, {
modifier: true,
},
'onUpdate:argument': ($event) => (val = $event),
});
})
```
#### v-models (从 1.1.0 开始不推荐使用)
@@ -234,7 +232,7 @@ h(A, {
modifier: true,
},
'onUpdate:bar': ($event) => (bar = $event),
});
})
```
#### 自定义指令
@@ -245,18 +243,18 @@ h(A, {
const App = {
directives: { custom: customDirective },
setup() {
return () => <a v-custom:arg={val} />;
return () => <a v-custom:arg={val} />
},
};
}
```
```jsx
const App = {
directives: { custom: customDirective },
setup() {
return () => <a v-custom={[val, 'arg', ['a', 'b']]} />;
return () => <a v-custom={[val, 'arg', ['a', 'b']]} />
},
};
}
```
### 插槽
@@ -269,20 +267,20 @@ const A = (props, { slots }) => (
<h1>{slots.default ? slots.default() : 'foo'}</h1>
<h2>{slots.bar?.()}</h2>
</>
);
)
const App = {
setup() {
const slots = {
bar: () => <span>B</span>,
};
}
return () => (
<A v-slots={slots}>
<div>A</div>
</A>
);
)
},
};
}
// or
@@ -291,10 +289,10 @@ const App = {
const slots = {
default: () => <div>A</div>,
bar: () => <span>B</span>,
};
return () => <A v-slots={slots} />;
}
return () => <A v-slots={slots} />
},
};
}
// 或者,当 `enableObjectSlots` 不是 `false` 时,您可以使用对象插槽
const App = {
@@ -309,9 +307,9 @@ const App = {
</A>
<B>{() => 'foo'}</B>
</>
);
)
},
};
}
```
### 在 TypeScript 中使用

View File

@@ -92,7 +92,7 @@ Default: `false`
functional component
```jsx
const App = () => <div>Vue 3.0</div>;
const App = () => <div>Vue 3.0</div>
```
with render
@@ -100,27 +100,25 @@ with render
```jsx
const App = {
render() {
return <div>Vue 3.0</div>;
return <div>Vue 3.0</div>
},
};
}
```
```jsx
import { withModifiers, defineComponent } from 'vue';
import { withModifiers, defineComponent } from 'vue'
const App = defineComponent({
setup() {
const count = ref(0);
const count = ref(0)
const inc = () => {
count.value++;
};
count.value++
}
return () => (
<div onClick={withModifiers(inc, ['self'])}>{count.value}</div>
);
return () => <div onClick={withModifiers(inc, ['self'])}>{count.value}</div>
},
});
})
```
Fragment
@@ -131,20 +129,20 @@ const App = () => (
<span>I'm</span>
<span>Fragment</span>
</>
);
)
```
### Attributes / Props
```jsx
const App = () => <input type="email" />;
const App = () => <input type="email" />
```
with a dynamic binding:
```jsx
const placeholderText = 'email';
const App = () => <input type="email" placeholder={placeholderText} />;
const placeholderText = 'email'
const App = () => <input type="email" placeholder={placeholderText} />
```
### Directives
@@ -154,12 +152,12 @@ const App = () => <input type="email" placeholder={placeholderText} />;
```jsx
const App = {
data() {
return { visible: true };
return { visible: true }
},
render() {
return <input v-show={this.visible} />;
return <input v-show={this.visible} />
},
};
}
```
#### v-model
@@ -195,7 +193,7 @@ h(A, {
modifier: true,
},
'onUpdate:argument': ($event) => (val = $event),
});
})
```
#### v-models (Not recommended since v1.1.0)
@@ -238,7 +236,7 @@ h(A, {
modifier: true,
},
'onUpdate:bar': ($event) => (bar = $event),
});
})
```
#### custom directive
@@ -249,18 +247,18 @@ Recommended when using string arguments
const App = {
directives: { custom: customDirective },
setup() {
return () => <a v-custom:arg={val} />;
return () => <a v-custom:arg={val} />
},
};
}
```
```jsx
const App = {
directives: { custom: customDirective },
setup() {
return () => <a v-custom={[val, 'arg', ['a', 'b']]} />;
return () => <a v-custom={[val, 'arg', ['a', 'b']]} />
},
};
}
```
### Slot
@@ -273,20 +271,20 @@ const A = (props, { slots }) => (
<h1>{slots.default ? slots.default() : 'foo'}</h1>
<h2>{slots.bar?.()}</h2>
</>
);
)
const App = {
setup() {
const slots = {
bar: () => <span>B</span>,
};
}
return () => (
<A v-slots={slots}>
<div>A</div>
</A>
);
)
},
};
}
// or
@@ -295,10 +293,10 @@ const App = {
const slots = {
default: () => <div>A</div>,
bar: () => <span>B</span>,
};
return () => <A v-slots={slots} />;
}
return () => <A v-slots={slots} />
},
};
}
// or you can use object slots when `enableObjectSlots` is not false.
const App = {
@@ -313,9 +311,9 @@ const App = {
</A>
<B>{() => 'foo'}</B>
</>
);
)
},
};
}
```
### In TypeScript

View File

@@ -1,58 +1,58 @@
import t from '@babel/types';
import type * as BabelCore from '@babel/core';
import _template from '@babel/template';
import t from '@babel/types'
import type * as BabelCore from '@babel/core'
import _template from '@babel/template'
// @ts-expect-error
import _syntaxJsx from '@babel/plugin-syntax-jsx';
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports';
import { type NodePath, type Visitor } from '@babel/traverse';
import ResolveType from '@vue/babel-plugin-resolve-type';
import { declare } from '@babel/helper-plugin-utils';
import transformVueJSX from './transform-vue-jsx';
import sugarFragment from './sugar-fragment';
import type { State, VueJSXPluginOptions } from './interface';
import _syntaxJsx from '@babel/plugin-syntax-jsx'
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports'
import { type NodePath, type Visitor } from '@babel/traverse'
import ResolveType from '@vue/babel-plugin-resolve-type'
import { declare } from '@babel/helper-plugin-utils'
import transformVueJSX from './transform-vue-jsx'
import sugarFragment from './sugar-fragment'
import type { State, VueJSXPluginOptions } from './interface'
export { VueJSXPluginOptions };
export { VueJSXPluginOptions }
const hasJSX = (parentPath: NodePath<t.Program>) => {
let fileHasJSX = false;
let fileHasJSX = false
parentPath.traverse({
JSXElement(path) {
// skip ts error
fileHasJSX = true;
path.stop();
fileHasJSX = true
path.stop()
},
JSXFragment(path) {
fileHasJSX = true;
path.stop();
fileHasJSX = true
path.stop()
},
});
})
return fileHasJSX;
};
return fileHasJSX
}
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/;
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/
/* #__NO_SIDE_EFFECTS__ */
function interopDefault(m: any) {
return m.default || m;
return m.default || m
}
const syntaxJsx = /*#__PURE__*/ interopDefault(_syntaxJsx);
const template = /*#__PURE__*/ interopDefault(_template);
const syntaxJsx = /*#__PURE__*/ interopDefault(_syntaxJsx)
const template = /*#__PURE__*/ interopDefault(_template)
const plugin: (
api: object,
options: VueJSXPluginOptions | null | undefined,
dirname: string
dirname: string,
) => BabelCore.PluginObj<State> = declare<
VueJSXPluginOptions,
BabelCore.PluginObj<State>
>((api, opt, dirname) => {
const { types } = api;
let resolveType: BabelCore.PluginObj<BabelCore.PluginPass> | undefined;
const { types } = api
let resolveType: BabelCore.PluginObj<BabelCore.PluginPass> | undefined
if (opt.resolveType) {
if (typeof opt.resolveType === 'boolean') opt.resolveType = {};
resolveType = ResolveType(api, opt.resolveType, dirname);
if (typeof opt.resolveType === 'boolean') opt.resolveType = {}
resolveType = ResolveType(api, opt.resolveType, dirname)
}
return {
...(resolveType || {}),
@@ -81,117 +81,117 @@ const plugin: (
'mergeProps',
'createTextVNode',
'isVNode',
];
]
if (isModule(path)) {
// import { createVNode } from "vue";
const importMap: Record<
string,
t.MemberExpression | t.Identifier
> = {};
> = {}
importNames.forEach((name) => {
state.set(name, () => {
if (importMap[name]) {
return types.cloneNode(importMap[name]);
return types.cloneNode(importMap[name])
}
const identifier = addNamed(path, name, 'vue', {
ensureLiveReference: true,
});
importMap[name] = identifier;
return identifier;
});
});
const { enableObjectSlots = true } = state.opts;
})
importMap[name] = identifier
return identifier
})
})
const { enableObjectSlots = true } = state.opts
if (enableObjectSlots) {
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
if (importMap.runtimeIsSlot) {
return importMap.runtimeIsSlot;
return importMap.runtimeIsSlot
}
const { name: isVNodeName } = state.get(
'isVNode'
)() as t.Identifier;
const isSlot = path.scope.generateUidIdentifier('isSlot');
'isVNode',
)() as t.Identifier
const isSlot = path.scope.generateUidIdentifier('isSlot')
const ast = template.ast`
function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
}
`;
`
const lastImport = (path.get('body') as NodePath[])
.filter((p) => p.isImportDeclaration())
.pop();
.pop()
if (lastImport) {
lastImport.insertAfter(ast);
lastImport.insertAfter(ast)
}
importMap.runtimeIsSlot = isSlot;
return isSlot;
});
importMap.runtimeIsSlot = isSlot
return isSlot
})
}
} else {
// var _vue = require('vue');
let sourceName: t.Identifier;
let sourceName: t.Identifier
importNames.forEach((name) => {
state.set(name, () => {
if (!sourceName) {
sourceName = addNamespace(path, 'vue', {
ensureLiveReference: true,
});
})
}
return t.memberExpression(sourceName, t.identifier(name));
});
});
return t.memberExpression(sourceName, t.identifier(name))
})
})
const helpers: Record<string, t.Identifier> = {};
const helpers: Record<string, t.Identifier> = {}
const { enableObjectSlots = true } = state.opts;
const { enableObjectSlots = true } = state.opts
if (enableObjectSlots) {
state.set('@vue/babel-plugin-jsx/runtimeIsSlot', () => {
if (helpers.runtimeIsSlot) {
return helpers.runtimeIsSlot;
return helpers.runtimeIsSlot
}
const isSlot = path.scope.generateUidIdentifier('isSlot');
const isSlot = path.scope.generateUidIdentifier('isSlot')
const { object: objectName } = state.get(
'isVNode'
)() as t.MemberExpression;
'isVNode',
)() as t.MemberExpression
const ast = template.ast`
function ${isSlot.name}(s) {
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${
(objectName as t.Identifier).name
}.isVNode(s));
}
`;
`
const nodePaths = path.get('body') as NodePath[];
const nodePaths = path.get('body') as NodePath[]
const lastImport = nodePaths
.filter(
(p) =>
p.isVariableDeclaration() &&
p.node.declarations.some(
(d) =>
(d.id as t.Identifier)?.name === sourceName.name
)
(d.id as t.Identifier)?.name === sourceName.name,
),
)
.pop();
.pop()
if (lastImport) {
lastImport.insertAfter(ast);
lastImport.insertAfter(ast)
}
return isSlot;
});
return isSlot
})
}
}
const {
opts: { pragma = '' },
file,
} = state;
} = state
if (pragma) {
state.set('createVNode', () => t.identifier(pragma));
state.set('createVNode', () => t.identifier(pragma))
}
if (file.ast.comments) {
for (const comment of file.ast.comments) {
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value);
const jsxMatches = JSX_ANNOTATION_REGEX.exec(comment.value)
if (jsxMatches) {
state.set('createVNode', () => t.identifier(jsxMatches[1]));
state.set('createVNode', () => t.identifier(jsxMatches[1]))
}
}
}
@@ -199,8 +199,8 @@ const plugin: (
},
},
},
};
});
}
})
export default plugin;
export { plugin as 'module.exports' };
export default plugin
export { plugin as 'module.exports' }

View File

@@ -1,32 +1,32 @@
import type t from '@babel/types';
import type * as BabelCore from '@babel/core';
import { type Options } from '@vue/babel-plugin-resolve-type';
import type t from '@babel/types'
import type * as BabelCore from '@babel/core'
import { type Options } from '@vue/babel-plugin-resolve-type'
export type Slots = t.Identifier | t.ObjectExpression | null;
export type Slots = t.Identifier | t.ObjectExpression | null
export type State = {
get: (name: string) => any;
set: (name: string, value: any) => any;
opts: VueJSXPluginOptions;
file: BabelCore.BabelFile;
};
get: (name: string) => any
set: (name: string, value: any) => any
opts: VueJSXPluginOptions
file: BabelCore.BabelFile
}
export interface VueJSXPluginOptions {
/** transform `on: { click: xx }` to `onClick: xxx` */
transformOn?: boolean;
transformOn?: boolean
/** enable optimization or not. */
optimize?: boolean;
optimize?: boolean
/** merge static and dynamic class / style attributes / onXXX handlers */
mergeProps?: boolean;
mergeProps?: boolean
/** configuring custom elements */
isCustomElement?: (tag: string) => boolean;
isCustomElement?: (tag: string) => boolean
/** enable object slots syntax */
enableObjectSlots?: boolean;
enableObjectSlots?: boolean
/** Replace the function used when compiling JSX expressions */
pragma?: string;
pragma?: string
/**
* (**Experimental**) Infer component metadata from types (e.g. `props`, `emits`, `name`)
* @default false
*/
resolveType?: Options | boolean;
resolveType?: Options | boolean
}

View File

@@ -1,13 +1,13 @@
import t from '@babel/types';
import { type NodePath } from '@babel/traverse';
import { createIdentifier } from './utils';
import type { State } from './interface';
import t from '@babel/types'
import { type NodePath } from '@babel/traverse'
import { createIdentifier } from './utils'
import type { State } from './interface'
export type Tag =
| t.Identifier
| t.MemberExpression
| t.StringLiteral
| t.CallExpression;
| t.CallExpression
/**
* Get JSX element type
@@ -17,111 +17,111 @@ export type Tag =
const getType = (path: NodePath<t.JSXOpeningElement>) => {
const typePath = path.get('attributes').find((attribute) => {
if (!attribute.isJSXAttribute()) {
return false;
return false
}
return (
attribute.get('name').isJSXIdentifier() &&
(attribute.get('name') as NodePath<t.JSXIdentifier>).node.name === 'type'
);
}) as NodePath<t.JSXAttribute> | undefined;
)
}) as NodePath<t.JSXAttribute> | undefined
return typePath ? typePath.get('value').node : null;
};
return typePath ? typePath.get('value').node : null
}
const parseModifiers = (value: any): string[] =>
t.isArrayExpression(value)
? value.elements
.map((el) => (t.isStringLiteral(el) ? el.value : ''))
.filter(Boolean)
: [];
: []
const parseDirectives = (params: {
name: string;
path: NodePath<t.JSXAttribute>;
value: t.Expression | null;
state: State;
tag: Tag;
isComponent: boolean;
name: string
path: NodePath<t.JSXAttribute>
value: t.Expression | null
state: State
tag: Tag
isComponent: boolean
}) => {
const { path, value, state, tag, isComponent } = params;
const args: Array<t.Expression | t.NullLiteral> = [];
const vals: t.Expression[] = [];
const modifiersSet: Set<string>[] = [];
const { path, value, state, tag, isComponent } = params
const args: Array<t.Expression | t.NullLiteral> = []
const vals: t.Expression[] = []
const modifiersSet: Set<string>[] = []
let directiveName;
let directiveArgument;
let directiveModifiers;
let directiveName
let directiveArgument
let directiveModifiers
if ('namespace' in path.node.name) {
[directiveName, directiveArgument] = params.name.split(':');
directiveName = path.node.name.namespace.name;
directiveArgument = path.node.name.name.name;
directiveModifiers = directiveArgument.split('_').slice(1);
;[directiveName, directiveArgument] = params.name.split(':')
directiveName = path.node.name.namespace.name
directiveArgument = path.node.name.name.name
directiveModifiers = directiveArgument.split('_').slice(1)
} else {
const underscoreModifiers = params.name.split('_');
directiveName = underscoreModifiers.shift() || '';
directiveModifiers = underscoreModifiers;
const underscoreModifiers = params.name.split('_')
directiveName = underscoreModifiers.shift() || ''
directiveModifiers = underscoreModifiers
}
directiveName = directiveName
.replace(/^v/, '')
.replace(/^-/, '')
.replace(/^\S/, (s: string) => s.toLowerCase());
.replace(/^\S/, (s: string) => s.toLowerCase())
if (directiveArgument) {
args.push(t.stringLiteral(directiveArgument.split('_')[0]));
args.push(t.stringLiteral(directiveArgument.split('_')[0]))
}
const isVModels = directiveName === 'models';
const isVModel = directiveName === 'model';
const isVModels = directiveName === 'models'
const isVModel = directiveName === 'model'
if (isVModel && !path.get('value').isJSXExpressionContainer()) {
throw new Error('You have to use JSX Expression inside your v-model');
throw new Error('You have to use JSX Expression inside your v-model')
}
if (isVModels && !isComponent) {
throw new Error('v-models can only use in custom components');
throw new Error('v-models can only use in custom components')
}
const shouldResolve =
!['html', 'text', 'model', 'slots', 'models'].includes(directiveName) ||
(isVModel && !isComponent);
(isVModel && !isComponent)
let modifiers = directiveModifiers;
let modifiers = directiveModifiers
if (t.isArrayExpression(value)) {
const elementsList = isVModels ? value.elements! : [value];
const elementsList = isVModels ? value.elements! : [value]
elementsList.forEach((element) => {
if (isVModels && !t.isArrayExpression(element)) {
throw new Error('You should pass a Two-dimensional Arrays to v-models');
throw new Error('You should pass a Two-dimensional Arrays to v-models')
}
const { elements } = element as t.ArrayExpression;
const [first, second, third] = elements;
const { elements } = element as t.ArrayExpression
const [first, second, third] = elements
if (
second &&
!t.isArrayExpression(second) &&
!t.isSpreadElement(second)
) {
args.push(second);
modifiers = parseModifiers(third as t.ArrayExpression);
args.push(second)
modifiers = parseModifiers(third as t.ArrayExpression)
} else if (t.isArrayExpression(second)) {
if (!shouldResolve) {
args.push(t.nullLiteral());
args.push(t.nullLiteral())
}
modifiers = parseModifiers(second);
modifiers = parseModifiers(second)
} else if (!shouldResolve) {
// work as v-model={[value]} or v-models={[[value]]}
args.push(t.nullLiteral());
args.push(t.nullLiteral())
}
modifiersSet.push(new Set(modifiers));
vals.push(first as t.Expression);
});
modifiersSet.push(new Set(modifiers))
vals.push(first as t.Expression)
})
} else if (isVModel && !shouldResolve) {
// work as v-model={value}
args.push(t.nullLiteral());
modifiersSet.push(new Set(directiveModifiers));
args.push(t.nullLiteral())
modifiersSet.push(new Set(directiveModifiers))
} else {
modifiersSet.push(new Set(directiveModifiers));
modifiersSet.push(new Set(directiveModifiers))
}
return {
@@ -139,59 +139,62 @@ const parseDirectives = (params: {
!!modifiersSet[0]?.size &&
t.objectExpression(
[...modifiersSet[0]].map((modifier) =>
t.objectProperty(t.identifier(modifier), t.booleanLiteral(true))
)
t.objectProperty(
t.identifier(modifier),
t.booleanLiteral(true),
),
),
),
].filter(Boolean) as t.Expression[])
: undefined,
};
};
}
}
const resolveDirective = (
path: NodePath<t.JSXAttribute>,
state: State,
tag: Tag,
directiveName: string
directiveName: string,
) => {
if (directiveName === 'show') {
return createIdentifier(state, 'vShow');
return createIdentifier(state, 'vShow')
}
if (directiveName === 'model') {
let modelToUse;
const type = getType(path.parentPath as NodePath<t.JSXOpeningElement>);
let modelToUse
const type = getType(path.parentPath as NodePath<t.JSXOpeningElement>)
switch ((tag as t.StringLiteral).value) {
case 'select':
modelToUse = createIdentifier(state, 'vModelSelect');
break;
modelToUse = createIdentifier(state, 'vModelSelect')
break
case 'textarea':
modelToUse = createIdentifier(state, 'vModelText');
break;
modelToUse = createIdentifier(state, 'vModelText')
break
default:
if (t.isStringLiteral(type) || !type) {
switch ((type as t.StringLiteral)?.value) {
case 'checkbox':
modelToUse = createIdentifier(state, 'vModelCheckbox');
break;
modelToUse = createIdentifier(state, 'vModelCheckbox')
break
case 'radio':
modelToUse = createIdentifier(state, 'vModelRadio');
break;
modelToUse = createIdentifier(state, 'vModelRadio')
break
default:
modelToUse = createIdentifier(state, 'vModelText');
modelToUse = createIdentifier(state, 'vModelText')
}
} else {
modelToUse = createIdentifier(state, 'vModelDynamic');
modelToUse = createIdentifier(state, 'vModelDynamic')
}
}
return modelToUse;
return modelToUse
}
const referenceName =
'v' + directiveName[0].toUpperCase() + directiveName.slice(1);
'v' + directiveName[0].toUpperCase() + directiveName.slice(1)
if (path.scope.references[referenceName]) {
return t.identifier(referenceName);
return t.identifier(referenceName)
}
return t.callExpression(createIdentifier(state, 'resolveDirective'), [
t.stringLiteral(directiveName),
]);
};
])
}
export default parseDirectives;
export default parseDirectives

View File

@@ -31,4 +31,4 @@ export const PatchFlagNames = {
[PatchFlags.NEED_PATCH]: 'NEED_PATCH',
[PatchFlags.HOISTED]: 'HOISTED',
[PatchFlags.BAIL]: 'BAIL',
};
}

View File

@@ -21,4 +21,4 @@ const enum SlotFlags {
FORWARDED = 3,
}
export default SlotFlags;
export default SlotFlags

View File

@@ -1,25 +1,25 @@
import t from '@babel/types';
import { type NodePath, type Visitor } from '@babel/traverse';
import type { State } from './interface';
import { FRAGMENT, createIdentifier } from './utils';
import t from '@babel/types'
import { type NodePath, type Visitor } from '@babel/traverse'
import type { State } from './interface'
import { FRAGMENT, createIdentifier } from './utils'
const transformFragment = (
path: NodePath<t.JSXFragment>,
Fragment: t.JSXIdentifier | t.JSXMemberExpression
Fragment: t.JSXIdentifier | t.JSXMemberExpression,
) => {
const children = path.get('children') || [];
const children = path.get('children') || []
return t.jsxElement(
t.jsxOpeningElement(Fragment, []),
t.jsxClosingElement(Fragment),
children.map(({ node }) => node),
false
);
};
false,
)
}
const visitor: Visitor<State> = {
JSXFragment: {
enter(path, state) {
const fragmentCallee = createIdentifier(state, FRAGMENT);
const fragmentCallee = createIdentifier(state, FRAGMENT)
path.replaceWith(
transformFragment(
path,
@@ -27,12 +27,12 @@ const visitor: Visitor<State> = {
? t.jsxIdentifier(fragmentCallee.name)
: t.jsxMemberExpression(
t.jsxIdentifier((fragmentCallee.object as t.Identifier).name),
t.jsxIdentifier((fragmentCallee.property as t.Identifier).name)
)
)
);
t.jsxIdentifier((fragmentCallee.property as t.Identifier).name),
),
),
)
},
},
};
}
export default visitor;
export default visitor

View File

@@ -1,6 +1,6 @@
import t from '@babel/types';
import { type NodePath, type Visitor } from '@babel/traverse';
import { addDefault } from '@babel/helper-module-imports';
import t from '@babel/types'
import { type NodePath, type Visitor } from '@babel/traverse'
import { addDefault } from '@babel/helper-module-imports'
import {
buildIIFE,
checkIsComponent,
@@ -17,43 +17,43 @@ import {
transformJSXText,
transformText,
walksScope,
} from './utils';
import SlotFlags from './slotFlags';
import { PatchFlags } from './patchFlags';
import parseDirectives from './parseDirectives';
import type { Slots, State } from './interface';
} from './utils'
import SlotFlags from './slotFlags'
import { PatchFlags } from './patchFlags'
import parseDirectives from './parseDirectives'
import type { Slots, State } from './interface'
const xlinkRE = /^xlink([A-Z])/;
const xlinkRE = /^xlink([A-Z])/
type ExcludesBoolean = <T>(x: T | false | true) => x is T;
type ExcludesBoolean = <T>(x: T | false | true) => x is T
const getJSXAttributeValue = (
path: NodePath<t.JSXAttribute>,
state: State
state: State,
): t.StringLiteral | t.Expression | null => {
const valuePath = path.get('value');
const valuePath = path.get('value')
if (valuePath.isJSXElement()) {
return transformJSXElement(valuePath, state);
return transformJSXElement(valuePath, state)
}
if (valuePath.isStringLiteral()) {
return t.stringLiteral(transformText(valuePath.node.value));
return t.stringLiteral(transformText(valuePath.node.value))
}
if (valuePath.isJSXExpressionContainer()) {
return transformJSXExpressionContainer(valuePath);
return transformJSXExpressionContainer(valuePath)
}
return null;
};
return null
}
const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
const tag = getTag(path, state);
const isComponent = checkIsComponent(path.get('openingElement'), state);
const props = path.get('openingElement').get('attributes');
const directives: t.ArrayExpression[] = [];
const dynamicPropNames = new Set<string>();
const tag = getTag(path, state)
const isComponent = checkIsComponent(path.get('openingElement'), state)
const props = path.get('openingElement').get('attributes')
const directives: t.ArrayExpression[] = []
const dynamicPropNames = new Set<string>()
let slots: Slots = null;
let patchFlag = 0;
let slots: Slots = null
let patchFlag = 0
if (props.length === 0) {
return {
@@ -64,26 +64,25 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
directives,
patchFlag,
dynamicPropNames,
};
}
}
let properties: t.ObjectProperty[] = [];
let properties: t.ObjectProperty[] = []
// patchFlag analysis
let hasRef = false;
let hasClassBinding = false;
let hasStyleBinding = false;
let hasHydrationEventBinding = false;
let hasDynamicKeys = false;
let hasRef = false
let hasClassBinding = false
let hasStyleBinding = false
let hasHydrationEventBinding = false
let hasDynamicKeys = false
const mergeArgs: (t.CallExpression | t.ObjectExpression | t.Identifier)[] =
[];
const { mergeProps = true } = state.opts;
const mergeArgs: (t.CallExpression | t.ObjectExpression | t.Identifier)[] = []
const { mergeProps = true } = state.opts
props.forEach((prop) => {
if (prop.isJSXAttribute()) {
let name = getJSXAttributeName(prop);
let name = getJSXAttributeName(prop)
const attributeValue = getJSXAttributeValue(prop, state);
const attributeValue = getJSXAttributeValue(prop, state)
if (!isConstant(attributeValue) || name === 'ref') {
if (
@@ -95,17 +94,17 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
// omit v-model handlers
name !== 'onUpdate:modelValue'
) {
hasHydrationEventBinding = true;
hasHydrationEventBinding = true
}
if (name === 'ref') {
hasRef = true;
hasRef = true
} else if (name === 'class' && !isComponent) {
hasClassBinding = true;
hasClassBinding = true
} else if (name === 'style' && !isComponent) {
hasStyleBinding = true;
hasStyleBinding = true
} else if (name !== 'key' && !isDirective(name) && name !== 'on') {
dynamicPropNames.add(name);
dynamicPropNames.add(name)
}
}
if (state.opts.transformOn && (name === 'on' || name === 'nativeOn')) {
@@ -114,15 +113,15 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
'transformOn',
addDefault(path, '@vue/babel-helper-vue-transform-on', {
nameHint: '_transformOn',
})
);
}),
)
}
mergeArgs.push(
t.callExpression(state.get('transformOn'), [
attributeValue || t.booleanLiteral(true),
])
);
return;
]),
)
return
}
if (isDirective(name)) {
const { directive, modifiers, values, args, directiveName } =
@@ -133,34 +132,34 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
path: prop,
state,
value: attributeValue,
});
})
if (directiveName === 'slots') {
slots = attributeValue as Slots;
return;
slots = attributeValue as Slots
return
}
if (directive) {
directives.push(t.arrayExpression(directive));
directives.push(t.arrayExpression(directive))
} else if (directiveName === 'html') {
properties.push(
t.objectProperty(t.stringLiteral('innerHTML'), values[0] as any)
);
dynamicPropNames.add('innerHTML');
t.objectProperty(t.stringLiteral('innerHTML'), values[0] as any),
)
dynamicPropNames.add('innerHTML')
} else if (directiveName === 'text') {
properties.push(
t.objectProperty(t.stringLiteral('textContent'), values[0] as any)
);
dynamicPropNames.add('textContent');
t.objectProperty(t.stringLiteral('textContent'), values[0] as any),
)
dynamicPropNames.add('textContent')
}
if (['models', 'model'].includes(directiveName)) {
values.forEach((value, index) => {
const propName = args[index];
const propName = args[index]
// v-model target with variable
const isDynamic =
propName &&
!t.isStringLiteral(propName) &&
!t.isNullLiteral(propName);
!t.isNullLiteral(propName)
// must be v-model or v-models and is a component
if (!directive) {
@@ -170,13 +169,13 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
? t.stringLiteral('modelValue')
: propName,
value as any,
isDynamic
)
);
isDynamic,
),
)
if (!isDynamic) {
dynamicPropNames.add(
(propName as t.StringLiteral)?.value || 'modelValue'
);
(propName as t.StringLiteral)?.value || 'modelValue',
)
}
if (modifiers[index]?.size) {
@@ -186,24 +185,24 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
? t.binaryExpression(
'+',
propName,
t.stringLiteral('Modifiers')
t.stringLiteral('Modifiers'),
)
: t.stringLiteral(
`${
(propName as t.StringLiteral)?.value || 'model'
}Modifiers`
}Modifiers`,
),
t.objectExpression(
[...modifiers[index]].map((modifier) =>
t.objectProperty(
t.stringLiteral(modifier),
t.booleanLiteral(true)
)
)
t.booleanLiteral(true),
),
),
),
isDynamic
)
);
isDynamic,
),
)
}
}
@@ -212,8 +211,8 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
: t.stringLiteral(
`onUpdate:${
(propName as t.StringLiteral)?.value || 'modelValue'
}`
);
}`,
)
properties.push(
t.objectProperty(
@@ -223,68 +222,68 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
t.assignmentExpression(
'=',
value as any,
t.identifier('$event')
)
t.identifier('$event'),
),
),
isDynamic
)
);
isDynamic,
),
)
if (!isDynamic) {
dynamicPropNames.add((updateName as t.StringLiteral).value);
dynamicPropNames.add((updateName as t.StringLiteral).value)
} else {
hasDynamicKeys = true;
hasDynamicKeys = true
}
});
})
}
} else {
if (name.match(xlinkRE)) {
name = name.replace(
xlinkRE,
(_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`
);
(_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`,
)
}
properties.push(
t.objectProperty(
t.stringLiteral(name),
attributeValue || t.booleanLiteral(true)
)
);
attributeValue || t.booleanLiteral(true),
),
)
}
} else {
if (properties.length && mergeProps) {
mergeArgs.push(
t.objectExpression(dedupeProperties(properties, mergeProps))
);
properties = [];
t.objectExpression(dedupeProperties(properties, mergeProps)),
)
properties = []
}
// JSXSpreadAttribute
hasDynamicKeys = true;
hasDynamicKeys = true
transformJSXSpreadAttribute(
path as NodePath,
prop as NodePath<t.JSXSpreadAttribute>,
mergeProps,
mergeProps ? mergeArgs : properties
);
mergeProps ? mergeArgs : properties,
)
}
});
})
// patchFlag analysis
if (hasDynamicKeys) {
patchFlag |= PatchFlags.FULL_PROPS;
patchFlag |= PatchFlags.FULL_PROPS
} else {
if (hasClassBinding) {
patchFlag |= PatchFlags.CLASS;
patchFlag |= PatchFlags.CLASS
}
if (hasStyleBinding) {
patchFlag |= PatchFlags.STYLE;
patchFlag |= PatchFlags.STYLE
}
if (dynamicPropNames.size) {
patchFlag |= PatchFlags.PROPS;
patchFlag |= PatchFlags.PROPS
}
if (hasHydrationEventBinding) {
patchFlag |= PatchFlags.HYDRATE_EVENTS;
patchFlag |= PatchFlags.HYDRATE_EVENTS
}
}
@@ -292,34 +291,34 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
(hasRef || directives.length > 0)
) {
patchFlag |= PatchFlags.NEED_PATCH;
patchFlag |= PatchFlags.NEED_PATCH
}
let propsExpression: t.Expression | t.ObjectProperty | t.Literal =
t.nullLiteral();
t.nullLiteral()
if (mergeArgs.length) {
if (properties.length) {
mergeArgs.push(
t.objectExpression(dedupeProperties(properties, mergeProps))
);
t.objectExpression(dedupeProperties(properties, mergeProps)),
)
}
if (mergeArgs.length > 1) {
propsExpression = t.callExpression(
createIdentifier(state, 'mergeProps'),
mergeArgs
);
mergeArgs,
)
} else {
// single no need for a mergeProps call
propsExpression = mergeArgs[0];
propsExpression = mergeArgs[0]
}
} else if (properties.length) {
// single no need for spread
if (properties.length === 1 && t.isSpreadElement(properties[0])) {
propsExpression = (properties[0] as unknown as t.SpreadElement).argument;
propsExpression = (properties[0] as unknown as t.SpreadElement).argument
} else {
propsExpression = t.objectExpression(
dedupeProperties(properties, mergeProps)
);
dedupeProperties(properties, mergeProps),
)
}
}
@@ -331,8 +330,8 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
directives,
patchFlag,
dynamicPropNames,
};
};
}
}
/**
* Get children from Array of JSX children
@@ -347,52 +346,52 @@ const getChildren = (
| t.JSXElement
| t.JSXFragment
>[],
state: State
state: State,
): t.Expression[] =>
paths
.map((path) => {
if (path.isJSXText()) {
const transformedText = transformJSXText(path);
const transformedText = transformJSXText(path)
if (transformedText) {
return t.callExpression(createIdentifier(state, 'createTextVNode'), [
transformedText,
]);
])
}
return transformedText;
return transformedText
}
if (path.isJSXExpressionContainer()) {
const expression = transformJSXExpressionContainer(path);
const expression = transformJSXExpressionContainer(path)
if (t.isIdentifier(expression)) {
const { name } = expression;
const { referencePaths = [] } = path.scope.getBinding(name) || {};
const { name } = expression
const { referencePaths = [] } = path.scope.getBinding(name) || {}
referencePaths.forEach((referencePath) => {
walksScope(referencePath, name, SlotFlags.DYNAMIC);
});
walksScope(referencePath, name, SlotFlags.DYNAMIC)
})
}
return expression;
return expression
}
if (path.isJSXSpreadChild()) {
return transformJSXSpreadChild(path);
return transformJSXSpreadChild(path)
}
if (path.isCallExpression()) {
return (path as NodePath<t.CallExpression>).node;
return (path as NodePath<t.CallExpression>).node
}
if (path.isJSXElement()) {
return transformJSXElement(path, state);
return transformJSXElement(path, state)
}
throw new Error(`getChildren: ${path.type} is not supported`);
throw new Error(`getChildren: ${path.type} is not supported`)
})
.filter(
((value: any) => value != null && !t.isJSXEmptyExpression(value)) as any
);
((value: any) => value != null && !t.isJSXEmptyExpression(value)) as any,
)
const transformJSXElement = (
path: NodePath<t.JSXElement>,
state: State
state: State,
): t.CallExpression => {
const children = getChildren(path.get('children'), state);
const children = getChildren(path.get('children'), state)
const {
tag,
props,
@@ -401,9 +400,9 @@ const transformJSXElement = (
patchFlag,
dynamicPropNames,
slots,
} = buildProps(path, state);
} = buildProps(path, state)
const { optimize = false } = state.opts;
const { optimize = false } = state.opts
// #541 - directives can't be resolved in optimized slots
// all parents should be deoptimized
@@ -413,19 +412,19 @@ const transformJSXElement = (
(d) =>
d.elements?.[0]?.type === 'CallExpression' &&
d.elements[0].callee.type === 'Identifier' &&
d.elements[0].callee.name === '_resolveDirective'
d.elements[0].callee.name === '_resolveDirective',
)
) {
let currentPath = path;
let currentPath = path
while (currentPath.parentPath?.isJSXElement()) {
currentPath = currentPath.parentPath;
currentPath.setData('slotFlag', 0);
currentPath = currentPath.parentPath
currentPath.setData('slotFlag', 0)
}
}
const slotFlag = path.getData('slotFlag') ?? SlotFlags.STABLE;
const optimizeSlots = optimize && slotFlag !== 0;
let VNodeChild;
const slotFlag = path.getData('slotFlag') ?? SlotFlags.STABLE
const optimizeSlots = optimize && slotFlag !== 0
let VNodeChild
if (children.length > 1 || slots) {
/*
@@ -442,8 +441,8 @@ const transformJSXElement = (
t.identifier('default'),
t.arrowFunctionExpression(
[],
t.arrayExpression(buildIIFE(path, children))
)
t.arrayExpression(buildIIFE(path, children)),
),
),
...(slots
? t.isObjectExpression(slots)
@@ -452,53 +451,53 @@ const transformJSXElement = (
: []),
optimizeSlots &&
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
].filter(Boolean as any)
].filter(Boolean as any),
)
: slots
: t.arrayExpression(children);
: t.arrayExpression(children)
} else if (children.length === 1) {
/*
<A>{a}</A> or <A>{() => a}</A>
*/
const { enableObjectSlots = true } = state.opts;
const child = children[0];
const { enableObjectSlots = true } = state.opts
const child = children[0]
const objectExpression = t.objectExpression(
[
t.objectProperty(
t.identifier('default'),
t.arrowFunctionExpression(
[],
t.arrayExpression(buildIIFE(path, [child]))
)
t.arrayExpression(buildIIFE(path, [child])),
),
),
optimizeSlots &&
(t.objectProperty(
t.identifier('_'),
t.numericLiteral(slotFlag)
t.numericLiteral(slotFlag),
) as any),
].filter(Boolean)
);
].filter(Boolean),
)
if (t.isIdentifier(child) && isComponent) {
VNodeChild = enableObjectSlots
? t.conditionalExpression(
t.callExpression(
state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(),
[child]
[child],
),
child,
objectExpression
objectExpression,
)
: objectExpression;
: objectExpression
} else if (t.isCallExpression(child) && child.loc && isComponent) {
// the element was generated and doesn't have location information
if (enableObjectSlots) {
const { scope } = path;
const slotId = scope.generateUidIdentifier('slot');
const { scope } = path
const slotId = scope.generateUidIdentifier('slot')
if (scope) {
scope.push({
id: slotId,
kind: 'let',
});
})
}
const alternate = t.objectExpression(
[
@@ -506,24 +505,24 @@ const transformJSXElement = (
t.identifier('default'),
t.arrowFunctionExpression(
[],
t.arrayExpression(buildIIFE(path, [slotId]))
)
t.arrayExpression(buildIIFE(path, [slotId])),
),
),
optimizeSlots &&
(t.objectProperty(
t.identifier('_'),
t.numericLiteral(slotFlag)
t.numericLiteral(slotFlag),
) as any),
].filter(Boolean)
);
const assignment = t.assignmentExpression('=', slotId, child);
].filter(Boolean),
)
const assignment = t.assignmentExpression('=', slotId, child)
const condition = t.callExpression(
state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(),
[assignment]
);
VNodeChild = t.conditionalExpression(condition, slotId, alternate);
[assignment],
)
VNodeChild = t.conditionalExpression(condition, slotId, alternate)
} else {
VNodeChild = objectExpression;
VNodeChild = objectExpression
}
} else if (
t.isFunctionExpression(child) ||
@@ -531,24 +530,24 @@ const transformJSXElement = (
) {
VNodeChild = t.objectExpression([
t.objectProperty(t.identifier('default'), child),
]);
])
} else if (t.isObjectExpression(child)) {
VNodeChild = t.objectExpression(
[
...child.properties,
optimizeSlots &&
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
].filter(Boolean as any)
);
].filter(Boolean as any),
)
} else {
VNodeChild = isComponent
? t.objectExpression([
t.objectProperty(
t.identifier('default'),
t.arrowFunctionExpression([], t.arrayExpression([child]))
t.arrowFunctionExpression([], t.arrayExpression([child])),
),
])
: t.arrayExpression([child]);
: t.arrayExpression([child])
}
}
@@ -562,27 +561,27 @@ const transformJSXElement = (
!!dynamicPropNames.size &&
optimize &&
t.arrayExpression(
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name))
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name)),
),
].filter(Boolean as unknown as ExcludesBoolean)
);
].filter(Boolean as unknown as ExcludesBoolean),
)
if (!directives.length) {
return createVNode;
return createVNode
}
return t.callExpression(createIdentifier(state, 'withDirectives'), [
createVNode,
t.arrayExpression(directives),
]);
};
])
}
const visitor: Visitor<State> = {
JSXElement: {
exit(path, state) {
path.replaceWith(transformJSXElement(path, state));
path.replaceWith(transformJSXElement(path, state))
},
},
};
}
export default visitor;
export default visitor

View File

@@ -1,11 +1,11 @@
import t from '@babel/types';
import { type NodePath } from '@babel/traverse';
import { isHTMLTag, isSVGTag } from '@vue/shared';
import type { State } from './interface';
import SlotFlags from './slotFlags';
export const JSX_HELPER_KEY = 'JSX_HELPER_KEY';
export const FRAGMENT = 'Fragment';
export const KEEP_ALIVE = 'KeepAlive';
import t from '@babel/types'
import { type NodePath } from '@babel/traverse'
import { isHTMLTag, isSVGTag } from '@vue/shared'
import type { State } from './interface'
import SlotFlags from './slotFlags'
export const JSX_HELPER_KEY = 'JSX_HELPER_KEY'
export const FRAGMENT = 'Fragment'
export const KEEP_ALIVE = 'KeepAlive'
/**
* create Identifier
@@ -16,8 +16,8 @@ export const KEEP_ALIVE = 'KeepAlive';
*/
export const createIdentifier = (
state: State,
name: string
): t.Identifier | t.MemberExpression => state.get(name)();
name: string,
): t.Identifier | t.MemberExpression => state.get(name)()
/**
* Checks if string is describing a directive
@@ -25,7 +25,7 @@ export const createIdentifier = (
*/
export const isDirective = (src: string): boolean =>
src.startsWith('v-') ||
(src.startsWith('v') && src.length >= 2 && src[1] >= 'A' && src[1] <= 'Z');
(src.startsWith('v') && src.length >= 2 && src[1] >= 'A' && src[1] <= 'Z')
/**
* Should transformed to slots
@@ -34,7 +34,7 @@ export const isDirective = (src: string): boolean =>
*/
// if _Fragment is already imported, it will end with number
export const shouldTransformedToSlots = (tag: string) =>
!(tag.match(RegExp(`^_?${FRAGMENT}\\d*$`)) || tag === KEEP_ALIVE);
!(tag.match(RegExp(`^_?${FRAGMENT}\\d*$`)) || tag === KEEP_ALIVE)
/**
* Check if a Node is a component
@@ -45,23 +45,23 @@ export const shouldTransformedToSlots = (tag: string) =>
*/
export const checkIsComponent = (
path: NodePath<t.JSXOpeningElement>,
state: State
state: State,
): boolean => {
const namePath = path.get('name');
const namePath = path.get('name')
if (namePath.isJSXMemberExpression()) {
return shouldTransformedToSlots(namePath.node.property.name); // For withCtx
return shouldTransformedToSlots(namePath.node.property.name) // For withCtx
}
const tag = (namePath as NodePath<t.JSXIdentifier>).node.name;
const tag = (namePath as NodePath<t.JSXIdentifier>).node.name
return (
!state.opts.isCustomElement?.(tag) &&
shouldTransformedToSlots(tag) &&
!isHTMLTag(tag) &&
!isSVGTag(tag)
);
};
)
}
/**
* Transform JSXMemberExpression to MemberExpression
@@ -69,20 +69,20 @@ export const checkIsComponent = (
* @returns MemberExpression
*/
export const transformJSXMemberExpression = (
path: NodePath<t.JSXMemberExpression>
path: NodePath<t.JSXMemberExpression>,
): t.MemberExpression => {
const objectPath = path.node.object;
const propertyPath = path.node.property;
const objectPath = path.node.object
const propertyPath = path.node.property
const transformedObject = t.isJSXMemberExpression(objectPath)
? transformJSXMemberExpression(
path.get('object') as NodePath<t.JSXMemberExpression>
path.get('object') as NodePath<t.JSXMemberExpression>,
)
: t.isJSXIdentifier(objectPath)
? t.identifier(objectPath.name)
: t.nullLiteral();
const transformedProperty = t.identifier(propertyPath.name);
return t.memberExpression(transformedObject, transformedProperty);
};
: t.nullLiteral()
const transformedProperty = t.identifier(propertyPath.name)
return t.memberExpression(transformedObject, transformedProperty)
}
/**
* Get tag (first attribute for h) from JSXOpeningElement
@@ -92,11 +92,11 @@ export const transformJSXMemberExpression = (
*/
export const getTag = (
path: NodePath<t.JSXElement>,
state: State
state: State,
): t.Identifier | t.CallExpression | t.StringLiteral | t.MemberExpression => {
const namePath = path.get('openingElement').get('name');
const namePath = path.get('openingElement').get('name')
if (namePath.isJSXIdentifier()) {
const { name } = namePath.node;
const { name } = namePath.node
if (!isHTMLTag(name) && !isSVGTag(name)) {
return name === FRAGMENT
? createIdentifier(state, FRAGMENT)
@@ -106,26 +106,26 @@ export const getTag = (
? t.stringLiteral(name)
: t.callExpression(createIdentifier(state, 'resolveComponent'), [
t.stringLiteral(name),
]);
])
}
return t.stringLiteral(name);
return t.stringLiteral(name)
}
if (namePath.isJSXMemberExpression()) {
return transformJSXMemberExpression(namePath);
return transformJSXMemberExpression(namePath)
}
throw new Error(`getTag: ${namePath.type} is not supported`);
};
throw new Error(`getTag: ${namePath.type} is not supported`)
}
export const getJSXAttributeName = (path: NodePath<t.JSXAttribute>): string => {
const nameNode = path.node.name;
const nameNode = path.node.name
if (t.isJSXIdentifier(nameNode)) {
return nameNode.name;
return nameNode.name
}
return `${nameNode.namespace.name}:${nameNode.name.name}`;
};
return `${nameNode.namespace.name}:${nameNode.name.name}`
}
/**
* Transform JSXText to StringLiteral
@@ -133,56 +133,56 @@ export const getJSXAttributeName = (path: NodePath<t.JSXAttribute>): string => {
* @returns StringLiteral | null
*/
export const transformJSXText = (
path: NodePath<t.JSXText | t.StringLiteral>
path: NodePath<t.JSXText | t.StringLiteral>,
): t.StringLiteral | null => {
const str = transformText(path.node.value);
return str !== '' ? t.stringLiteral(str) : null;
};
const str = transformText(path.node.value)
return str !== '' ? t.stringLiteral(str) : null
}
export const transformText = (text: string) => {
const lines = text.split(/\r\n|\n|\r/);
const lines = text.split(/\r\n|\n|\r/)
let lastNonEmptyLine = 0;
let lastNonEmptyLine = 0
for (let i = 0; i < lines.length; i++) {
if (lines[i].match(/[^ \t]/)) {
lastNonEmptyLine = i;
lastNonEmptyLine = i
}
}
let str = '';
let str = ''
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const line = lines[i]
const isFirstLine = i === 0;
const isLastLine = i === lines.length - 1;
const isLastNonEmptyLine = i === lastNonEmptyLine;
const isFirstLine = i === 0
const isLastLine = i === lines.length - 1
const isLastNonEmptyLine = i === lastNonEmptyLine
// replace rendered whitespace tabs with spaces
let trimmedLine = line.replace(/\t/g, ' ');
let trimmedLine = line.replace(/\t/g, ' ')
// trim whitespace touching a newline
if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, '');
trimmedLine = trimmedLine.replace(/^[ ]+/, '')
}
// trim whitespace touching an endline
if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, '');
trimmedLine = trimmedLine.replace(/[ ]+$/, '')
}
if (trimmedLine) {
if (!isLastNonEmptyLine) {
trimmedLine += ' ';
trimmedLine += ' '
}
str += trimmedLine;
str += trimmedLine
}
}
return str;
};
return str
}
/**
* Transform JSXExpressionContainer to Expression
@@ -190,8 +190,8 @@ export const transformText = (text: string) => {
* @returns Expression
*/
export const transformJSXExpressionContainer = (
path: NodePath<t.JSXExpressionContainer>
): t.Expression => path.get('expression').node as t.Expression;
path: NodePath<t.JSXExpressionContainer>,
): t.Expression => path.get('expression').node as t.Expression
/**
* Transform JSXSpreadChild
@@ -199,33 +199,33 @@ export const transformJSXExpressionContainer = (
* @returns SpreadElement
*/
export const transformJSXSpreadChild = (
path: NodePath<t.JSXSpreadChild>
): t.SpreadElement => t.spreadElement(path.get('expression').node);
path: NodePath<t.JSXSpreadChild>,
): t.SpreadElement => t.spreadElement(path.get('expression').node)
export const walksScope = (
path: NodePath,
name: string,
slotFlag: SlotFlags
slotFlag: SlotFlags,
): void => {
if (path.scope.hasBinding(name) && path.parentPath) {
if (t.isJSXElement(path.parentPath.node)) {
path.parentPath.setData('slotFlag', slotFlag);
path.parentPath.setData('slotFlag', slotFlag)
}
walksScope(path.parentPath, name, slotFlag);
walksScope(path.parentPath, name, slotFlag)
}
};
}
export const buildIIFE = (
path: NodePath<t.JSXElement>,
children: t.Expression[]
children: t.Expression[],
) => {
const { parentPath } = path;
const { parentPath } = path
if (parentPath.isAssignmentExpression()) {
const { left } = parentPath.node as t.AssignmentExpression;
const { left } = parentPath.node as t.AssignmentExpression
if (t.isIdentifier(left)) {
return children.map((child) => {
if (t.isIdentifier(child) && child.name === left.name) {
const insertName = path.scope.generateUidIdentifier(child.name);
const insertName = path.scope.generateUidIdentifier(child.name)
parentPath.insertBefore(
t.variableDeclaration('const', [
t.variableDeclarator(
@@ -234,69 +234,69 @@ export const buildIIFE = (
t.functionExpression(
null,
[],
t.blockStatement([t.returnStatement(child)])
t.blockStatement([t.returnStatement(child)]),
),
[]
)
[],
),
),
])
);
return insertName;
]),
)
return insertName
}
return child;
});
return child
})
}
}
return children;
};
return children
}
const onRE = /^on[^a-z]/;
const onRE = /^on[^a-z]/
export const isOn = (key: string) => onRE.test(key);
export const isOn = (key: string) => onRE.test(key)
const mergeAsArray = (
existing: t.ObjectProperty,
incoming: t.ObjectProperty
incoming: t.ObjectProperty,
) => {
if (t.isArrayExpression(existing.value)) {
existing.value.elements.push(incoming.value as t.Expression);
existing.value.elements.push(incoming.value as t.Expression)
} else {
existing.value = t.arrayExpression([
existing.value as t.Expression,
incoming.value as t.Expression,
]);
])
}
};
}
export const dedupeProperties = (
properties: t.ObjectProperty[] = [],
mergeProps?: boolean
mergeProps?: boolean,
) => {
if (!mergeProps) {
return properties;
return properties
}
const knownProps = new Map<string, t.ObjectProperty>();
const deduped: t.ObjectProperty[] = [];
const knownProps = new Map<string, t.ObjectProperty>()
const deduped: t.ObjectProperty[] = []
properties.forEach((prop) => {
if (t.isStringLiteral(prop.key)) {
const { value: name } = prop.key;
const existing = knownProps.get(name);
const { value: name } = prop.key
const existing = knownProps.get(name)
if (existing) {
if (name === 'style' || name === 'class' || name.startsWith('on')) {
mergeAsArray(existing, prop);
mergeAsArray(existing, prop)
}
} else {
knownProps.set(name, prop);
deduped.push(prop);
knownProps.set(name, prop)
deduped.push(prop)
}
} else {
// v-model target with variable
deduped.push(prop);
deduped.push(prop)
}
});
})
return deduped;
};
return deduped
}
/**
* Check if an attribute value is constant
@@ -304,52 +304,52 @@ export const dedupeProperties = (
* @returns boolean
*/
export const isConstant = (
node: t.Expression | t.Identifier | t.Literal | t.SpreadElement | null
node: t.Expression | t.Identifier | t.Literal | t.SpreadElement | null,
): boolean => {
if (t.isIdentifier(node)) {
return node.name === 'undefined';
return node.name === 'undefined'
}
if (t.isArrayExpression(node)) {
const { elements } = node;
return elements.every((element) => element && isConstant(element));
const { elements } = node
return elements.every((element) => element && isConstant(element))
}
if (t.isObjectExpression(node)) {
return node.properties.every((property) =>
isConstant((property as any).value)
);
isConstant((property as any).value),
)
}
if (
t.isTemplateLiteral(node) ? !node.expressions.length : t.isLiteral(node)
) {
return true;
return true
}
return false;
};
return false
}
export const transformJSXSpreadAttribute = (
nodePath: NodePath,
path: NodePath<t.JSXSpreadAttribute>,
mergeProps: boolean,
args: (t.ObjectProperty | t.Expression | t.SpreadElement)[]
args: (t.ObjectProperty | t.Expression | t.SpreadElement)[],
) => {
const argument = path.get('argument') as NodePath<
t.ObjectExpression | t.Identifier
>;
>
const properties = t.isObjectExpression(argument.node)
? argument.node.properties
: undefined;
: undefined
if (!properties) {
if (argument.isIdentifier()) {
walksScope(
nodePath,
(argument.node as t.Identifier).name,
SlotFlags.DYNAMIC
);
SlotFlags.DYNAMIC,
)
}
args.push(mergeProps ? argument.node : t.spreadElement(argument.node));
args.push(mergeProps ? argument.node : t.spreadElement(argument.node))
} else if (mergeProps) {
args.push(t.objectExpression(properties));
args.push(t.objectExpression(properties))
} else {
args.push(...(properties as t.ObjectProperty[]));
args.push(...(properties as t.ObjectProperty[]))
}
};
}

View File

@@ -5,57 +5,57 @@ import {
defineComponent,
reactive,
ref,
} from 'vue';
import { type VueWrapper, mount, shallowMount } from '@vue/test-utils';
} from 'vue'
import { type VueWrapper, mount, shallowMount } from '@vue/test-utils'
const patchFlagExpect = (
wrapper: VueWrapper<ComponentPublicInstance>,
flag: number,
dynamic: string[] | null
dynamic: string[] | null,
) => {
const { patchFlag, dynamicProps } = wrapper.vm.$.subTree as any;
const { patchFlag, dynamicProps } = wrapper.vm.$.subTree as any
expect(patchFlag).toBe(flag);
expect(dynamicProps).toEqual(dynamic);
};
expect(patchFlag).toBe(flag)
expect(dynamicProps).toEqual(dynamic)
}
describe('Transform JSX', () => {
test('should render with render function', () => {
const wrapper = shallowMount({
render() {
return <div>123</div>;
return <div>123</div>
},
});
expect(wrapper.text()).toBe('123');
});
})
expect(wrapper.text()).toBe('123')
})
test('should render with setup', () => {
const wrapper = shallowMount({
setup() {
return () => <div>123</div>;
return () => <div>123</div>
},
});
expect(wrapper.text()).toBe('123');
});
})
expect(wrapper.text()).toBe('123')
})
test('Extracts attrs', () => {
const wrapper = shallowMount({
setup() {
return () => <div id="hi" />;
return () => <div id="hi" />
},
});
expect(wrapper.element.id).toBe('hi');
});
})
expect(wrapper.element.id).toBe('hi')
})
test('Binds attrs', () => {
const id = 'foo';
const id = 'foo'
const wrapper = shallowMount({
setup() {
return () => <div>{id}</div>;
return () => <div>{id}</div>
},
});
expect(wrapper.text()).toBe('foo');
});
})
expect(wrapper.text()).toBe('foo')
})
test('should not fallthrough with inheritAttrs: false', () => {
const Child = defineComponent({
@@ -63,25 +63,25 @@ describe('Transform JSX', () => {
foo: Number,
},
setup(props) {
return () => <div>{props.foo}</div>;
return () => <div>{props.foo}</div>
},
});
})
Child.inheritAttrs = false;
Child.inheritAttrs = false
const wrapper = mount({
render() {
return <Child class="parent" foo={1} />;
return <Child class="parent" foo={1} />
},
});
expect(wrapper.classes()).toStrictEqual([]);
expect(wrapper.text()).toBe('1');
});
})
expect(wrapper.classes()).toStrictEqual([])
expect(wrapper.text()).toBe('1')
})
test('Fragment', () => {
const Child = () => <div>123</div>;
const Child = () => <div>123</div>
Child.inheritAttrs = false;
Child.inheritAttrs = false
const wrapper = mount({
setup() {
@@ -90,146 +90,146 @@ describe('Transform JSX', () => {
<Child />
<div>456</div>
</>
);
)
},
});
})
expect(wrapper.html()).toBe('<div>123</div>\n<div>456</div>');
});
expect(wrapper.html()).toBe('<div>123</div>\n<div>456</div>')
})
test('nested component', () => {
const A = {
B: defineComponent({
setup() {
return () => <div>123</div>;
return () => <div>123</div>
},
}),
};
}
A.B.inheritAttrs = false;
A.B.inheritAttrs = false
const wrapper = mount(() => <A.B />);
const wrapper = mount(() => <A.B />)
expect(wrapper.html()).toBe('<div>123</div>');
});
expect(wrapper.html()).toBe('<div>123</div>')
})
test('xlink:href', () => {
const wrapper = shallowMount({
setup() {
return () => <use xlinkHref={'#name'}></use>;
return () => <use xlinkHref={'#name'}></use>
},
});
expect(wrapper.attributes()['xlink:href']).toBe('#name');
});
})
expect(wrapper.attributes()['xlink:href']).toBe('#name')
})
test('Merge class', () => {
const wrapper = shallowMount({
setup() {
// @ts-expect-error
return () => <div class="a" {...{ class: 'b' }} />;
return () => <div class="a" {...{ class: 'b' }} />
},
});
expect(wrapper.classes().sort()).toEqual(['a', 'b'].sort());
});
})
expect(wrapper.classes().sort()).toEqual(['a', 'b'].sort())
})
test('Merge style', () => {
const propsA = {
style: {
color: 'red',
} as CSSProperties,
};
}
const propsB = {
style: {
color: 'blue',
width: '300px',
height: '300px',
} as CSSProperties,
};
}
const wrapper = shallowMount({
setup() {
// @ts-ignore
return () => <div {...propsA} {...propsB} />;
return () => <div {...propsA} {...propsB} />
},
});
})
expect(wrapper.html()).toBe(
'<div style="color: blue; width: 300px; height: 300px;"></div>'
);
});
'<div style="color: blue; width: 300px; height: 300px;"></div>',
)
})
test('JSXSpreadChild', () => {
const a = ['1', '2'];
const a = ['1', '2']
const wrapper = shallowMount({
setup() {
return () => <div>{[...a]}</div>;
return () => <div>{[...a]}</div>
},
});
expect(wrapper.text()).toBe('12');
});
})
expect(wrapper.text()).toBe('12')
})
test('domProps input[value]', () => {
const val = 'foo';
const val = 'foo'
const wrapper = shallowMount({
setup() {
return () => <input type="text" value={val} />;
return () => <input type="text" value={val} />
},
});
expect(wrapper.html()).toBe('<input type="text" value="foo">');
});
})
expect(wrapper.html()).toBe('<input type="text" value="foo">')
})
test('domProps input[checked]', () => {
const val = true;
const val = true
const wrapper = shallowMount({
setup() {
return () => <input checked={val} />;
return () => <input checked={val} />
},
});
})
expect(wrapper.vm.$.subTree?.props?.checked).toBe(val);
});
expect(wrapper.vm.$.subTree?.props?.checked).toBe(val)
})
test('domProps option[selected]', () => {
const val = true;
const val = true
const wrapper = shallowMount({
render() {
return <option selected={val} />;
return <option selected={val} />
},
});
expect(wrapper.vm.$.subTree?.props?.selected).toBe(val);
});
})
expect(wrapper.vm.$.subTree?.props?.selected).toBe(val)
})
test('domProps video[muted]', () => {
const val = true;
const val = true
const wrapper = shallowMount({
render() {
return <video muted={val} />;
return <video muted={val} />
},
});
})
expect(wrapper.vm.$.subTree?.props?.muted).toBe(val);
});
expect(wrapper.vm.$.subTree?.props?.muted).toBe(val)
})
test('Spread (single object expression)', () => {
const props = {
id: '1',
};
}
const wrapper = shallowMount({
render() {
return <div {...props}>123</div>;
return <div {...props}>123</div>
},
});
expect(wrapper.html()).toBe('<div id="1">123</div>');
});
})
expect(wrapper.html()).toBe('<div id="1">123</div>')
})
test('Spread (mixed)', async () => {
const calls: number[] = [];
const calls: number[] = []
const data = {
id: 'hehe',
onClick() {
calls.push(3);
calls.push(3)
},
innerHTML: '2',
class: ['a', 'b'],
};
}
const wrapper = shallowMount({
setup() {
@@ -241,51 +241,51 @@ describe('Transform JSX', () => {
onClick={() => calls.push(4)}
hook-insert={() => calls.push(2)}
/>
);
)
},
});
})
expect(wrapper.attributes('id')).toBe('hehe');
expect(wrapper.attributes('type')).toBe('button');
expect(wrapper.text()).toBe('2');
expect(wrapper.classes()).toEqual(expect.arrayContaining(['a', 'b', 'c']));
expect(wrapper.attributes('id')).toBe('hehe')
expect(wrapper.attributes('type')).toBe('button')
expect(wrapper.text()).toBe('2')
expect(wrapper.classes()).toEqual(expect.arrayContaining(['a', 'b', 'c']))
await wrapper.trigger('click');
await wrapper.trigger('click')
expect(calls).toEqual(expect.arrayContaining([3, 4]));
});
expect(calls).toEqual(expect.arrayContaining([3, 4]))
})
test('empty string', () => {
const wrapper = shallowMount({
setup() {
return () => <h1 title=""></h1>;
return () => <h1 title=""></h1>
},
});
expect(wrapper.html()).toBe('<h1 title=""></h1>');
});
});
})
expect(wrapper.html()).toBe('<h1 title=""></h1>')
})
})
describe('directive', () => {
test('vHtml', () => {
const wrapper = shallowMount({
setup() {
const html = '<div>foo</div>';
return () => <h1 v-html={html}></h1>;
const html = '<div>foo</div>'
return () => <h1 v-html={html}></h1>
},
});
expect(wrapper.html()).toBe('<h1>\n <div>foo</div>\n</h1>');
});
})
expect(wrapper.html()).toBe('<h1>\n <div>foo</div>\n</h1>')
})
test('vText', () => {
const text = 'foo';
const text = 'foo'
const wrapper = shallowMount({
setup() {
return () => <div v-text={text}></div>;
return () => <div v-text={text}></div>
},
});
expect(wrapper.html()).toBe('<div>foo</div>');
});
});
})
expect(wrapper.html()).toBe('<div>foo</div>')
})
})
describe('slots', () => {
test('with default', () => {
@@ -296,11 +296,11 @@ describe('slots', () => {
{slots.default?.()}
{slots.foo?.('val')}
</div>
);
)
},
});
})
A.inheritAttrs = false;
A.inheritAttrs = false
const wrapper = mount({
setup() {
@@ -308,98 +308,98 @@ describe('slots', () => {
<A v-slots={{ foo: (val: string) => val }}>
<span>default</span>
</A>
);
)
},
});
})
expect(wrapper.html()).toBe('<div><span>default</span>val</div>');
});
expect(wrapper.html()).toBe('<div><span>default</span>val</div>')
})
test('without default', () => {
const A = defineComponent({
setup(_, { slots }) {
return () => <div>{slots.foo?.('foo')}</div>;
return () => <div>{slots.foo?.('foo')}</div>
},
});
})
A.inheritAttrs = false;
A.inheritAttrs = false
const wrapper = mount({
setup() {
return () => <A v-slots={{ foo: (val: string) => val }} />;
return () => <A v-slots={{ foo: (val: string) => val }} />
},
});
})
expect(wrapper.html()).toBe('<div>foo</div>');
});
});
expect(wrapper.html()).toBe('<div>foo</div>')
})
})
describe('PatchFlags', () => {
test('static', () => {
const wrapper = shallowMount({
setup() {
return () => <div class="static">static</div>;
return () => <div class="static">static</div>
},
});
patchFlagExpect(wrapper, 0, null);
});
})
patchFlagExpect(wrapper, 0, null)
})
test('props', async () => {
const wrapper = mount({
setup() {
const visible = ref(true);
const visible = ref(true)
const onClick = () => {
visible.value = false;
};
visible.value = false
}
return () => (
<div v-show={visible.value} onClick={onClick}>
NEED_PATCH
</div>
);
)
},
});
})
patchFlagExpect(wrapper, 8, ['onClick']);
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<div style="display: none;">NEED_PATCH</div>');
});
patchFlagExpect(wrapper, 8, ['onClick'])
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<div style="display: none;">NEED_PATCH</div>')
})
test('#728: template literals with expressions should be treated as dynamic', async () => {
const wrapper = mount({
setup() {
const foo = ref(0);
const foo = ref(0)
return () => (
<button value={`${foo.value}`} onClick={() => foo.value++}></button>
);
)
},
});
patchFlagExpect(wrapper, 8, ['value', 'onClick']);
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<button value="1"></button>');
});
})
patchFlagExpect(wrapper, 8, ['value', 'onClick'])
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<button value="1"></button>')
})
test('full props', async () => {
const wrapper = mount({
setup() {
const bindProps = reactive({ class: 'a', style: { marginTop: 10 } });
const bindProps = reactive({ class: 'a', style: { marginTop: 10 } })
const onClick = () => {
bindProps.class = 'b';
};
bindProps.class = 'b'
}
return () => (
<div {...bindProps} class="static" onClick={onClick}>
full props
</div>
);
)
},
});
patchFlagExpect(wrapper, 16, ['onClick']);
})
patchFlagExpect(wrapper, 16, ['onClick'])
await wrapper.trigger('click');
await wrapper.trigger('click')
expect(wrapper.classes().sort()).toEqual(['b', 'static'].sort());
});
});
expect(wrapper.classes().sort()).toEqual(['b', 'static'].sort())
})
})
describe('variables outside slots', () => {
const A = defineComponent({
@@ -407,11 +407,11 @@ describe('variables outside slots', () => {
inc: Function,
},
render() {
return this.$slots.default?.();
return this.$slots.default?.()
},
});
})
A.inheritAttrs = false;
A.inheritAttrs = false
test('internal', async () => {
const wrapper = mount(
@@ -419,17 +419,17 @@ describe('variables outside slots', () => {
data() {
return {
val: 0,
};
}
},
methods: {
inc() {
this.val += 1;
this.val += 1
},
},
render() {
const attrs = {
innerHTML: `${this.val}`,
};
}
return (
<A inc={this.inc}>
<div>
@@ -441,15 +441,15 @@ describe('variables outside slots', () => {
+1
</button>
</A>
);
)
},
})
);
}),
)
expect(wrapper.get('#textarea').element.innerHTML).toBe('0');
await wrapper.get('#button').trigger('click');
expect(wrapper.get('#textarea').element.innerHTML).toBe('1');
});
expect(wrapper.get('#textarea').element.innerHTML).toBe('0')
await wrapper.get('#button').trigger('click')
expect(wrapper.get('#textarea').element.innerHTML).toBe('1')
})
test('forwarded', async () => {
const wrapper = mount(
@@ -457,18 +457,18 @@ describe('variables outside slots', () => {
data() {
return {
val: 0,
};
}
},
methods: {
inc() {
this.val += 1;
this.val += 1
},
},
render() {
const attrs = {
innerHTML: `${this.val}`,
};
const textarea = <textarea id="textarea" {...attrs} />;
}
const textarea = <textarea id="textarea" {...attrs} />
return (
<A inc={this.inc}>
<div>{textarea}</div>
@@ -476,38 +476,38 @@ describe('variables outside slots', () => {
+1
</button>
</A>
);
)
},
})
);
}),
)
expect(wrapper.get('#textarea').element.innerHTML).toBe('0');
await wrapper.get('#button').trigger('click');
expect(wrapper.get('#textarea').element.innerHTML).toBe('1');
});
});
expect(wrapper.get('#textarea').element.innerHTML).toBe('0')
await wrapper.get('#button').trigger('click')
expect(wrapper.get('#textarea').element.innerHTML).toBe('1')
})
})
test('reassign variable as component should work', () => {
let a: any = 1;
let a: any = 1
const A = defineComponent({
setup(_, { slots }) {
return () => <span>{slots.default!()}</span>;
return () => <span>{slots.default!()}</span>
},
});
})
const _a2 = 2;
a = _a2;
a = <A>{a}</A>;
const _a2 = 2
a = _a2
a = <A>{a}</A>
const wrapper = mount({
render() {
return a;
return a
},
});
})
expect(wrapper.html()).toBe('<span>2</span>');
});
expect(wrapper.html()).toBe('<span>2</span>')
})
describe('should support passing object slots via JSX children', () => {
const A = defineComponent({
@@ -517,78 +517,78 @@ describe('should support passing object slots via JSX children', () => {
{slots.default?.()}
{slots.foo?.()}
</span>
);
)
},
});
})
test('single expression, variable', () => {
const slots = { default: () => 1, foo: () => 2 };
const slots = { default: () => 1, foo: () => 2 }
const wrapper = mount({
render() {
return <A>{slots}</A>;
return <A>{slots}</A>
},
});
})
expect(wrapper.html()).toBe('<span>12</span>');
});
expect(wrapper.html()).toBe('<span>12</span>')
})
test('single expression, object literal', () => {
const wrapper = mount({
render() {
return <A>{{ default: () => 1, foo: () => 2 }}</A>;
return <A>{{ default: () => 1, foo: () => 2 }}</A>
},
});
})
expect(wrapper.html()).toBe('<span>12</span>');
});
expect(wrapper.html()).toBe('<span>12</span>')
})
test('single expression, object literal', () => {
const wrapper = mount({
render() {
return <A>{{ default: () => 1, foo: () => 2 }}</A>;
return <A>{{ default: () => 1, foo: () => 2 }}</A>
},
});
})
expect(wrapper.html()).toBe('<span>12</span>');
});
expect(wrapper.html()).toBe('<span>12</span>')
})
test('single expression, non-literal value', () => {
const foo = () => 1;
const foo = () => 1
const wrapper = mount({
render() {
return <A>{foo()}</A>;
return <A>{foo()}</A>
},
});
})
expect(wrapper.html()).toBe('<span>1<!----></span>');
});
expect(wrapper.html()).toBe('<span>1<!----></span>')
})
test('single expression, function expression', () => {
const wrapper = mount({
render() {
return <A>{() => 'foo'}</A>;
return <A>{() => 'foo'}</A>
},
});
})
expect(wrapper.html()).toBe('<span>foo<!----></span>');
});
expect(wrapper.html()).toBe('<span>foo<!----></span>')
})
test('single expression, function expression variable', () => {
const foo = () => 'foo';
const foo = () => 'foo'
const wrapper = mount({
render() {
return <A>{foo}</A>;
return <A>{foo}</A>
},
});
})
expect(wrapper.html()).toBe('<span>foo<!----></span>');
});
expect(wrapper.html()).toBe('<span>foo<!----></span>')
})
test('single expression, array map expression', () => {
const data = ['A', 'B', 'C'];
const data = ['A', 'B', 'C']
const wrapper = mount({
render() {
@@ -600,9 +600,9 @@ describe('should support passing object slots via JSX children', () => {
</A>
))}
</>
);
)
},
});
})
expect(wrapper.html()).toMatchInlineSnapshot(
`
@@ -612,12 +612,12 @@ describe('should support passing object slots via JSX children', () => {
<!----></span>
<span><span>C</span>
<!----></span>"
`
);
});
`,
)
})
test('xx', () => {
const data = ['A', 'B', 'C'];
const data = ['A', 'B', 'C']
const wrapper = mount({
render() {
@@ -627,9 +627,9 @@ describe('should support passing object slots via JSX children', () => {
<A>{() => <span>{item}</span>}</A>
))}
</>
);
)
},
});
})
expect(wrapper.html()).toMatchInlineSnapshot(
`
@@ -639,7 +639,7 @@ describe('should support passing object slots via JSX children', () => {
<!----></span>
<span><span>C</span>
<!----></span>"
`
);
});
});
`,
)
})
})

View File

@@ -1,7 +1,7 @@
import { transformAsync } from '@babel/core';
import { transformAsync } from '@babel/core'
// @ts-expect-error missing types
import typescript from '@babel/plugin-syntax-typescript';
import VueJsx from '../src';
import typescript from '@babel/plugin-syntax-typescript'
import VueJsx from '../src'
describe('resolve type', () => {
describe('runtime props', () => {
@@ -16,9 +16,9 @@ describe('resolve type', () => {
[typescript, { isTSX: true }],
[VueJsx, { resolveType: true }],
],
}
);
expect(result!.code).toMatchSnapshot();
});
});
});
},
)
expect(result!.code).toMatchSnapshot()
})
})
})

View File

@@ -1 +1 @@
import 'regenerator-runtime/runtime';
import 'regenerator-runtime/runtime'

View File

@@ -1,9 +1,9 @@
import { transform } from '@babel/core';
import JSX, { type VueJSXPluginOptions } from '../src';
import { transform } from '@babel/core'
import JSX, { type VueJSXPluginOptions } from '../src'
interface Test {
name: string;
from: string;
name: string
from: string
}
const transpile = (source: string, options: VueJSXPluginOptions = {}) =>
@@ -18,14 +18,14 @@ const transpile = (source: string, options: VueJSXPluginOptions = {}) =>
},
(error, result) => {
if (error) {
return reject(error);
return reject(error)
}
resolve(result?.code);
}
)
);
resolve(result?.code)
},
),
)
[
;[
{
name: 'input[type="checkbox"]',
from: '<input type="checkbox" v-model={test} />',
@@ -222,10 +222,10 @@ const transpile = (source: string, options: VueJSXPluginOptions = {}) =>
].forEach(({ name, from }) => {
test(name, async () => {
expect(
await transpile(from, { optimize: true, enableObjectSlots: true })
).toMatchSnapshot(name);
});
});
await transpile(from, { optimize: true, enableObjectSlots: true }),
).toMatchSnapshot(name)
})
})
const overridePropsTests: Test[] = [
{
@@ -236,13 +236,13 @@ const overridePropsTests: Test[] = [
name: 'multiple',
from: '<A loading {...a} {...{ b: 1, c: { d: 2 } }} class="x" style={x} />',
},
];
]
overridePropsTests.forEach(({ name, from }) => {
test(`override props ${name}`, async () => {
expect(await transpile(from, { mergeProps: false })).toMatchSnapshot(name);
});
});
expect(await transpile(from, { mergeProps: false })).toMatchSnapshot(name)
})
})
const slotsTests: Test[] = [
{
@@ -296,58 +296,58 @@ const slotsTests: Test[] = [
</>
`,
},
];
]
slotsTests.forEach(({ name, from }) => {
test(`passing object slots via JSX children ${name}`, async () => {
expect(
await transpile(from, { optimize: true, enableObjectSlots: true })
).toMatchSnapshot(name);
});
});
await transpile(from, { optimize: true, enableObjectSlots: true }),
).toMatchSnapshot(name)
})
})
const objectSlotsTests = [
{
name: 'defaultSlot',
from: '<Badge>{slots.default()}</Badge>',
},
];
]
objectSlotsTests.forEach(({ name, from }) => {
test(`disable object slot syntax with ${name}`, async () => {
expect(
await transpile(from, { optimize: true, enableObjectSlots: false })
).toMatchSnapshot(name);
});
});
await transpile(from, { optimize: true, enableObjectSlots: false }),
).toMatchSnapshot(name)
})
})
const pragmaTests = [
{
name: 'custom',
from: '<div>pragma</div>',
},
];
]
pragmaTests.forEach(({ name, from }) => {
test(`set pragma to ${name}`, async () => {
expect(await transpile(from, { pragma: 'custom' })).toMatchSnapshot(name);
});
});
expect(await transpile(from, { pragma: 'custom' })).toMatchSnapshot(name)
})
})
const isCustomElementTests = [
{
name: 'isCustomElement',
from: '<foo><span>foo</span></foo>',
},
];
]
isCustomElementTests.forEach(({ name, from }) => {
test(name, async () => {
expect(
await transpile(from, { isCustomElement: (tag) => tag === 'foo' })
).toMatchSnapshot(name);
});
});
await transpile(from, { isCustomElement: (tag) => tag === 'foo' }),
).toMatchSnapshot(name)
})
})
const fragmentTests = [
{
@@ -358,10 +358,10 @@ const fragmentTests = [
const Root2 = () => <_Fragment>root2</_Fragment>
`,
},
];
]
fragmentTests.forEach(({ name, from }) => {
test(name, async () => {
expect(await transpile(from)).toMatchSnapshot(name);
});
});
expect(await transpile(from)).toMatchSnapshot(name)
})
})

View File

@@ -1,5 +1,5 @@
import { mount, shallowMount } from '@vue/test-utils';
import { type VNode, defineComponent } from 'vue';
import { mount, shallowMount } from '@vue/test-utils'
import { type VNode, defineComponent } from 'vue'
test('input[type="checkbox"] should work', async () => {
const wrapper = shallowMount(
@@ -7,24 +7,24 @@ test('input[type="checkbox"] should work', async () => {
data() {
return {
test: true,
};
}
},
render() {
return <input type="checkbox" v-model={this.test} />;
return <input type="checkbox" v-model={this.test} />
},
}),
{ attachTo: document.body }
);
{ attachTo: document.body },
)
expect(wrapper.vm.$el.checked).toBe(true);
wrapper.vm.test = false;
await wrapper.vm.$nextTick();
expect(wrapper.vm.$el.checked).toBe(false);
expect(wrapper.vm.test).toBe(false);
await wrapper.trigger('click');
expect(wrapper.vm.$el.checked).toBe(true);
expect(wrapper.vm.test).toBe(true);
});
expect(wrapper.vm.$el.checked).toBe(true)
wrapper.vm.test = false
await wrapper.vm.$nextTick()
expect(wrapper.vm.$el.checked).toBe(false)
expect(wrapper.vm.test).toBe(false)
await wrapper.trigger('click')
expect(wrapper.vm.$el.checked).toBe(true)
expect(wrapper.vm.test).toBe(true)
})
test('input[type="radio"] should work', async () => {
const wrapper = shallowMount(
@@ -38,24 +38,24 @@ test('input[type="radio"] should work', async () => {
<input type="radio" value="1" v-model={this.test} name="test" />
<input type="radio" value="2" v-model={this.test} name="test" />
</>
);
)
},
}),
{ attachTo: document.body }
);
{ attachTo: document.body },
)
const [a, b] = wrapper.vm.$.subTree.children as VNode[];
const [a, b] = wrapper.vm.$.subTree.children as VNode[]
expect(a.el!.checked).toBe(true);
wrapper.vm.test = '2';
await wrapper.vm.$nextTick();
expect(a.el!.checked).toBe(false);
expect(b.el!.checked).toBe(true);
await a.el!.click();
expect(a.el!.checked).toBe(true);
expect(b.el!.checked).toBe(false);
expect(wrapper.vm.test).toBe('1');
});
expect(a.el!.checked).toBe(true)
wrapper.vm.test = '2'
await wrapper.vm.$nextTick()
expect(a.el!.checked).toBe(false)
expect(b.el!.checked).toBe(true)
await a.el!.click()
expect(a.el!.checked).toBe(true)
expect(b.el!.checked).toBe(false)
expect(wrapper.vm.test).toBe('1')
})
test('select should work with value bindings', async () => {
const wrapper = shallowMount(
@@ -70,28 +70,28 @@ test('select should work with value bindings', async () => {
<option value={2}>b</option>
<option value={3}>c</option>
</select>
);
)
},
})
);
}),
)
const el = wrapper.vm.$el;
const el = wrapper.vm.$el
expect(el.value).toBe('2');
expect(el.children[1].selected).toBe(true);
wrapper.vm.test = 3;
await wrapper.vm.$nextTick();
expect(el.value).toBe('3');
expect(el.children[2].selected).toBe(true);
expect(el.value).toBe('2')
expect(el.children[1].selected).toBe(true)
wrapper.vm.test = 3
await wrapper.vm.$nextTick()
expect(el.value).toBe('3')
expect(el.children[2].selected).toBe(true)
el.value = '1';
await wrapper.trigger('change');
expect(wrapper.vm.test).toBe('1');
el.value = '1'
await wrapper.trigger('change')
expect(wrapper.vm.test).toBe('1')
el.value = '2';
await wrapper.trigger('change');
expect(wrapper.vm.test).toBe(2);
});
el.value = '2'
await wrapper.trigger('change')
expect(wrapper.vm.test).toBe(2)
})
test('textarea should update value both ways', async () => {
const wrapper = shallowMount(
@@ -100,20 +100,20 @@ test('textarea should update value both ways', async () => {
test: 'b',
}),
render() {
return <textarea v-model={this.test} />;
return <textarea v-model={this.test} />
},
})
);
const el = wrapper.vm.$el;
}),
)
const el = wrapper.vm.$el
expect(el.value).toBe('b');
wrapper.vm.test = 'a';
await wrapper.vm.$nextTick();
expect(el.value).toBe('a');
el.value = 'c';
await wrapper.trigger('input');
expect(wrapper.vm.test).toBe('c');
});
expect(el.value).toBe('b')
wrapper.vm.test = 'a'
await wrapper.vm.$nextTick()
expect(el.value).toBe('a')
el.value = 'c'
await wrapper.trigger('input')
expect(wrapper.vm.test).toBe('c')
})
test('input[type="text"] should update value both ways', async () => {
const wrapper = shallowMount(
@@ -122,20 +122,20 @@ test('input[type="text"] should update value both ways', async () => {
test: 'b',
}),
render() {
return <input v-model={this.test} />;
return <input v-model={this.test} />
},
})
);
const el = wrapper.vm.$el;
}),
)
const el = wrapper.vm.$el
expect(el.value).toBe('b');
wrapper.vm.test = 'a';
await wrapper.vm.$nextTick();
expect(el.value).toBe('a');
el.value = 'c';
await wrapper.trigger('input');
expect(wrapper.vm.test).toBe('c');
});
expect(el.value).toBe('b')
wrapper.vm.test = 'a'
await wrapper.vm.$nextTick()
expect(el.value).toBe('a')
el.value = 'c'
await wrapper.trigger('input')
expect(wrapper.vm.test).toBe('c')
})
test('input[type="text"] .lazy modifier', async () => {
const wrapper = shallowMount(
@@ -144,21 +144,21 @@ test('input[type="text"] .lazy modifier', async () => {
test: 'b',
}),
render() {
return <input v-model={[this.test, ['lazy']]} />;
return <input v-model={[this.test, ['lazy']]} />
},
})
);
const el = wrapper.vm.$el;
}),
)
const el = wrapper.vm.$el
expect(el.value).toBe('b');
expect(wrapper.vm.test).toBe('b');
el.value = 'c';
await wrapper.trigger('input');
expect(wrapper.vm.test).toBe('b');
el.value = 'c';
await wrapper.trigger('change');
expect(wrapper.vm.test).toBe('c');
});
expect(el.value).toBe('b')
expect(wrapper.vm.test).toBe('b')
el.value = 'c'
await wrapper.trigger('input')
expect(wrapper.vm.test).toBe('b')
el.value = 'c'
await wrapper.trigger('change')
expect(wrapper.vm.test).toBe('c')
})
test('dynamic type should work', async () => {
const wrapper = shallowMount(
@@ -167,19 +167,19 @@ test('dynamic type should work', async () => {
return {
test: true,
type: 'checkbox',
};
}
},
render() {
return <input type={this.type} v-model={this.test} />;
return <input type={this.type} v-model={this.test} />
},
})
);
}),
)
expect(wrapper.vm.$el.checked).toBe(true);
wrapper.vm.test = false;
await wrapper.vm.$nextTick();
expect(wrapper.vm.$el.checked).toBe(false);
});
expect(wrapper.vm.$el.checked).toBe(true)
wrapper.vm.test = false
await wrapper.vm.$nextTick()
expect(wrapper.vm.$el.checked).toBe(false)
})
test('underscore modifier should work', async () => {
const wrapper = shallowMount(
@@ -188,21 +188,21 @@ test('underscore modifier should work', async () => {
test: 'b',
}),
render() {
return <input v-model_lazy={this.test} />;
return <input v-model_lazy={this.test} />
},
})
);
const el = wrapper.vm.$el;
}),
)
const el = wrapper.vm.$el
expect(el.value).toBe('b');
expect(wrapper.vm.test).toBe('b');
el.value = 'c';
await wrapper.trigger('input');
expect(wrapper.vm.test).toBe('b');
el.value = 'c';
await wrapper.trigger('change');
expect(wrapper.vm.test).toBe('c');
});
expect(el.value).toBe('b')
expect(wrapper.vm.test).toBe('b')
el.value = 'c'
await wrapper.trigger('input')
expect(wrapper.vm.test).toBe('b')
el.value = 'c'
await wrapper.trigger('change')
expect(wrapper.vm.test).toBe('c')
})
test('underscore modifier should work in custom component', async () => {
const Child = defineComponent({
@@ -218,38 +218,38 @@ test('underscore modifier should work in custom component', async () => {
},
setup(props, { emit }) {
const handleClick = () => {
emit('update:modelValue', 3);
};
emit('update:modelValue', 3)
}
return () => (
<div onClick={handleClick}>
{props.modelModifiers.double
? props.modelValue * 2
: props.modelValue}
</div>
);
)
},
});
})
const wrapper = mount(
defineComponent({
data() {
return {
foo: 1,
};
}
},
render() {
return <Child v-model_double={this.foo} />;
return <Child v-model_double={this.foo} />
},
})
);
}),
)
expect(wrapper.html()).toBe('<div>2</div>');
wrapper.vm.$data.foo += 1;
await wrapper.vm.$nextTick();
expect(wrapper.html()).toBe('<div>4</div>');
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<div>6</div>');
});
expect(wrapper.html()).toBe('<div>2</div>')
wrapper.vm.$data.foo += 1
await wrapper.vm.$nextTick()
expect(wrapper.html()).toBe('<div>4</div>')
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<div>6</div>')
})
test('Named model', async () => {
const Child = defineComponent({
@@ -262,11 +262,11 @@ test('Named model', async () => {
},
setup(props, { emit }) {
const handleClick = () => {
emit('update:value', 2);
};
return () => <div onClick={handleClick}>{props.value}</div>;
emit('update:value', 2)
}
return () => <div onClick={handleClick}>{props.value}</div>
},
});
})
const wrapper = mount(
defineComponent({
@@ -274,18 +274,18 @@ test('Named model', async () => {
foo: 0,
}),
render() {
return <Child v-model:value={this.foo} />;
return <Child v-model:value={this.foo} />
},
})
);
}),
)
expect(wrapper.html()).toBe('<div>0</div>');
wrapper.vm.$data.foo += 1;
await wrapper.vm.$nextTick();
expect(wrapper.html()).toBe('<div>1</div>');
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<div>2</div>');
});
expect(wrapper.html()).toBe('<div>0</div>')
wrapper.vm.$data.foo += 1
await wrapper.vm.$nextTick()
expect(wrapper.html()).toBe('<div>1</div>')
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<div>2</div>')
})
test('named model and underscore modifier should work in custom component', async () => {
const Child = defineComponent({
@@ -301,33 +301,33 @@ test('named model and underscore modifier should work in custom component', asyn
},
setup(props, { emit }) {
const handleClick = () => {
emit('update:value', 3);
};
emit('update:value', 3)
}
return () => (
<div onClick={handleClick}>
{props.valueModifiers.double ? props.value * 2 : props.value}
</div>
);
)
},
});
})
const wrapper = mount(
defineComponent({
data() {
return {
foo: 1,
};
}
},
render() {
return <Child v-model:value_double={this.foo} />;
return <Child v-model:value_double={this.foo} />
},
})
);
}),
)
expect(wrapper.html()).toBe('<div>2</div>');
wrapper.vm.$data.foo += 1;
await wrapper.vm.$nextTick();
expect(wrapper.html()).toBe('<div>4</div>');
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<div>6</div>');
});
expect(wrapper.html()).toBe('<div>2</div>')
wrapper.vm.$data.foo += 1
await wrapper.vm.$nextTick()
expect(wrapper.html()).toBe('<div>4</div>')
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<div>6</div>')
})

View File

@@ -1,5 +1,5 @@
import { mount } from '@vue/test-utils';
import { defineComponent } from 'vue';
import { mount } from '@vue/test-utils'
import { defineComponent } from 'vue'
test('single value binding should work', async () => {
const Child = defineComponent({
@@ -9,32 +9,32 @@ test('single value binding should work', async () => {
emits: ['update:foo'],
setup(props, { emit }) {
const handleClick = () => {
emit('update:foo', 3);
};
return () => <div onClick={handleClick}>{props.foo}</div>;
emit('update:foo', 3)
}
return () => <div onClick={handleClick}>{props.foo}</div>
},
});
})
const wrapper = mount(
defineComponent({
data() {
return {
foo: 1,
};
}
},
render() {
return <Child v-models={[[this.foo, 'foo']]} />;
return <Child v-models={[[this.foo, 'foo']]} />
},
})
);
}),
)
expect(wrapper.html()).toBe('<div>1</div>');
wrapper.vm.$data.foo += 1;
await wrapper.vm.$nextTick();
expect(wrapper.html()).toBe('<div>2</div>');
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<div>3</div>');
});
expect(wrapper.html()).toBe('<div>1</div>')
wrapper.vm.$data.foo += 1
await wrapper.vm.$nextTick()
expect(wrapper.html()).toBe('<div>2</div>')
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<div>3</div>')
})
test('multiple values binding should work', async () => {
const Child = defineComponent({
@@ -45,16 +45,16 @@ test('multiple values binding should work', async () => {
emits: ['update:foo', 'update:bar'],
setup(props, { emit }) {
const handleClick = () => {
emit('update:foo', 3);
emit('update:bar', 2);
};
emit('update:foo', 3)
emit('update:bar', 2)
}
return () => (
<div onClick={handleClick}>
{props.foo},{props.bar}
</div>
);
)
},
});
})
const wrapper = mount(
defineComponent({
@@ -62,7 +62,7 @@ test('multiple values binding should work', async () => {
return {
foo: 1,
bar: 0,
};
}
},
render() {
return (
@@ -72,19 +72,19 @@ test('multiple values binding should work', async () => {
[this.bar, 'bar'],
]}
/>
);
)
},
})
);
}),
)
expect(wrapper.html()).toBe('<div>1,0</div>');
wrapper.vm.$data.foo += 1;
wrapper.vm.$data.bar += 1;
await wrapper.vm.$nextTick();
expect(wrapper.html()).toBe('<div>2,1</div>');
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<div>3,2</div>');
});
expect(wrapper.html()).toBe('<div>1,0</div>')
wrapper.vm.$data.foo += 1
wrapper.vm.$data.bar += 1
await wrapper.vm.$nextTick()
expect(wrapper.html()).toBe('<div>2,1</div>')
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<div>3,2</div>')
})
test('modifier should work', async () => {
const Child = defineComponent({
@@ -100,33 +100,33 @@ test('modifier should work', async () => {
emits: ['update:foo'],
setup(props, { emit }) {
const handleClick = () => {
emit('update:foo', 3);
};
emit('update:foo', 3)
}
return () => (
<div onClick={handleClick}>
{props.fooModifiers.double ? props.foo * 2 : props.foo}
</div>
);
)
},
});
})
const wrapper = mount(
defineComponent({
data() {
return {
foo: 1,
};
}
},
render() {
return <Child v-models={[[this.foo, 'foo', ['double']]]} />;
return <Child v-models={[[this.foo, 'foo', ['double']]]} />
},
})
);
}),
)
expect(wrapper.html()).toBe('<div>2</div>');
wrapper.vm.$data.foo += 1;
await wrapper.vm.$nextTick();
expect(wrapper.html()).toBe('<div>4</div>');
await wrapper.trigger('click');
expect(wrapper.html()).toBe('<div>6</div>');
});
expect(wrapper.html()).toBe('<div>2</div>')
wrapper.vm.$data.foo += 1
await wrapper.vm.$nextTick()
expect(wrapper.html()).toBe('<div>4</div>')
await wrapper.trigger('click')
expect(wrapper.html()).toBe('<div>6</div>')
})

View File

@@ -1,31 +1,31 @@
import type * as BabelCore from '@babel/core';
import { parseExpression } from '@babel/parser';
import type * as BabelCore from '@babel/core'
import { parseExpression } from '@babel/parser'
import {
type SimpleTypeResolveContext,
type SimpleTypeResolveOptions,
extractRuntimeEmits,
extractRuntimeProps,
} from '@vue/compiler-sfc';
import { codeFrameColumns } from '@babel/code-frame';
import { addNamed } from '@babel/helper-module-imports';
import { declare } from '@babel/helper-plugin-utils';
} from '@vue/compiler-sfc'
import { codeFrameColumns } from '@babel/code-frame'
import { addNamed } from '@babel/helper-module-imports'
import { declare } from '@babel/helper-plugin-utils'
export { SimpleTypeResolveOptions as Options };
export { SimpleTypeResolveOptions as Options }
const plugin: (
api: object,
options: SimpleTypeResolveOptions | null | undefined,
dirname: string
dirname: string,
) => BabelCore.PluginObj<BabelCore.PluginPass> =
declare<SimpleTypeResolveOptions>(({ types: t }, options) => {
let ctx: SimpleTypeResolveContext | undefined;
let helpers: Set<string> | undefined;
let ctx: SimpleTypeResolveContext | undefined
let helpers: Set<string> | undefined
return {
name: 'babel-plugin-resolve-type',
pre(file) {
const filename = file.opts.filename || 'unknown.js';
helpers = new Set();
const filename = file.opts.filename || 'unknown.js'
helpers = new Set()
ctx = {
filename: filename,
source: file.code,
@@ -45,91 +45,89 @@ const plugin: (
line: node.loc!.end.line,
column: node.loc!.end.column + 1,
},
}
)}`
);
},
)}`,
)
},
helper(key) {
helpers!.add(key);
return `_${key}`;
helpers!.add(key)
return `_${key}`
},
getString(node) {
return file.code.slice(node.start!, node.end!);
return file.code.slice(node.start!, node.end!)
},
propsTypeDecl: undefined,
propsRuntimeDefaults: undefined,
propsDestructuredBindings: {},
emitsTypeDecl: undefined,
};
}
},
visitor: {
CallExpression(path) {
if (!ctx) {
throw new Error(
'[@vue/babel-plugin-resolve-type] context is not loaded.'
);
'[@vue/babel-plugin-resolve-type] context is not loaded.',
)
}
const { node } = path;
const { node } = path
if (!t.isIdentifier(node.callee, { name: 'defineComponent' })) return;
if (!checkDefineComponent(path)) return;
if (!t.isIdentifier(node.callee, { name: 'defineComponent' })) return
if (!checkDefineComponent(path)) return
const comp = node.arguments[0];
if (!comp || !t.isFunction(comp)) return;
const comp = node.arguments[0]
if (!comp || !t.isFunction(comp)) return
let options = node.arguments[1];
let options = node.arguments[1]
if (!options) {
options = t.objectExpression([]);
node.arguments.push(options);
options = t.objectExpression([])
node.arguments.push(options)
}
let propsGenerics: BabelCore.types.TSType | undefined;
let emitsGenerics: BabelCore.types.TSType | undefined;
let propsGenerics: BabelCore.types.TSType | undefined
let emitsGenerics: BabelCore.types.TSType | undefined
if (node.typeParameters && node.typeParameters.params.length > 0) {
propsGenerics = node.typeParameters.params[0];
emitsGenerics = node.typeParameters.params[1];
propsGenerics = node.typeParameters.params[0]
emitsGenerics = node.typeParameters.params[1]
}
node.arguments[1] =
processProps(comp, propsGenerics, options) || options;
processProps(comp, propsGenerics, options) || options
node.arguments[1] =
processEmits(comp, emitsGenerics, node.arguments[1]) || options;
processEmits(comp, emitsGenerics, node.arguments[1]) || options
},
VariableDeclarator(path) {
inferComponentName(path);
inferComponentName(path)
},
},
post(file) {
for (const helper of helpers!) {
addNamed(file.path, `_${helper}`, 'vue');
addNamed(file.path, `_${helper}`, 'vue')
}
},
};
}
function inferComponentName(
path: BabelCore.NodePath<BabelCore.types.VariableDeclarator>
path: BabelCore.NodePath<BabelCore.types.VariableDeclarator>,
) {
const id = path.get('id');
const init = path.get('init');
if (!id || !id.isIdentifier() || !init || !init.isCallExpression())
return;
const id = path.get('id')
const init = path.get('init')
if (!id || !id.isIdentifier() || !init || !init.isCallExpression()) return
if (!init.get('callee')?.isIdentifier({ name: 'defineComponent' }))
return;
if (!checkDefineComponent(init)) return;
if (!init.get('callee')?.isIdentifier({ name: 'defineComponent' })) return
if (!checkDefineComponent(init)) return
const nameProperty = t.objectProperty(
t.identifier('name'),
t.stringLiteral(id.node.name)
);
const { arguments: args } = init.node;
if (args.length === 0) return;
t.stringLiteral(id.node.name),
)
const { arguments: args } = init.node
if (args.length === 0) return
if (args.length === 1) {
init.node.arguments.push(t.objectExpression([]));
init.node.arguments.push(t.objectExpression([]))
}
args[1] = addProperty(t, args[1], nameProperty);
args[1] = addProperty(t, args[1], nameProperty)
}
function processProps(
@@ -138,39 +136,39 @@ const plugin: (
options:
| BabelCore.types.ArgumentPlaceholder
| BabelCore.types.SpreadElement
| BabelCore.types.Expression
| BabelCore.types.Expression,
) {
const props = comp.params[0];
if (!props) return;
const props = comp.params[0]
if (!props) return
if (props.type === 'AssignmentPattern') {
if (generics) {
ctx!.propsTypeDecl = resolveTypeReference(generics);
ctx!.propsTypeDecl = resolveTypeReference(generics)
} else {
ctx!.propsTypeDecl = getTypeAnnotation(props.left);
ctx!.propsTypeDecl = getTypeAnnotation(props.left)
}
ctx!.propsRuntimeDefaults = props.right;
ctx!.propsRuntimeDefaults = props.right
} else {
if (generics) {
ctx!.propsTypeDecl = resolveTypeReference(generics);
ctx!.propsTypeDecl = resolveTypeReference(generics)
} else {
ctx!.propsTypeDecl = getTypeAnnotation(props);
ctx!.propsTypeDecl = getTypeAnnotation(props)
}
}
if (!ctx!.propsTypeDecl) return;
if (!ctx!.propsTypeDecl) return
const runtimeProps = extractRuntimeProps(ctx!);
const runtimeProps = extractRuntimeProps(ctx!)
if (!runtimeProps) {
return;
return
}
const ast = parseExpression(runtimeProps);
const ast = parseExpression(runtimeProps)
return addProperty(
t,
options,
t.objectProperty(t.identifier('props'), ast)
);
t.objectProperty(t.identifier('props'), ast),
)
}
function processEmits(
@@ -179,92 +177,92 @@ const plugin: (
options:
| BabelCore.types.ArgumentPlaceholder
| BabelCore.types.SpreadElement
| BabelCore.types.Expression
| BabelCore.types.Expression,
) {
let emitType: BabelCore.types.Node | undefined;
let emitType: BabelCore.types.Node | undefined
if (generics) {
emitType = resolveTypeReference(generics);
emitType = resolveTypeReference(generics)
}
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1]);
const setupCtx = comp.params[1] && getTypeAnnotation(comp.params[1])
if (
!emitType &&
setupCtx &&
t.isTSTypeReference(setupCtx) &&
t.isIdentifier(setupCtx.typeName, { name: 'SetupContext' })
) {
emitType = setupCtx.typeParameters?.params[0];
emitType = setupCtx.typeParameters?.params[0]
}
if (!emitType) return;
if (!emitType) return
ctx!.emitsTypeDecl = emitType;
const runtimeEmits = extractRuntimeEmits(ctx!);
ctx!.emitsTypeDecl = emitType
const runtimeEmits = extractRuntimeEmits(ctx!)
const ast = t.arrayExpression(
Array.from(runtimeEmits).map((e) => t.stringLiteral(e))
);
Array.from(runtimeEmits).map((e) => t.stringLiteral(e)),
)
return addProperty(
t,
options,
t.objectProperty(t.identifier('emits'), ast)
);
t.objectProperty(t.identifier('emits'), ast),
)
}
function resolveTypeReference(typeNode: BabelCore.types.TSType) {
if (!ctx) return;
if (!ctx) return
if (t.isTSTypeReference(typeNode)) {
const typeName = getTypeReferenceName(typeNode);
const typeName = getTypeReferenceName(typeNode)
if (typeName) {
const typeDeclaration = findTypeDeclaration(typeName);
const typeDeclaration = findTypeDeclaration(typeName)
if (typeDeclaration) {
return typeDeclaration;
return typeDeclaration
}
}
}
return;
return
}
function getTypeReferenceName(typeRef: BabelCore.types.TSTypeReference) {
if (t.isIdentifier(typeRef.typeName)) {
return typeRef.typeName.name;
return typeRef.typeName.name
} else if (t.isTSQualifiedName(typeRef.typeName)) {
const parts: string[] = [];
let current: BabelCore.types.TSEntityName = typeRef.typeName;
const parts: string[] = []
let current: BabelCore.types.TSEntityName = typeRef.typeName
while (t.isTSQualifiedName(current)) {
if (t.isIdentifier(current.right)) {
parts.unshift(current.right.name);
parts.unshift(current.right.name)
}
current = current.left;
current = current.left
}
if (t.isIdentifier(current)) {
parts.unshift(current.name);
parts.unshift(current.name)
}
return parts.join('.');
return parts.join('.')
}
return null;
return null
}
function findTypeDeclaration(typeName: string) {
if (!ctx) return null;
if (!ctx) return null
for (const statement of ctx.ast) {
if (
t.isTSInterfaceDeclaration(statement) &&
statement.id.name === typeName
) {
return t.tsTypeLiteral(statement.body.body);
return t.tsTypeLiteral(statement.body.body)
}
if (
t.isTSTypeAliasDeclaration(statement) &&
statement.id.name === typeName
) {
return statement.typeAnnotation;
return statement.typeAnnotation
}
if (t.isExportNamedDeclaration(statement) && statement.declaration) {
@@ -272,22 +270,22 @@ const plugin: (
t.isTSInterfaceDeclaration(statement.declaration) &&
statement.declaration.id.name === typeName
) {
return t.tsTypeLiteral(statement.declaration.body.body);
return t.tsTypeLiteral(statement.declaration.body.body)
}
if (
t.isTSTypeAliasDeclaration(statement.declaration) &&
statement.declaration.id.name === typeName
) {
return statement.declaration.typeAnnotation;
return statement.declaration.typeAnnotation
}
}
}
return null;
return null
}
});
export default plugin;
})
export default plugin
function getTypeAnnotation(node: BabelCore.types.Node) {
if (
@@ -295,33 +293,32 @@ function getTypeAnnotation(node: BabelCore.types.Node) {
node.typeAnnotation &&
node.typeAnnotation.type === 'TSTypeAnnotation'
) {
return node.typeAnnotation.typeAnnotation;
return node.typeAnnotation.typeAnnotation
}
}
function checkDefineComponent(
path: BabelCore.NodePath<BabelCore.types.CallExpression>
path: BabelCore.NodePath<BabelCore.types.CallExpression>,
) {
const defineCompImport =
path.scope.getBinding('defineComponent')?.path.parent;
if (!defineCompImport) return true;
const defineCompImport = path.scope.getBinding('defineComponent')?.path.parent
if (!defineCompImport) return true
return (
defineCompImport.type === 'ImportDeclaration' &&
/^@?vue(\/|$)/.test(defineCompImport.source.value)
);
)
}
function addProperty<T extends BabelCore.types.Node>(
t: (typeof BabelCore)['types'],
object: T,
property: BabelCore.types.ObjectProperty
property: BabelCore.types.ObjectProperty,
) {
if (t.isObjectExpression(object)) {
object.properties.unshift(property);
object.properties.unshift(property)
} else if (t.isExpression(object)) {
return t.objectExpression([property, t.spreadElement(object)]);
return t.objectExpression([property, t.spreadElement(object)])
}
return object;
return object
}
export { plugin as 'module.exports' };
export { plugin as 'module.exports' }

View File

@@ -1,13 +1,13 @@
import { transformAsync } from '@babel/core';
import { transformAsync } from '@babel/core'
// @ts-expect-error missing types
import typescript from '@babel/plugin-syntax-typescript';
import ResolveType from '../src';
import typescript from '@babel/plugin-syntax-typescript'
import ResolveType from '../src'
async function transform(code: string): Promise<string> {
const result = await transformAsync(code, {
plugins: [[typescript, { isTSX: true }], ResolveType],
});
return result!.code!;
})
return result!.code!
}
describe('resolve type', () => {
@@ -26,10 +26,10 @@ describe('resolve type', () => {
defineComponent((props: Props & Props2) => {
return () => h('div', props.msg);
})
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('with generic', async () => {
const result = await transform(
@@ -42,10 +42,10 @@ describe('resolve type', () => {
defineComponent<Props>((props) => {
return () => h('div', props.msg);
})
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('with static default value and generic', async () => {
const result = await transform(
@@ -58,10 +58,10 @@ describe('resolve type', () => {
defineComponent<Props>((props = { msg: 'hello' }) => {
return () => h('div', props.msg);
})
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('with static default value', async () => {
const result = await transform(
@@ -70,10 +70,10 @@ describe('resolve type', () => {
defineComponent((props: { msg?: string } = { msg: 'hello' }) => {
return () => h('div', props.msg);
})
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('with dynamic default value', async () => {
const result = await transform(
@@ -83,11 +83,11 @@ describe('resolve type', () => {
defineComponent((props: { msg?: string } = defaults) => {
return () => h('div', props.msg);
})
`
);
expect(result).toMatchSnapshot();
});
});
`,
)
expect(result).toMatchSnapshot()
})
})
describe('runtime emits', () => {
test('basic', async () => {
@@ -103,10 +103,10 @@ describe('resolve type', () => {
return () => {};
}
);
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('with generic emit type', async () => {
const result = await transform(
@@ -122,11 +122,11 @@ describe('resolve type', () => {
return () => {};
}
);
`
);
expect(result).toMatchSnapshot();
});
});
`,
)
expect(result).toMatchSnapshot()
})
})
test('w/ tsx', async () => {
const result = await transform(
@@ -135,10 +135,10 @@ describe('resolve type', () => {
defineComponent(() => {
return () => <div/ >;
});
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
describe('defineComponent scope', () => {
test('fake', async () => {
@@ -148,10 +148,10 @@ describe('resolve type', () => {
defineComponent((props: { msg?: string }) => {
return () => <div/ >;
});
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('w/o import', async () => {
const result = await transform(
@@ -159,10 +159,10 @@ describe('resolve type', () => {
defineComponent((props: { msg?: string }) => {
return () => <div/ >;
});
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('import sub-package', async () => {
const result = await transform(
@@ -171,11 +171,11 @@ describe('resolve type', () => {
defineComponent((props: { msg?: string }) => {
return () => <div/ >;
});
`
);
expect(result).toMatchSnapshot();
});
});
`,
)
expect(result).toMatchSnapshot()
})
})
describe('infer component name', () => {
test('no options', async () => {
@@ -183,39 +183,39 @@ describe('resolve type', () => {
`
import { defineComponent } from 'vue';
const Foo = defineComponent(() => {})
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('object options', async () => {
const result = await transform(
`
import { defineComponent } from 'vue';
const Foo = defineComponent(() => {}, { foo: 'bar' })
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('identifier options', async () => {
const result = await transform(
`
import { defineComponent } from 'vue';
const Foo = defineComponent(() => {}, opts)
`
);
expect(result).toMatchSnapshot();
});
`,
)
expect(result).toMatchSnapshot()
})
test('rest param', async () => {
const result = await transform(
`
import { defineComponent } from 'vue';
const Foo = defineComponent(() => {}, ...args)
`
);
expect(result).toMatchSnapshot();
});
});
});
`,
)
expect(result).toMatchSnapshot()
})
})
})

View File

@@ -1,13 +1,13 @@
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
// @ts-ignore
self.MonacoEnvironment = {
globalAPI: true,
getWorker(_: any, label: string) {
if (label === 'typescript' || label === 'javascript') {
return new tsWorker();
return new tsWorker()
}
return new editorWorker();
return new editorWorker()
},
};
}

View File

@@ -1,30 +1,30 @@
import * as monaco from 'monaco-editor';
import { watchEffect } from 'vue';
import { transform } from '@babel/standalone';
import babelPluginJsx from '@vue/babel-plugin-jsx';
import * as monaco from 'monaco-editor'
import { watchEffect } from 'vue'
import { transform } from '@babel/standalone'
import babelPluginJsx from '@vue/babel-plugin-jsx'
// @ts-expect-error missing types
import typescript from '@babel/plugin-syntax-typescript';
import typescript from '@babel/plugin-syntax-typescript'
import {
type VueJSXPluginOptions,
compilerOptions,
initOptions,
} from './options';
import './editor.worker';
import './index.css';
} from './options'
import './editor.worker'
import './index.css'
main();
main()
interface PersistedState {
src: string;
options: VueJSXPluginOptions;
src: string
options: VueJSXPluginOptions
}
function main() {
const persistedState: PersistedState = JSON.parse(
localStorage.getItem('state') || '{}'
);
localStorage.getItem('state') || '{}',
)
Object.assign(compilerOptions, persistedState.options);
Object.assign(compilerOptions, persistedState.options)
const sharedEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions =
{
@@ -39,11 +39,11 @@ function main() {
minimap: {
enabled: false,
},
};
}
monaco.languages.typescript.typescriptDefaults.setDiagnosticsOptions({
noSemanticValidation: true,
});
})
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
allowJs: true,
allowNonTsExtensions: true,
@@ -51,7 +51,7 @@ function main() {
target: monaco.languages.typescript.ScriptTarget.Latest,
module: monaco.languages.typescript.ModuleKind.ESNext,
isolatedModules: true,
});
})
const editor = monaco.editor.create(document.getElementById('source')!, {
...sharedEditorOptions,
@@ -62,9 +62,9 @@ function main() {
const App = defineComponent((props) => <div>Hello World</div>)`,
'typescript',
monaco.Uri.parse('file:///app.tsx')
monaco.Uri.parse('file:///app.tsx'),
),
});
})
const output = monaco.editor.create(document.getElementById('output')!, {
readOnly: true,
@@ -72,19 +72,19 @@ const App = defineComponent((props) => <div>Hello World</div>)`,
model: monaco.editor.createModel(
'',
'typescript',
monaco.Uri.parse('file:///output.tsx')
monaco.Uri.parse('file:///output.tsx'),
),
});
})
const reCompile = () => {
const src = editor.getValue();
const src = editor.getValue()
const state = JSON.stringify({
src,
options: compilerOptions,
});
localStorage.setItem('state', state);
window.location.hash = encodeURIComponent(src);
console.clear();
})
localStorage.setItem('state', state)
window.location.hash = encodeURIComponent(src)
console.clear()
try {
const res = transform(src, {
babelrc: false,
@@ -93,37 +93,37 @@ const App = defineComponent((props) => <div>Hello World</div>)`,
[typescript, { isTSX: true }],
],
ast: true,
});
console.log('AST', res.ast!);
output.setValue(res.code!);
})
console.log('AST', res.ast!)
output.setValue(res.code!)
} catch (err: any) {
console.error(err);
output.setValue(err.message!);
console.error(err)
output.setValue(err.message!)
}
};
}
// handle resize
window.addEventListener('resize', () => {
editor.layout();
output.layout();
});
editor.layout()
output.layout()
})
initOptions();
watchEffect(reCompile);
initOptions()
watchEffect(reCompile)
// update compile output when input changes
editor.onDidChangeModelContent(debounce(reCompile));
editor.onDidChangeModelContent(debounce(reCompile))
}
function debounce<T extends (...args: any[]) => any>(fn: T, delay = 300): T {
let prevTimer: number | null = null;
let prevTimer: number | null = null
return ((...args: any[]) => {
if (prevTimer) {
clearTimeout(prevTimer);
clearTimeout(prevTimer)
}
prevTimer = window.setTimeout(() => {
fn(...args);
prevTimer = null;
}, delay);
}) as any;
fn(...args)
prevTimer = null
}, delay)
}) as any
}

View File

@@ -1,7 +1,7 @@
import { createApp, defineComponent, reactive } from 'vue';
import { type VueJSXPluginOptions } from '@vue/babel-plugin-jsx';
import { createApp, defineComponent, reactive } from 'vue'
import { type VueJSXPluginOptions } from '@vue/babel-plugin-jsx'
export { VueJSXPluginOptions };
export { VueJSXPluginOptions }
export const compilerOptions: VueJSXPluginOptions = reactive({
mergeProps: true,
@@ -9,7 +9,7 @@ export const compilerOptions: VueJSXPluginOptions = reactive({
transformOn: false,
enableObjectSlots: true,
resolveType: false,
});
})
const App = defineComponent({
setup() {
@@ -34,7 +34,7 @@ const App = defineComponent({
onChange={(e: Event) => {
compilerOptions.mergeProps = (
e.target as HTMLInputElement
).checked;
).checked
}}
/>
<label for="mergeProps">mergeProps</label>
@@ -49,7 +49,7 @@ const App = defineComponent({
onChange={(e: Event) => {
compilerOptions.optimize = (
e.target as HTMLInputElement
).checked;
).checked
}}
/>
<label for="optimize">optimize</label>
@@ -64,7 +64,7 @@ const App = defineComponent({
onChange={(e: Event) => {
compilerOptions.transformOn = (
e.target as HTMLInputElement
).checked;
).checked
}}
/>
<label for="transformOn">transformOn</label>
@@ -79,7 +79,7 @@ const App = defineComponent({
onChange={(e: Event) => {
compilerOptions.enableObjectSlots = (
e.target as HTMLInputElement
).checked;
).checked
}}
/>
<label for="enableObjectSlots">enableObjectSlots</label>
@@ -94,7 +94,7 @@ const App = defineComponent({
onChange={(e: Event) => {
compilerOptions.resolveType = (
e.target as HTMLInputElement
).checked;
).checked
}}
/>
<label for="resolveType">resolveType</label>
@@ -102,10 +102,10 @@ const App = defineComponent({
</ul>
</div>
</>,
];
]
},
});
})
export function initOptions() {
createApp(App).mount(document.getElementById('header')!);
createApp(App).mount(document.getElementById('header')!)
}

View File

@@ -1,5 +1,5 @@
import { defineConfig } from 'vite';
import VueJSX from '@vitejs/plugin-vue-jsx';
import { defineConfig } from 'vite'
import VueJSX from '@vitejs/plugin-vue-jsx'
export default defineConfig({
resolve: {
@@ -11,4 +11,4 @@ export default defineConfig({
'process.env.BABEL_TYPES_8_BREAKING': 'false',
},
plugins: [VueJSX()],
});
})

View File

@@ -1,4 +1,4 @@
import { defineConfig } from 'tsdown';
import { defineConfig } from 'tsdown'
export default defineConfig({
workspace: [
@@ -14,4 +14,4 @@ export default defineConfig({
devExports: 'dev',
},
fixedExtension: true,
});
})

View File

@@ -1,6 +1,6 @@
import { defineConfig } from 'vitest/config';
import { babel } from '@rollup/plugin-babel';
import Jsx from './packages/babel-plugin-jsx/src';
import { defineConfig } from 'vitest/config'
import { babel } from '@rollup/plugin-babel'
import Jsx from './packages/babel-plugin-jsx/src'
export default defineConfig({
resolve: {
@@ -25,4 +25,4 @@ export default defineConfig({
globals: true,
environment: 'jsdom',
},
});
})