mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2025-12-09 15:09:40 +08:00
style: format
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"trailingComma": "es5"
|
||||
"semi": false,
|
||||
"singleQuote": true
|
||||
}
|
||||
|
||||
@@ -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/'],
|
||||
}
|
||||
);
|
||||
},
|
||||
)
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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 中使用
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -31,4 +31,4 @@ export const PatchFlagNames = {
|
||||
[PatchFlags.NEED_PATCH]: 'NEED_PATCH',
|
||||
[PatchFlags.HOISTED]: 'HOISTED',
|
||||
[PatchFlags.BAIL]: 'BAIL',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,4 +21,4 @@ const enum SlotFlags {
|
||||
FORWARDED = 3,
|
||||
}
|
||||
|
||||
export default SlotFlags;
|
||||
export default SlotFlags
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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[]))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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>"
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1 +1 @@
|
||||
import 'regenerator-runtime/runtime';
|
||||
import 'regenerator-runtime/runtime'
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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>')
|
||||
})
|
||||
|
||||
@@ -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>')
|
||||
})
|
||||
|
||||
@@ -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' }
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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')!)
|
||||
}
|
||||
|
||||
@@ -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()],
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
});
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user