mirror of
https://github.com/vuejs/babel-plugin-jsx.git
synced 2025-01-10 00:09:12 +08:00
refactor: upgrade project setup (#646)
This commit is contained in:
parent
687be8aca8
commit
dbba3205e4
@ -1,30 +0,0 @@
|
||||
version: 2.1
|
||||
jobs:
|
||||
build:
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: vuejs/ci
|
||||
user: node
|
||||
|
||||
working_directory: /home/node/repo
|
||||
|
||||
steps:
|
||||
- checkout
|
||||
|
||||
# Download and cache dependencies
|
||||
- restore_cache:
|
||||
keys:
|
||||
- v2-dependencies-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: yarn install --pure-lockfile
|
||||
|
||||
- save_cache:
|
||||
paths:
|
||||
- node_modules
|
||||
- ~/.cache/yarn
|
||||
key: v2-dependencies-{{ checksum "yarn.lock" }}
|
||||
|
||||
- run: yarn lint
|
||||
|
||||
# run tests!
|
||||
- run: yarn test
|
@ -1 +1,3 @@
|
||||
dist
|
||||
dist
|
||||
coverage
|
||||
node_modules
|
||||
|
70
.eslintrc.js
70
.eslintrc.js
@ -1,34 +1,60 @@
|
||||
const { builtinModules } = require('node:module');
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
parserOptions: {
|
||||
ecmaVersion: 2020,
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
project: './tsconfig.json',
|
||||
},
|
||||
env: {
|
||||
browser: true,
|
||||
node: true,
|
||||
jest: true,
|
||||
es6: true,
|
||||
},
|
||||
parser: '@typescript-eslint/parser',
|
||||
plugins: ['@typescript-eslint', 'import'],
|
||||
|
||||
extends: [
|
||||
'airbnb-typescript/base',
|
||||
'eslint:recommended',
|
||||
'plugin:@typescript-eslint/recommended',
|
||||
'prettier',
|
||||
],
|
||||
plugins: ['import'],
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
sourceType: 'module',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'no-nested-ternary': [0],
|
||||
'no-param-reassign': [0],
|
||||
'no-use-before-define': [0],
|
||||
'no-restricted-syntax': [0],
|
||||
'no-plusplus': [0],
|
||||
'import/no-extraneous-dependencies': [0],
|
||||
'consistent-return': [0],
|
||||
'no-bitwise': [0],
|
||||
'@typescript-eslint/no-use-before-define': [0],
|
||||
'prefer-destructuring': [2, { array: false }],
|
||||
'max-len': [0],
|
||||
eqeqeq: ['warn', 'always', { null: 'never' }],
|
||||
'no-debugger': ['error'],
|
||||
'no-empty': ['warn', { allowEmptyCatch: true }],
|
||||
'prefer-const': [
|
||||
'warn',
|
||||
{
|
||||
destructuring: 'all',
|
||||
},
|
||||
],
|
||||
'@typescript-eslint/ban-ts-comment': 'off',
|
||||
'@typescript-eslint/no-var-requires': 'off',
|
||||
'@typescript-eslint/no-non-null-assertion': 'off',
|
||||
'@typescript-eslint/no-explicit-any': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
{ prefer: 'type-imports', fixStyle: 'inline-type-imports' },
|
||||
],
|
||||
|
||||
'import/no-nodejs-modules': [
|
||||
'error',
|
||||
{ allow: builtinModules.map((mod) => `node:${mod}`) },
|
||||
],
|
||||
'import/no-duplicates': 'error',
|
||||
'import/order': 'error',
|
||||
'sort-imports': [
|
||||
'error',
|
||||
{
|
||||
ignoreCase: false,
|
||||
ignoreDeclarationSort: true,
|
||||
ignoreMemberSort: false,
|
||||
memberSyntaxSortOrder: ['none', 'all', 'multiple', 'single'],
|
||||
allowSeparatedGroups: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
3
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -8,16 +8,13 @@ assignees:
|
||||
|
||||
### 🐛 Bug description
|
||||
|
||||
|
||||
<!-- Please describe the bug in detail above so that everyone can understand. -->
|
||||
|
||||
### 🏞 Desired result
|
||||
|
||||
|
||||
<!-- Please describe above what you expected to see. -->
|
||||
|
||||
### 🚑 Other information
|
||||
|
||||
|
||||
<!-- Please enter other information such as screenshots above. -->
|
||||
<!-- From: https://github.com/one-template/issue-template -->
|
||||
|
3
.github/ISSUE_TEMPLATE/question.md
vendored
3
.github/ISSUE_TEMPLATE/question.md
vendored
@ -8,16 +8,13 @@ assignees: ''
|
||||
|
||||
### 🧐 Problem Description
|
||||
|
||||
|
||||
<!-- Describe the problem in detail so that everyone can understand. -->
|
||||
|
||||
### 💻 Sample code
|
||||
|
||||
|
||||
<!-- If you have a solution, state it clearly here. -->
|
||||
|
||||
### 🚑 Other information
|
||||
|
||||
|
||||
<!-- Other information such as screenshots can be posted here. -->
|
||||
<!-- From: https://github.com/one-template/issue-template -->
|
||||
|
25
.github/renovate.json5
vendored
Normal file
25
.github/renovate.json5
vendored
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||
extends: [
|
||||
'config:base',
|
||||
'schedule:weekly',
|
||||
'group:allNonMajor',
|
||||
':semanticCommitTypeAll(chore)',
|
||||
],
|
||||
labels: ['dependencies'],
|
||||
pin: false,
|
||||
rangeStrategy: 'bump',
|
||||
node: false,
|
||||
packageRules: [
|
||||
{
|
||||
depTypeList: ['peerDependencies'],
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
ignoreDeps: [
|
||||
'typescript',
|
||||
|
||||
// Pure ESM
|
||||
'camelcase',
|
||||
],
|
||||
}
|
67
.github/workflows/ci.yml
vendored
Normal file
67
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,67 @@
|
||||
name: CI
|
||||
|
||||
permissions: {}
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.event.number || github.sha }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 20
|
||||
runs-on: ubuntu-latest
|
||||
name: Unit Test
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
|
||||
- name: Install deps
|
||||
run: pnpm install
|
||||
|
||||
- name: Test unit
|
||||
run: pnpm run test
|
||||
|
||||
lint:
|
||||
timeout-minutes: 10
|
||||
runs-on: ubuntu-latest
|
||||
name: Lint
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
|
||||
- name: Install deps
|
||||
run: pnpm install
|
||||
|
||||
- name: Lint
|
||||
run: pnpm run lint
|
||||
|
||||
- name: Check formatting
|
||||
run: pnpm prettier --check .
|
||||
|
||||
- name: Typecheck
|
||||
run: pnpm run typecheck
|
4
.github/workflows/issue-reply.yml
vendored
4
.github/workflows/issue-reply.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
steps:
|
||||
- name: help wanted
|
||||
if: github.event.label.name == 'help wanted'
|
||||
uses: actions-cool/issues-helper@v2.5.0
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
@ -19,7 +19,7 @@ jobs:
|
||||
|
||||
- name: need reproduction
|
||||
if: github.event.label.name == 'need reproduction'
|
||||
uses: actions-cool/issues-helper@v2.5.0
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
|
40
.github/workflows/release.yml
vendored
Normal file
40
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,40 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Set node
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: pnpm
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
||||
- run: npx changelogithub
|
||||
continue-on-error: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
||||
|
||||
- name: Install Dependencies
|
||||
run: pnpm i
|
||||
|
||||
- name: PNPM build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Publish to NPM
|
||||
run: pnpm -r publish --access public --no-git-checks
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -1,10 +1,7 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
5
.prettierignore
Normal file
5
.prettierignore
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules
|
||||
dist
|
||||
coverage
|
||||
pnpm-lock.yaml
|
||||
CHANGELOG.md
|
3
.prettierrc
Normal file
3
.prettierrc
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"singleQuote": true
|
||||
}
|
3
global.d.ts
vendored
3
global.d.ts
vendored
@ -1,3 +0,0 @@
|
||||
declare module '*.js';
|
||||
declare module '@babel/helper-module-imports';
|
||||
declare module '@babel/plugin-syntax-jsx';
|
@ -1,8 +0,0 @@
|
||||
{
|
||||
"npmClient": "yarn",
|
||||
"useWorkspaces": true,
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "1.1.1"
|
||||
}
|
6
netlify.toml
Normal file
6
netlify.toml
Normal file
@ -0,0 +1,6 @@
|
||||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
|
||||
[build]
|
||||
command = "pnpm run build"
|
||||
publish = "packages/jsx-explorer/dist"
|
38
package.json
38
package.json
@ -1,14 +1,13 @@
|
||||
{
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"packages/*"
|
||||
],
|
||||
"packageManager": "pnpm@8.6.2",
|
||||
"scripts": {
|
||||
"publish": "lerna publish",
|
||||
"test": "lerna run test",
|
||||
"lint": "lerna run lint",
|
||||
"dev": "node scripts/dev.js",
|
||||
"site": "node scripts/site.js"
|
||||
"build": "pnpm run -r build",
|
||||
"test": "vitest",
|
||||
"lint": "eslint --cache .",
|
||||
"format": "prettier --write .",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"release": "bumpp -r"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@ -16,13 +15,20 @@
|
||||
"jsx"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-airbnb-typescript": "^12.3.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"lerna": "^3.22.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "18.8.0"
|
||||
"@rollup/plugin-babel": "^6.0.3",
|
||||
"@types/babel__core": "^7.20.1",
|
||||
"@types/node": "^20.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.11",
|
||||
"@vitest/coverage-v8": "^0.32.2",
|
||||
"bumpp": "^9.1.1",
|
||||
"eslint": "^8.43.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"jsdom": "^22.1.0",
|
||||
"prettier": "^2.8.8",
|
||||
"tsup": "^7.0.0",
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vitest": "^0.32.2"
|
||||
}
|
||||
}
|
||||
|
4
packages/babel-helper-vue-transform-on/index.d.ts
vendored
Normal file
4
packages/babel-helper-vue-transform-on/index.d.ts
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
declare function transformOn(
|
||||
obj: Record<string, any>
|
||||
): Record<`on${string}`, any>;
|
||||
export default transformOn;
|
@ -1,7 +1,7 @@
|
||||
const transformOn = (obj) => {
|
||||
const result = {};
|
||||
Object.keys(obj).forEach((evt) => {
|
||||
result[`on${evt[0].toUpperCase()}${evt.substr(1)}`] = obj[evt];
|
||||
result[`on${evt[0].toUpperCase()}${evt.slice(1)}`] = obj[evt];
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
@ -4,5 +4,6 @@
|
||||
"description": "to help transform on",
|
||||
"author": "Amour1688 <lcz_1996@foxmail.com>",
|
||||
"license": "MIT",
|
||||
"main": "index.js"
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts"
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Vue 3 Babel JSX 插件
|
||||
|
||||
[![CircleCI](https://circleci.com/gh/vuejs/babel-plugin-jsx.svg?style=svg)](https://circleci.com/gh/vuejs/vue-next) [![npm package](https://img.shields.io/npm/v/@vue/babel-plugin-jsx.svg?style=flat-square)](https://www.npmjs.com/package/@vue/babel-plugin-jsx)
|
||||
[![npm package](https://img.shields.io/npm/v/@vue/babel-plugin-jsx.svg?style=flat-square)](https://www.npmjs.com/package/@vue/babel-plugin-jsx)
|
||||
[![issues-helper](https://img.shields.io/badge/Issues%20Manage%20By-issues--helper-orange?style=flat-square)](https://github.com/actions-cool/issues-helper)
|
||||
|
||||
以 JSX 的方式来编写 Vue 代码
|
||||
@ -92,7 +92,7 @@ const App = {
|
||||
```
|
||||
|
||||
```jsx
|
||||
import { withModifiers, defineComponent } from "vue";
|
||||
import { withModifiers, defineComponent } from 'vue';
|
||||
|
||||
const App = defineComponent({
|
||||
setup() {
|
||||
@ -103,7 +103,7 @@ const App = defineComponent({
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div onClick={withModifiers(inc, ["self"])}>{count.value}</div>
|
||||
<div onClick={withModifiers(inc, ['self'])}>{count.value}</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
@ -129,7 +129,7 @@ const App = () => <input type="email" />;
|
||||
动态绑定:
|
||||
|
||||
```jsx
|
||||
const placeholderText = "email";
|
||||
const placeholderText = 'email';
|
||||
const App = () => <input type="email" placeholder={placeholderText} />;
|
||||
```
|
||||
|
||||
@ -161,11 +161,11 @@ const App = {
|
||||
```
|
||||
|
||||
```jsx
|
||||
<input v-model={[val, ["modifier"]]} />
|
||||
<input v-model={[val, ['modifier']]} />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<A v-model={[val, "argument", ["modifier"]]} />
|
||||
<A v-model={[val, 'argument', ['modifier']]} />
|
||||
```
|
||||
|
||||
会编译成:
|
||||
@ -176,7 +176,7 @@ h(A, {
|
||||
argumentModifiers: {
|
||||
modifier: true,
|
||||
},
|
||||
"onUpdate:argument": ($event) => (val = $event),
|
||||
'onUpdate:argument': ($event) => (val = $event),
|
||||
});
|
||||
```
|
||||
|
||||
@ -185,14 +185,14 @@ h(A, {
|
||||
> 注意: 你应该传递一个二维数组给 v-models。
|
||||
|
||||
```jsx
|
||||
<A v-models={[[foo], [bar, "bar"]]} />
|
||||
<A v-models={[[foo], [bar, 'bar']]} />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<A
|
||||
v-models={[
|
||||
[foo, "foo"],
|
||||
[bar, "bar"],
|
||||
[foo, 'foo'],
|
||||
[bar, 'bar'],
|
||||
]}
|
||||
/>
|
||||
```
|
||||
@ -200,8 +200,8 @@ h(A, {
|
||||
```jsx
|
||||
<A
|
||||
v-models={[
|
||||
[foo, ["modifier"]],
|
||||
[bar, "bar", ["modifier"]],
|
||||
[foo, ['modifier']],
|
||||
[bar, 'bar', ['modifier']],
|
||||
]}
|
||||
/>
|
||||
```
|
||||
@ -214,12 +214,12 @@ h(A, {
|
||||
modelModifiers: {
|
||||
modifier: true,
|
||||
},
|
||||
"onUpdate:modelValue": ($event) => (foo = $event),
|
||||
'onUpdate:modelValue': ($event) => (foo = $event),
|
||||
bar: bar,
|
||||
barModifiers: {
|
||||
modifier: true,
|
||||
},
|
||||
"onUpdate:bar": ($event) => (bar = $event),
|
||||
'onUpdate:bar': ($event) => (bar = $event),
|
||||
});
|
||||
```
|
||||
|
||||
@ -240,7 +240,7 @@ const App = {
|
||||
const App = {
|
||||
directives: { custom: customDirective },
|
||||
setup() {
|
||||
return () => <a v-custom={[val, "arg", ["a", "b"]]} />;
|
||||
return () => <a v-custom={[val, 'arg', ['a', 'b']]} />;
|
||||
},
|
||||
};
|
||||
```
|
||||
@ -252,8 +252,8 @@ const App = {
|
||||
```jsx
|
||||
const A = (props, { slots }) => (
|
||||
<>
|
||||
<h1>{ slots.default ? slots.default() : 'foo' }</h1>
|
||||
<h2>{ slots.bar?.() }</h2>
|
||||
<h1>{slots.default ? slots.default() : 'foo'}</h1>
|
||||
<h2>{slots.bar?.()}</h2>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -293,7 +293,7 @@ const App = {
|
||||
bar: () => <span>B</span>,
|
||||
}}
|
||||
</A>
|
||||
<B>{() => "foo"}</B>
|
||||
<B>{() => 'foo'}</B>
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
# Babel Plugin JSX for Vue 3.0
|
||||
# Babel Plugin JSX for Vue 3
|
||||
|
||||
[![CircleCI](https://circleci.com/gh/vuejs/babel-plugin-jsx.svg?style=svg)](https://circleci.com/gh/vuejs/babel-plugin-jsx) [![npm package](https://img.shields.io/npm/v/@vue/babel-plugin-jsx.svg?style=flat-square)](https://www.npmjs.com/package/@vue/babel-plugin-jsx)
|
||||
[![npm package](https://img.shields.io/npm/v/@vue/babel-plugin-jsx.svg?style=flat-square)](https://www.npmjs.com/package/@vue/babel-plugin-jsx)
|
||||
[![issues-helper](https://img.shields.io/badge/Issues%20Manage%20By-issues--helper-blueviolet?style=flat-square)](https://github.com/actions-cool/issues-helper)
|
||||
|
||||
To add Vue JSX support.
|
||||
@ -96,7 +96,7 @@ const App = {
|
||||
```
|
||||
|
||||
```jsx
|
||||
import { withModifiers, defineComponent } from "vue";
|
||||
import { withModifiers, defineComponent } from 'vue';
|
||||
|
||||
const App = defineComponent({
|
||||
setup() {
|
||||
@ -107,7 +107,7 @@ const App = defineComponent({
|
||||
};
|
||||
|
||||
return () => (
|
||||
<div onClick={withModifiers(inc, ["self"])}>{count.value}</div>
|
||||
<div onClick={withModifiers(inc, ['self'])}>{count.value}</div>
|
||||
);
|
||||
},
|
||||
});
|
||||
@ -133,7 +133,7 @@ const App = () => <input type="email" />;
|
||||
with a dynamic binding:
|
||||
|
||||
```jsx
|
||||
const placeholderText = "email";
|
||||
const placeholderText = 'email';
|
||||
const App = () => <input type="email" placeholder={placeholderText} />;
|
||||
```
|
||||
|
||||
@ -165,11 +165,11 @@ const App = {
|
||||
```
|
||||
|
||||
```jsx
|
||||
<input v-model={[val, ["modifier"]]} />
|
||||
<input v-model={[val, ['modifier']]} />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<A v-model={[val, "argument", ["modifier"]]} />
|
||||
<A v-model={[val, 'argument', ['modifier']]} />
|
||||
```
|
||||
|
||||
Will compile to:
|
||||
@ -180,7 +180,7 @@ h(A, {
|
||||
argumentModifiers: {
|
||||
modifier: true,
|
||||
},
|
||||
"onUpdate:argument": ($event) => (val = $event),
|
||||
'onUpdate:argument': ($event) => (val = $event),
|
||||
});
|
||||
```
|
||||
|
||||
@ -189,14 +189,14 @@ h(A, {
|
||||
> Note: You should pass a Two-dimensional Arrays to v-models.
|
||||
|
||||
```jsx
|
||||
<A v-models={[[foo], [bar, "bar"]]} />
|
||||
<A v-models={[[foo], [bar, 'bar']]} />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<A
|
||||
v-models={[
|
||||
[foo, "foo"],
|
||||
[bar, "bar"],
|
||||
[foo, 'foo'],
|
||||
[bar, 'bar'],
|
||||
]}
|
||||
/>
|
||||
```
|
||||
@ -204,8 +204,8 @@ h(A, {
|
||||
```jsx
|
||||
<A
|
||||
v-models={[
|
||||
[foo, ["modifier"]],
|
||||
[bar, "bar", ["modifier"]],
|
||||
[foo, ['modifier']],
|
||||
[bar, 'bar', ['modifier']],
|
||||
]}
|
||||
/>
|
||||
```
|
||||
@ -218,12 +218,12 @@ h(A, {
|
||||
modelModifiers: {
|
||||
modifier: true,
|
||||
},
|
||||
"onUpdate:modelValue": ($event) => (foo = $event),
|
||||
'onUpdate:modelValue': ($event) => (foo = $event),
|
||||
bar: bar,
|
||||
barModifiers: {
|
||||
modifier: true,
|
||||
},
|
||||
"onUpdate:bar": ($event) => (bar = $event),
|
||||
'onUpdate:bar': ($event) => (bar = $event),
|
||||
});
|
||||
```
|
||||
|
||||
@ -244,7 +244,7 @@ const App = {
|
||||
const App = {
|
||||
directives: { custom: customDirective },
|
||||
setup() {
|
||||
return () => <a v-custom={[val, "arg", ["a", "b"]]} />;
|
||||
return () => <a v-custom={[val, 'arg', ['a', 'b']]} />;
|
||||
},
|
||||
};
|
||||
```
|
||||
@ -256,8 +256,8 @@ const App = {
|
||||
```jsx
|
||||
const A = (props, { slots }) => (
|
||||
<>
|
||||
<h1>{ slots.default ? slots.default() : 'foo' }</h1>
|
||||
<h2>{ slots.bar?.() }</h2>
|
||||
<h1>{slots.default ? slots.default() : 'foo'}</h1>
|
||||
<h2>{slots.bar?.()}</h2>
|
||||
</>
|
||||
);
|
||||
|
||||
@ -297,7 +297,7 @@ const App = {
|
||||
bar: () => <span>B</span>,
|
||||
}}
|
||||
</A>
|
||||
<B>{() => "foo"}</B>
|
||||
<B>{() => 'foo'}</B>
|
||||
</>
|
||||
);
|
||||
},
|
||||
|
@ -1,10 +0,0 @@
|
||||
/* istanbul ignore next */
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
],
|
||||
plugins: [
|
||||
/* eslint-disable-next-line global-require */
|
||||
[require('./dist/index.js'), { optimize: true, isCustomElement: (tag) => /^x-/.test(tag) }],
|
||||
],
|
||||
};
|
2
packages/babel-plugin-jsx/global.d.ts
vendored
2
packages/babel-plugin-jsx/global.d.ts
vendored
@ -1,2 +0,0 @@
|
||||
declare module '@babel/helper-module-imports';
|
||||
declare module '@babel/plugin-syntax-jsx';
|
@ -1,11 +0,0 @@
|
||||
module.exports = {
|
||||
setupFiles: ['./test/setup.ts'],
|
||||
transform: {
|
||||
'\\.(ts|tsx)$': 'ts-jest',
|
||||
},
|
||||
globals: {
|
||||
'ts-jest': {
|
||||
babelConfig: true,
|
||||
},
|
||||
},
|
||||
};
|
@ -1,21 +1,20 @@
|
||||
{
|
||||
"name": "@vue/babel-plugin-jsx",
|
||||
"version": "1.1.1",
|
||||
"description": "Babel plugin for Vue 3.0 JSX",
|
||||
"description": "Babel plugin for Vue 3 JSX",
|
||||
"author": "Amour1688 <lcz_1996@foxmail.com>",
|
||||
"homepage": "https://github.com/vuejs/babel-plugin-jsx/tree/dev/packages/babel-plugin-jsx#readme",
|
||||
"license": "MIT",
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/index.d.ts",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vuejs/babel-plugin-jsx.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rm -rf dist && tsc",
|
||||
"watch": "rm -rf dist && tsc --watch",
|
||||
"lint": "eslint 'src/*.ts'",
|
||||
"test": "yarn build && jest --coverage",
|
||||
"prepublishOnly": "yarn build"
|
||||
"build": "tsup",
|
||||
"watch": "tsup --watch"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/vuejs/babel-plugin-jsx/issues"
|
||||
@ -24,30 +23,26 @@
|
||||
"dist"
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/helper-module-imports": "^7.18.6",
|
||||
"@babel/plugin-syntax-jsx": "^7.18.6",
|
||||
"@babel/template": "^7.18.10",
|
||||
"@babel/traverse": "^7.19.4",
|
||||
"@babel/types": "^7.19.4",
|
||||
"@vue/babel-helper-vue-transform-on": "^1.0.2",
|
||||
"@babel/helper-module-imports": "^7.22.5",
|
||||
"@babel/plugin-syntax-jsx": "^7.22.5",
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/traverse": "^7.22.5",
|
||||
"@babel/types": "^7.22.5",
|
||||
"@vue/babel-helper-vue-transform-on": "workspace:^",
|
||||
"camelcase": "^6.3.0",
|
||||
"html-tags": "^3.2.0",
|
||||
"html-tags": "^3.3.1",
|
||||
"svg-tags": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.3",
|
||||
"@babel/preset-env": "^7.19.4",
|
||||
"@types/jest": "^26.0.24",
|
||||
"@babel/core": "^7.22.5",
|
||||
"@babel/preset-env": "^7.22.5",
|
||||
"@types/babel__template": "^7.4.1",
|
||||
"@types/babel__traverse": "^7.20.1",
|
||||
"@types/svg-tags": "^1.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^4.33.0",
|
||||
"@typescript-eslint/parser": "^4.33.0",
|
||||
"@vue/compiler-dom": "3.2.41",
|
||||
"@vue/test-utils": "2.0.0-beta.2",
|
||||
"jest": "^26.6.3",
|
||||
"regenerator-runtime": "^0.13.10",
|
||||
"ts-jest": "^26.5.6",
|
||||
"typescript": "^4.8.4",
|
||||
"vue": "3.2.41"
|
||||
"@vue/runtime-dom": "^3.3.4",
|
||||
"@vue/test-utils": "^2.3.2",
|
||||
"regenerator-runtime": "^0.13.11",
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@babel/core": "^7.0.0-0"
|
||||
|
@ -1,12 +1,14 @@
|
||||
import * as t from '@babel/types';
|
||||
import * as BabelCore from '@babel/core';
|
||||
import type * as BabelCore from '@babel/core';
|
||||
import template from '@babel/template';
|
||||
// @ts-expect-error
|
||||
import syntaxJsx from '@babel/plugin-syntax-jsx';
|
||||
import { addNamed, isModule, addNamespace } from '@babel/helper-module-imports';
|
||||
import { NodePath } from '@babel/traverse';
|
||||
// @ts-expect-error
|
||||
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports';
|
||||
import { type NodePath } from '@babel/traverse';
|
||||
import transformVueJSX from './transform-vue-jsx';
|
||||
import sugarFragment from './sugar-fragment';
|
||||
import type { VueJSXPluginOptions, State } from './interface';
|
||||
import type { State, VueJSXPluginOptions } from './interface';
|
||||
|
||||
export { VueJSXPluginOptions };
|
||||
|
||||
@ -77,7 +79,7 @@ export default ({ types }: typeof BabelCore) => ({
|
||||
return importMap.runtimeIsSlot;
|
||||
}
|
||||
const { name: isVNodeName } = state.get(
|
||||
'isVNode',
|
||||
'isVNode'
|
||||
)() as t.Identifier;
|
||||
const isSlot = path.scope.generateUidIdentifier('isSlot');
|
||||
const ast = template.ast`
|
||||
@ -119,21 +121,24 @@ export default ({ types }: typeof BabelCore) => ({
|
||||
}
|
||||
const isSlot = path.scope.generateUidIdentifier('isSlot');
|
||||
const { object: objectName } = state.get(
|
||||
'isVNode',
|
||||
'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));
|
||||
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 lastImport = nodePaths
|
||||
.filter(
|
||||
(p) => p.isVariableDeclaration()
|
||||
&& p.node.declarations.some(
|
||||
(d) => (d.id as t.Identifier)?.name === sourceName.name,
|
||||
),
|
||||
(p) =>
|
||||
p.isVariableDeclaration() &&
|
||||
p.node.declarations.some(
|
||||
(d) => (d.id as t.Identifier)?.name === sourceName.name
|
||||
)
|
||||
)
|
||||
.pop();
|
||||
if (lastImport) {
|
||||
@ -169,17 +174,18 @@ export default ({ types }: typeof BabelCore) => ({
|
||||
|
||||
body
|
||||
.filter(
|
||||
(nodePath) => t.isImportDeclaration(nodePath.node)
|
||||
&& nodePath.node.source.value === 'vue',
|
||||
(nodePath) =>
|
||||
t.isImportDeclaration(nodePath.node) &&
|
||||
nodePath.node.source.value === 'vue'
|
||||
)
|
||||
.forEach((nodePath) => {
|
||||
const { specifiers } = nodePath.node as t.ImportDeclaration;
|
||||
let shouldRemove = false;
|
||||
specifiers.forEach((specifier) => {
|
||||
if (
|
||||
!specifier.loc
|
||||
&& t.isImportSpecifier(specifier)
|
||||
&& t.isIdentifier(specifier.imported)
|
||||
!specifier.loc &&
|
||||
t.isImportSpecifier(specifier) &&
|
||||
t.isIdentifier(specifier.imported)
|
||||
) {
|
||||
specifiersMap.set(specifier.imported.name, specifier);
|
||||
shouldRemove = true;
|
||||
@ -191,12 +197,12 @@ export default ({ types }: typeof BabelCore) => ({
|
||||
});
|
||||
|
||||
const specifiers = [...specifiersMap.keys()].map(
|
||||
(imported) => specifiersMap.get(imported)!,
|
||||
(imported) => specifiersMap.get(imported)!
|
||||
);
|
||||
if (specifiers.length) {
|
||||
path.unshiftContainer(
|
||||
'body',
|
||||
t.importDeclaration(specifiers, t.stringLiteral('vue')),
|
||||
t.importDeclaration(specifiers, t.stringLiteral('vue'))
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -1,5 +1,5 @@
|
||||
import * as t from '@babel/types';
|
||||
import * as BabelCore from '@babel/core';
|
||||
import type * as t from '@babel/types';
|
||||
import type * as BabelCore from '@babel/core';
|
||||
|
||||
export type Slots = t.Identifier | t.ObjectExpression | null;
|
||||
|
||||
@ -7,7 +7,7 @@ export type State = {
|
||||
get: (name: string) => any;
|
||||
set: (name: string, value: any) => any;
|
||||
opts: VueJSXPluginOptions;
|
||||
file: BabelCore.BabelFile
|
||||
file: BabelCore.BabelFile;
|
||||
};
|
||||
|
||||
export interface VueJSXPluginOptions {
|
||||
|
@ -1,9 +1,13 @@
|
||||
import * as t from '@babel/types';
|
||||
import { NodePath } from '@babel/traverse';
|
||||
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;
|
||||
export type Tag =
|
||||
| t.Identifier
|
||||
| t.MemberExpression
|
||||
| t.StringLiteral
|
||||
| t.CallExpression;
|
||||
|
||||
/**
|
||||
* Get JSX element type
|
||||
@ -11,37 +15,35 @@ export type Tag = t.Identifier | t.MemberExpression | t.StringLiteral | t.CallEx
|
||||
* @param path Path<JSXOpeningElement>
|
||||
*/
|
||||
const getType = (path: NodePath<t.JSXOpeningElement>) => {
|
||||
const typePath = path
|
||||
.get('attributes')
|
||||
.find((attribute) => {
|
||||
if (!t.isJSXAttribute(attribute)) {
|
||||
return false;
|
||||
}
|
||||
return t.isJSXIdentifier(attribute.get('name'))
|
||||
&& (attribute.get('name') as NodePath<t.JSXIdentifier>).node.name === 'type';
|
||||
}) as NodePath<t.JSXAttribute> | undefined;
|
||||
const typePath = path.get('attributes').find((attribute) => {
|
||||
if (!attribute.isJSXAttribute()) {
|
||||
return false;
|
||||
}
|
||||
return (
|
||||
attribute.get('name').isJSXIdentifier() &&
|
||||
(attribute.get('name') as NodePath<t.JSXIdentifier>).node.name === 'type'
|
||||
);
|
||||
}) as NodePath<t.JSXAttribute> | undefined;
|
||||
|
||||
return typePath ? typePath.get('value').node : null;
|
||||
};
|
||||
|
||||
const parseModifiers = (value: any): string[] => (
|
||||
const parseModifiers = (value: any): string[] =>
|
||||
t.isArrayExpression(value)
|
||||
? value.elements
|
||||
.map((el) => (t.isStringLiteral(el) ? el.value : ''))
|
||||
.filter(Boolean)
|
||||
: []);
|
||||
.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 { path, value, state, tag, isComponent } = params;
|
||||
const args: Array<t.Expression | t.NullLiteral> = [];
|
||||
const vals: t.Expression[] = [];
|
||||
const modifiersSet: Set<string>[] = [];
|
||||
@ -70,7 +72,7 @@ const parseDirectives = (params: {
|
||||
|
||||
const isVModels = directiveName === 'models';
|
||||
const isVModel = directiveName === 'model';
|
||||
if (isVModel && !t.isJSXExpressionContainer(path.get('value'))) {
|
||||
if (isVModel && !path.get('value').isJSXExpressionContainer()) {
|
||||
throw new Error('You have to use JSX Expression inside your v-model');
|
||||
}
|
||||
|
||||
@ -78,8 +80,9 @@ const parseDirectives = (params: {
|
||||
throw new Error('v-models can only use in custom components');
|
||||
}
|
||||
|
||||
const shouldResolve = !['html', 'text', 'model', 'models'].includes(directiveName)
|
||||
|| (isVModel && !isComponent);
|
||||
const shouldResolve =
|
||||
!['html', 'text', 'model', 'models'].includes(directiveName) ||
|
||||
(isVModel && !isComponent);
|
||||
|
||||
let modifiers = directiveModifiers;
|
||||
|
||||
@ -94,7 +97,11 @@ const parseDirectives = (params: {
|
||||
const { elements } = element as t.ArrayExpression;
|
||||
const [first, second, third] = elements;
|
||||
|
||||
if (second && !t.isArrayExpression(second) && !t.isSpreadElement(second)) {
|
||||
if (
|
||||
second &&
|
||||
!t.isArrayExpression(second) &&
|
||||
!t.isSpreadElement(second)
|
||||
) {
|
||||
args.push(second);
|
||||
modifiers = parseModifiers(third as t.ArrayExpression);
|
||||
} else if (t.isArrayExpression(second)) {
|
||||
@ -122,21 +129,21 @@ const parseDirectives = (params: {
|
||||
modifiers: modifiersSet,
|
||||
values: vals.length ? vals : [value],
|
||||
args,
|
||||
directive: shouldResolve ? [
|
||||
resolveDirective(path, state, tag, directiveName),
|
||||
vals[0] || value,
|
||||
modifiersSet[0]?.size
|
||||
? args[0] || t.unaryExpression('void', t.numericLiteral(0), true)
|
||||
: args[0],
|
||||
!!modifiersSet[0]?.size && t.objectExpression(
|
||||
[...modifiersSet[0]].map(
|
||||
(modifier) => t.objectProperty(
|
||||
t.identifier(modifier),
|
||||
t.booleanLiteral(true),
|
||||
),
|
||||
),
|
||||
),
|
||||
].filter(Boolean) as t.Expression[] : undefined,
|
||||
directive: shouldResolve
|
||||
? ([
|
||||
resolveDirective(path, state, tag, directiveName),
|
||||
vals[0] || value,
|
||||
modifiersSet[0]?.size
|
||||
? args[0] || t.unaryExpression('void', t.numericLiteral(0), true)
|
||||
: args[0],
|
||||
!!modifiersSet[0]?.size &&
|
||||
t.objectExpression(
|
||||
[...modifiersSet[0]].map((modifier) =>
|
||||
t.objectProperty(t.identifier(modifier), t.booleanLiteral(true))
|
||||
)
|
||||
),
|
||||
].filter(Boolean) as t.Expression[])
|
||||
: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
@ -144,7 +151,7 @@ const resolveDirective = (
|
||||
path: NodePath<t.JSXAttribute>,
|
||||
state: State,
|
||||
tag: Tag,
|
||||
directiveName: string,
|
||||
directiveName: string
|
||||
) => {
|
||||
if (directiveName === 'show') {
|
||||
return createIdentifier(state, 'vShow');
|
||||
@ -177,11 +184,9 @@ const resolveDirective = (
|
||||
}
|
||||
return modelToUse;
|
||||
}
|
||||
return t.callExpression(
|
||||
createIdentifier(state, 'resolveDirective'), [
|
||||
t.stringLiteral(directiveName),
|
||||
],
|
||||
);
|
||||
return t.callExpression(createIdentifier(state, 'resolveDirective'), [
|
||||
t.stringLiteral(directiveName),
|
||||
]);
|
||||
};
|
||||
|
||||
export default parseDirectives;
|
||||
|
@ -1,34 +1,36 @@
|
||||
import * as t from '@babel/types';
|
||||
import { NodePath } from '@babel/traverse';
|
||||
import { type NodePath } from '@babel/traverse';
|
||||
import type { State } from './interface';
|
||||
import { createIdentifier, FRAGMENT } from './utils';
|
||||
import { FRAGMENT, createIdentifier } from './utils';
|
||||
|
||||
const transformFragment = (
|
||||
path: NodePath<t.JSXElement>,
|
||||
Fragment: t.JSXIdentifier | t.JSXMemberExpression,
|
||||
Fragment: t.JSXIdentifier | t.JSXMemberExpression
|
||||
) => {
|
||||
const children = path.get('children') || [];
|
||||
return t.jsxElement(
|
||||
t.jsxOpeningElement(Fragment, []),
|
||||
t.jsxClosingElement(Fragment),
|
||||
children.map(({ node }) => node),
|
||||
false,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
export default ({
|
||||
export default {
|
||||
JSXFragment: {
|
||||
enter(path: NodePath<t.JSXElement>, state: State) {
|
||||
const fragmentCallee = createIdentifier(state, FRAGMENT);
|
||||
path.replaceWith(transformFragment(
|
||||
path,
|
||||
t.isIdentifier(fragmentCallee)
|
||||
? t.jsxIdentifier(fragmentCallee.name)
|
||||
: t.jsxMemberExpression(
|
||||
t.jsxIdentifier((fragmentCallee.object as t.Identifier).name),
|
||||
t.jsxIdentifier((fragmentCallee.property as t.Identifier).name),
|
||||
),
|
||||
));
|
||||
path.replaceWith(
|
||||
transformFragment(
|
||||
path,
|
||||
t.isIdentifier(fragmentCallee)
|
||||
? t.jsxIdentifier(fragmentCallee.name)
|
||||
: t.jsxMemberExpression(
|
||||
t.jsxIdentifier((fragmentCallee.object as t.Identifier).name),
|
||||
t.jsxIdentifier((fragmentCallee.property as t.Identifier).name)
|
||||
)
|
||||
)
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -1,26 +1,27 @@
|
||||
import * as t from '@babel/types';
|
||||
import { NodePath } from '@babel/traverse';
|
||||
import { type NodePath } from '@babel/traverse';
|
||||
// @ts-expect-error
|
||||
import { addDefault } from '@babel/helper-module-imports';
|
||||
import {
|
||||
buildIIFE,
|
||||
checkIsComponent,
|
||||
createIdentifier,
|
||||
dedupeProperties,
|
||||
getJSXAttributeName,
|
||||
getTag,
|
||||
isConstant,
|
||||
isDirective,
|
||||
isOn,
|
||||
transformJSXExpressionContainer,
|
||||
transformJSXSpreadAttribute,
|
||||
transformJSXSpreadChild,
|
||||
transformJSXText,
|
||||
transformJSXExpressionContainer,
|
||||
walksScope,
|
||||
buildIIFE,
|
||||
isDirective,
|
||||
checkIsComponent,
|
||||
getTag,
|
||||
getJSXAttributeName,
|
||||
isOn,
|
||||
isConstant,
|
||||
dedupeProperties,
|
||||
transformJSXSpreadAttribute,
|
||||
} from './utils';
|
||||
import SlotFlags from './slotFlags';
|
||||
import { PatchFlags } from './patchFlags';
|
||||
import parseDirectives from './parseDirectives';
|
||||
import type { State, Slots } from './interface';
|
||||
import type { Slots, State } from './interface';
|
||||
|
||||
const xlinkRE = /^xlink([A-Z])/;
|
||||
|
||||
@ -28,10 +29,8 @@ type ExcludesBoolean = <T>(x: T | false | true) => x is T;
|
||||
|
||||
const getJSXAttributeValue = (
|
||||
path: NodePath<t.JSXAttribute>,
|
||||
state: State,
|
||||
): (
|
||||
t.StringLiteral | t.Expression | null
|
||||
) => {
|
||||
state: State
|
||||
): t.StringLiteral | t.Expression | null => {
|
||||
const valuePath = path.get('value');
|
||||
if (valuePath.isJSXElement()) {
|
||||
return transformJSXElement(valuePath, state);
|
||||
@ -77,60 +76,57 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
||||
let hasHydrationEventBinding = false;
|
||||
let hasDynamicKeys = false;
|
||||
|
||||
const mergeArgs: (t.CallExpression | t.ObjectExpression | t.Identifier)[] = [];
|
||||
const mergeArgs: (t.CallExpression | t.ObjectExpression | t.Identifier)[] =
|
||||
[];
|
||||
const { mergeProps = true } = state.opts;
|
||||
props
|
||||
.forEach((prop) => {
|
||||
if (prop.isJSXAttribute()) {
|
||||
let name = getJSXAttributeName(prop);
|
||||
props.forEach((prop) => {
|
||||
if (prop.isJSXAttribute()) {
|
||||
let name = getJSXAttributeName(prop);
|
||||
|
||||
const attributeValue = getJSXAttributeValue(prop, state);
|
||||
const attributeValue = getJSXAttributeValue(prop, state);
|
||||
|
||||
if (!isConstant(attributeValue) || name === 'ref') {
|
||||
if (
|
||||
!isComponent
|
||||
&& isOn(name)
|
||||
// omit the flag for click handlers becaues hydration gives click
|
||||
// dedicated fast path.
|
||||
&& name.toLowerCase() !== 'onclick'
|
||||
// omit v-model handlers
|
||||
&& name !== 'onUpdate:modelValue'
|
||||
) {
|
||||
hasHydrationEventBinding = true;
|
||||
}
|
||||
|
||||
if (name === 'ref') {
|
||||
hasRef = true;
|
||||
} else if (name === 'class' && !isComponent) {
|
||||
hasClassBinding = true;
|
||||
} else if (name === 'style' && !isComponent) {
|
||||
hasStyleBinding = true;
|
||||
} else if (
|
||||
name !== 'key'
|
||||
&& !isDirective(name)
|
||||
&& name !== 'on'
|
||||
) {
|
||||
dynamicPropNames.add(name);
|
||||
}
|
||||
if (!isConstant(attributeValue) || name === 'ref') {
|
||||
if (
|
||||
!isComponent &&
|
||||
isOn(name) &&
|
||||
// omit the flag for click handlers becaues hydration gives click
|
||||
// dedicated fast path.
|
||||
name.toLowerCase() !== 'onclick' &&
|
||||
// omit v-model handlers
|
||||
name !== 'onUpdate:modelValue'
|
||||
) {
|
||||
hasHydrationEventBinding = true;
|
||||
}
|
||||
if (state.opts.transformOn && (name === 'on' || name === 'nativeOn')) {
|
||||
if (!state.get('transformOn')) {
|
||||
state.set('transformOn', addDefault(
|
||||
path,
|
||||
'@vue/babel-helper-vue-transform-on',
|
||||
{ nameHint: '_transformOn' },
|
||||
));
|
||||
}
|
||||
mergeArgs.push(t.callExpression(
|
||||
state.get('transformOn'),
|
||||
[attributeValue || t.booleanLiteral(true)],
|
||||
));
|
||||
return;
|
||||
|
||||
if (name === 'ref') {
|
||||
hasRef = true;
|
||||
} else if (name === 'class' && !isComponent) {
|
||||
hasClassBinding = true;
|
||||
} else if (name === 'style' && !isComponent) {
|
||||
hasStyleBinding = true;
|
||||
} else if (name !== 'key' && !isDirective(name) && name !== 'on') {
|
||||
dynamicPropNames.add(name);
|
||||
}
|
||||
if (isDirective(name)) {
|
||||
const {
|
||||
directive, modifiers, values, args, directiveName,
|
||||
} = parseDirectives({
|
||||
}
|
||||
if (state.opts.transformOn && (name === 'on' || name === 'nativeOn')) {
|
||||
if (!state.get('transformOn')) {
|
||||
state.set(
|
||||
'transformOn',
|
||||
addDefault(path, '@vue/babel-helper-vue-transform-on', {
|
||||
nameHint: '_transformOn',
|
||||
})
|
||||
);
|
||||
}
|
||||
mergeArgs.push(
|
||||
t.callExpression(state.get('transformOn'), [
|
||||
attributeValue || t.booleanLiteral(true),
|
||||
])
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (isDirective(name)) {
|
||||
const { directive, modifiers, values, args, directiveName } =
|
||||
parseDirectives({
|
||||
tag,
|
||||
isComponent,
|
||||
name,
|
||||
@ -139,107 +135,140 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
||||
value: attributeValue,
|
||||
});
|
||||
|
||||
if (directiveName === 'slots') {
|
||||
slots = attributeValue as Slots;
|
||||
return;
|
||||
}
|
||||
if (directive) {
|
||||
directives.push(t.arrayExpression(directive));
|
||||
} else if (directiveName === 'html') {
|
||||
properties.push(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');
|
||||
}
|
||||
if (directiveName === 'slots') {
|
||||
slots = attributeValue as Slots;
|
||||
return;
|
||||
}
|
||||
if (directive) {
|
||||
directives.push(t.arrayExpression(directive));
|
||||
} else if (directiveName === 'html') {
|
||||
properties.push(
|
||||
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');
|
||||
}
|
||||
|
||||
if (['models', 'model'].includes(directiveName)) {
|
||||
values.forEach((value, index) => {
|
||||
const propName = args[index];
|
||||
// v-model target with variable
|
||||
const isDynamic = propName && !t.isStringLiteral(propName) && !t.isNullLiteral(propName);
|
||||
|
||||
// must be v-model or v-models and is a component
|
||||
if (!directive) {
|
||||
properties.push(
|
||||
t.objectProperty(t.isNullLiteral(propName)
|
||||
? t.stringLiteral('modelValue') : propName, value as any, isDynamic),
|
||||
);
|
||||
if (!isDynamic) {
|
||||
dynamicPropNames.add((propName as t.StringLiteral)?.value || 'modelValue');
|
||||
}
|
||||
|
||||
if (modifiers[index]?.size) {
|
||||
properties.push(
|
||||
t.objectProperty(
|
||||
isDynamic
|
||||
? t.binaryExpression('+', propName, t.stringLiteral('Modifiers'))
|
||||
: t.stringLiteral(`${(propName as t.StringLiteral)?.value || 'model'}Modifiers`),
|
||||
t.objectExpression(
|
||||
[...modifiers[index]].map((modifier) => t.objectProperty(
|
||||
t.stringLiteral(modifier),
|
||||
t.booleanLiteral(true),
|
||||
)),
|
||||
),
|
||||
isDynamic,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const updateName = isDynamic
|
||||
? t.binaryExpression('+', t.stringLiteral('onUpdate'), propName)
|
||||
: t.stringLiteral(`onUpdate:${(propName as t.StringLiteral)?.value || 'modelValue'}`);
|
||||
if (['models', 'model'].includes(directiveName)) {
|
||||
values.forEach((value, index) => {
|
||||
const propName = args[index];
|
||||
// v-model target with variable
|
||||
const isDynamic =
|
||||
propName &&
|
||||
!t.isStringLiteral(propName) &&
|
||||
!t.isNullLiteral(propName);
|
||||
|
||||
// must be v-model or v-models and is a component
|
||||
if (!directive) {
|
||||
properties.push(
|
||||
t.objectProperty(
|
||||
updateName,
|
||||
t.arrowFunctionExpression(
|
||||
[t.identifier('$event')],
|
||||
t.assignmentExpression('=', value as any, t.identifier('$event')),
|
||||
),
|
||||
isDynamic,
|
||||
),
|
||||
t.isNullLiteral(propName)
|
||||
? t.stringLiteral('modelValue')
|
||||
: propName,
|
||||
value as any,
|
||||
isDynamic
|
||||
)
|
||||
);
|
||||
|
||||
if (!isDynamic) {
|
||||
dynamicPropNames.add((updateName as t.StringLiteral).value);
|
||||
} else {
|
||||
hasDynamicKeys = true;
|
||||
dynamicPropNames.add(
|
||||
(propName as t.StringLiteral)?.value || 'modelValue'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (name.match(xlinkRE)) {
|
||||
name = name.replace(xlinkRE, (_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`);
|
||||
}
|
||||
properties.push(t.objectProperty(
|
||||
t.stringLiteral(name),
|
||||
attributeValue || t.booleanLiteral(true),
|
||||
));
|
||||
|
||||
if (modifiers[index]?.size) {
|
||||
properties.push(
|
||||
t.objectProperty(
|
||||
isDynamic
|
||||
? t.binaryExpression(
|
||||
'+',
|
||||
propName,
|
||||
t.stringLiteral('Modifiers')
|
||||
)
|
||||
: t.stringLiteral(
|
||||
`${
|
||||
(propName as t.StringLiteral)?.value || 'model'
|
||||
}Modifiers`
|
||||
),
|
||||
t.objectExpression(
|
||||
[...modifiers[index]].map((modifier) =>
|
||||
t.objectProperty(
|
||||
t.stringLiteral(modifier),
|
||||
t.booleanLiteral(true)
|
||||
)
|
||||
)
|
||||
),
|
||||
isDynamic
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const updateName = isDynamic
|
||||
? t.binaryExpression('+', t.stringLiteral('onUpdate'), propName)
|
||||
: t.stringLiteral(
|
||||
`onUpdate:${
|
||||
(propName as t.StringLiteral)?.value || 'modelValue'
|
||||
}`
|
||||
);
|
||||
|
||||
properties.push(
|
||||
t.objectProperty(
|
||||
updateName,
|
||||
t.arrowFunctionExpression(
|
||||
[t.identifier('$event')],
|
||||
t.assignmentExpression(
|
||||
'=',
|
||||
value as any,
|
||||
t.identifier('$event')
|
||||
)
|
||||
),
|
||||
isDynamic
|
||||
)
|
||||
);
|
||||
|
||||
if (!isDynamic) {
|
||||
dynamicPropNames.add((updateName as t.StringLiteral).value);
|
||||
} else {
|
||||
hasDynamicKeys = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (properties.length && mergeProps) {
|
||||
mergeArgs.push(t.objectExpression(dedupeProperties(properties, mergeProps)));
|
||||
properties = [];
|
||||
if (name.match(xlinkRE)) {
|
||||
name = name.replace(
|
||||
xlinkRE,
|
||||
(_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`
|
||||
);
|
||||
}
|
||||
|
||||
// JSXSpreadAttribute
|
||||
hasDynamicKeys = true;
|
||||
transformJSXSpreadAttribute(
|
||||
path as NodePath,
|
||||
prop as NodePath<t.JSXSpreadAttribute>,
|
||||
mergeProps,
|
||||
mergeProps ? mergeArgs : properties,
|
||||
properties.push(
|
||||
t.objectProperty(
|
||||
t.stringLiteral(name),
|
||||
attributeValue || t.booleanLiteral(true)
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
if (properties.length && mergeProps) {
|
||||
mergeArgs.push(
|
||||
t.objectExpression(dedupeProperties(properties, mergeProps))
|
||||
);
|
||||
properties = [];
|
||||
}
|
||||
|
||||
// JSXSpreadAttribute
|
||||
hasDynamicKeys = true;
|
||||
transformJSXSpreadAttribute(
|
||||
path as NodePath,
|
||||
prop as NodePath<t.JSXSpreadAttribute>,
|
||||
mergeProps,
|
||||
mergeProps ? mergeArgs : properties
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// patchFlag analysis
|
||||
if (hasDynamicKeys) {
|
||||
@ -260,21 +289,24 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
||||
}
|
||||
|
||||
if (
|
||||
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS)
|
||||
&& (hasRef || directives.length > 0)
|
||||
(patchFlag === 0 || patchFlag === PatchFlags.HYDRATE_EVENTS) &&
|
||||
(hasRef || directives.length > 0)
|
||||
) {
|
||||
patchFlag |= PatchFlags.NEED_PATCH;
|
||||
}
|
||||
|
||||
let propsExpression: t.Expression | t.ObjectProperty | t.Literal = t.nullLiteral();
|
||||
let propsExpression: t.Expression | t.ObjectProperty | t.Literal =
|
||||
t.nullLiteral();
|
||||
if (mergeArgs.length) {
|
||||
if (properties.length) {
|
||||
mergeArgs.push(t.objectExpression(dedupeProperties(properties, mergeProps)));
|
||||
mergeArgs.push(
|
||||
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
|
||||
@ -285,7 +317,9 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
||||
if (properties.length === 1 && t.isSpreadElement(properties[0])) {
|
||||
propsExpression = (properties[0] as unknown as t.SpreadElement).argument;
|
||||
} else {
|
||||
propsExpression = t.objectExpression(dedupeProperties(properties, mergeProps));
|
||||
propsExpression = t.objectExpression(
|
||||
dedupeProperties(properties, mergeProps)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,54 +341,56 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
|
||||
*/
|
||||
const getChildren = (
|
||||
paths: NodePath<
|
||||
t.JSXText
|
||||
| t.JSXExpressionContainer
|
||||
| t.JSXSpreadChild
|
||||
| t.JSXElement
|
||||
| t.JSXFragment
|
||||
| t.JSXText
|
||||
| t.JSXExpressionContainer
|
||||
| t.JSXSpreadChild
|
||||
| t.JSXElement
|
||||
| t.JSXFragment
|
||||
>[],
|
||||
state: State,
|
||||
): t.Expression[] => paths
|
||||
.map((path) => {
|
||||
if (path.isJSXText()) {
|
||||
const transformedText = transformJSXText(path);
|
||||
if (transformedText) {
|
||||
return t.callExpression(createIdentifier(state, 'createTextVNode'), [transformedText]);
|
||||
state: State
|
||||
): t.Expression[] =>
|
||||
paths
|
||||
.map((path) => {
|
||||
if (path.isJSXText()) {
|
||||
const transformedText = transformJSXText(path);
|
||||
if (transformedText) {
|
||||
return t.callExpression(createIdentifier(state, 'createTextVNode'), [
|
||||
transformedText,
|
||||
]);
|
||||
}
|
||||
return transformedText;
|
||||
}
|
||||
return transformedText;
|
||||
}
|
||||
if (path.isJSXExpressionContainer()) {
|
||||
const expression = transformJSXExpressionContainer(path);
|
||||
if (path.isJSXExpressionContainer()) {
|
||||
const expression = transformJSXExpressionContainer(path);
|
||||
|
||||
if (t.isIdentifier(expression)) {
|
||||
const { name } = expression;
|
||||
const { referencePaths = [] } = path.scope.getBinding(name) || {};
|
||||
referencePaths.forEach((referencePath) => {
|
||||
walksScope(referencePath, name, SlotFlags.DYNAMIC);
|
||||
});
|
||||
if (t.isIdentifier(expression)) {
|
||||
const { name } = expression;
|
||||
const { referencePaths = [] } = path.scope.getBinding(name) || {};
|
||||
referencePaths.forEach((referencePath) => {
|
||||
walksScope(referencePath, name, SlotFlags.DYNAMIC);
|
||||
});
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
return expression;
|
||||
}
|
||||
if (t.isJSXSpreadChild(path)) {
|
||||
return transformJSXSpreadChild(path as NodePath<t.JSXSpreadChild>);
|
||||
}
|
||||
if (path.isCallExpression()) {
|
||||
return (path as NodePath<t.CallExpression>).node;
|
||||
}
|
||||
if (path.isJSXElement()) {
|
||||
return transformJSXElement(path, state);
|
||||
}
|
||||
throw new Error(`getChildren: ${path.type} is not supported`);
|
||||
}).filter(((value: any) => (
|
||||
value !== undefined
|
||||
&& value !== null
|
||||
&& !t.isJSXEmptyExpression(value)
|
||||
)) as any);
|
||||
if (path.isJSXSpreadChild()) {
|
||||
return transformJSXSpreadChild(path);
|
||||
}
|
||||
if (path.isCallExpression()) {
|
||||
return (path as NodePath<t.CallExpression>).node;
|
||||
}
|
||||
if (path.isJSXElement()) {
|
||||
return transformJSXElement(path, state);
|
||||
}
|
||||
throw new Error(`getChildren: ${path.type} is not supported`);
|
||||
})
|
||||
.filter(
|
||||
((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 {
|
||||
@ -380,21 +416,25 @@ const transformJSXElement = (
|
||||
*/
|
||||
VNodeChild = isComponent
|
||||
? children.length
|
||||
? t.objectExpression([
|
||||
!!children.length && t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, children))),
|
||||
),
|
||||
...(slots ? (
|
||||
t.isObjectExpression(slots)
|
||||
? (slots! as t.ObjectExpression).properties
|
||||
: [t.spreadElement(slots!)]
|
||||
) : []),
|
||||
optimize && t.objectProperty(
|
||||
t.identifier('_'),
|
||||
t.numericLiteral(slotFlag),
|
||||
),
|
||||
].filter(Boolean as any))
|
||||
? t.objectExpression(
|
||||
[
|
||||
!!children.length &&
|
||||
t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression(
|
||||
[],
|
||||
t.arrayExpression(buildIIFE(path, children))
|
||||
)
|
||||
),
|
||||
...(slots
|
||||
? t.isObjectExpression(slots)
|
||||
? (slots! as t.ObjectExpression).properties
|
||||
: [t.spreadElement(slots!)]
|
||||
: []),
|
||||
optimize &&
|
||||
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
|
||||
].filter(Boolean as any)
|
||||
)
|
||||
: slots
|
||||
: t.arrayExpression(children);
|
||||
} else if (children.length === 1) {
|
||||
@ -403,25 +443,35 @@ const transformJSXElement = (
|
||||
*/
|
||||
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]))),
|
||||
),
|
||||
optimize && t.objectProperty(
|
||||
t.identifier('_'),
|
||||
t.numericLiteral(slotFlag),
|
||||
) as any,
|
||||
].filter(Boolean));
|
||||
const objectExpression = t.objectExpression(
|
||||
[
|
||||
t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression(
|
||||
[],
|
||||
t.arrayExpression(buildIIFE(path, [child]))
|
||||
)
|
||||
),
|
||||
optimize &&
|
||||
(t.objectProperty(
|
||||
t.identifier('_'),
|
||||
t.numericLiteral(slotFlag)
|
||||
) as any),
|
||||
].filter(Boolean)
|
||||
);
|
||||
if (t.isIdentifier(child) && isComponent) {
|
||||
VNodeChild = enableObjectSlots ? t.conditionalExpression(
|
||||
t.callExpression(state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(), [child]),
|
||||
child,
|
||||
objectExpression,
|
||||
) : objectExpression;
|
||||
} else if (
|
||||
t.isCallExpression(child) && child.loc && isComponent
|
||||
) { // the element was generated and doesn't have location information
|
||||
VNodeChild = enableObjectSlots
|
||||
? t.conditionalExpression(
|
||||
t.callExpression(
|
||||
state.get('@vue/babel-plugin-jsx/runtimeIsSlot')(),
|
||||
[child]
|
||||
),
|
||||
child,
|
||||
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');
|
||||
@ -431,63 +481,72 @@ const transformJSXElement = (
|
||||
kind: 'let',
|
||||
});
|
||||
}
|
||||
const alternate = t.objectExpression([
|
||||
t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression([], t.arrayExpression(buildIIFE(path, [slotId]))),
|
||||
), optimize && t.objectProperty(
|
||||
t.identifier('_'),
|
||||
t.numericLiteral(slotFlag),
|
||||
) as any,
|
||||
].filter(Boolean));
|
||||
const alternate = t.objectExpression(
|
||||
[
|
||||
t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression(
|
||||
[],
|
||||
t.arrayExpression(buildIIFE(path, [slotId]))
|
||||
)
|
||||
),
|
||||
optimize &&
|
||||
(t.objectProperty(
|
||||
t.identifier('_'),
|
||||
t.numericLiteral(slotFlag)
|
||||
) as any),
|
||||
].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;
|
||||
}
|
||||
} else if (t.isFunctionExpression(child) || t.isArrowFunctionExpression(child)) {
|
||||
} else if (
|
||||
t.isFunctionExpression(child) ||
|
||||
t.isArrowFunctionExpression(child)
|
||||
) {
|
||||
VNodeChild = t.objectExpression([
|
||||
t.objectProperty(
|
||||
t.identifier('default'),
|
||||
child,
|
||||
),
|
||||
t.objectProperty(t.identifier('default'), child),
|
||||
]);
|
||||
} else if (t.isObjectExpression(child)) {
|
||||
VNodeChild = t.objectExpression([
|
||||
...child.properties,
|
||||
optimize && t.objectProperty(
|
||||
t.identifier('_'),
|
||||
t.numericLiteral(slotFlag),
|
||||
),
|
||||
].filter(Boolean as any));
|
||||
VNodeChild = t.objectExpression(
|
||||
[
|
||||
...child.properties,
|
||||
optimize &&
|
||||
t.objectProperty(t.identifier('_'), t.numericLiteral(slotFlag)),
|
||||
].filter(Boolean as any)
|
||||
);
|
||||
} else {
|
||||
VNodeChild = isComponent ? t.objectExpression([
|
||||
t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression([], t.arrayExpression([child])),
|
||||
),
|
||||
]) : t.arrayExpression([child]);
|
||||
VNodeChild = isComponent
|
||||
? t.objectExpression([
|
||||
t.objectProperty(
|
||||
t.identifier('default'),
|
||||
t.arrowFunctionExpression([], t.arrayExpression([child]))
|
||||
),
|
||||
])
|
||||
: t.arrayExpression([child]);
|
||||
}
|
||||
}
|
||||
|
||||
const createVNode = t.callExpression(createIdentifier(state, 'createVNode'), [
|
||||
tag,
|
||||
props,
|
||||
VNodeChild || t.nullLiteral(),
|
||||
!!patchFlag && optimize && t.numericLiteral(patchFlag),
|
||||
!!dynamicPropNames.size && optimize
|
||||
&& t.arrayExpression(
|
||||
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name)),
|
||||
),
|
||||
].filter(Boolean as unknown as ExcludesBoolean));
|
||||
const createVNode = t.callExpression(
|
||||
createIdentifier(state, 'createVNode'),
|
||||
[
|
||||
tag,
|
||||
props,
|
||||
VNodeChild || t.nullLiteral(),
|
||||
!!patchFlag && optimize && t.numericLiteral(patchFlag),
|
||||
!!dynamicPropNames.size &&
|
||||
optimize &&
|
||||
t.arrayExpression(
|
||||
[...dynamicPropNames.keys()].map((name) => t.stringLiteral(name))
|
||||
),
|
||||
].filter(Boolean as unknown as ExcludesBoolean)
|
||||
);
|
||||
|
||||
if (!directives.length) {
|
||||
return createVNode;
|
||||
@ -499,10 +558,10 @@ const transformJSXElement = (
|
||||
]);
|
||||
};
|
||||
|
||||
export default ({
|
||||
export default {
|
||||
JSXElement: {
|
||||
exit(path: NodePath<t.JSXElement>, state: State) {
|
||||
path.replaceWith(transformJSXElement(path, state));
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as t from '@babel/types';
|
||||
import htmlTags from 'html-tags';
|
||||
import svgTags from 'svg-tags';
|
||||
import { NodePath } from '@babel/traverse';
|
||||
import { type NodePath } from '@babel/traverse';
|
||||
import type { State } from './interface';
|
||||
import SlotFlags from './slotFlags';
|
||||
|
||||
@ -17,15 +17,17 @@ export const KEEP_ALIVE = 'KeepAlive';
|
||||
* @returns MemberExpression
|
||||
*/
|
||||
export const createIdentifier = (
|
||||
state: State, name: string,
|
||||
state: State,
|
||||
name: string
|
||||
): t.Identifier | t.MemberExpression => state.get(name)();
|
||||
|
||||
/**
|
||||
* Checks if string is describing a directive
|
||||
* @param src string
|
||||
*/
|
||||
export const isDirective = (src: string): boolean => src.startsWith('v-')
|
||||
|| (src.startsWith('v') && src.length >= 2 && src[1] >= 'A' && src[1] <= 'Z');
|
||||
export const isDirective = (src: string): boolean =>
|
||||
src.startsWith('v-') ||
|
||||
(src.startsWith('v') && src.length >= 2 && src[1] >= 'A' && src[1] <= 'Z');
|
||||
|
||||
/**
|
||||
* Should transformed to slots
|
||||
@ -33,7 +35,8 @@ export const isDirective = (src: string): boolean => src.startsWith('v-')
|
||||
* @returns boolean
|
||||
*/
|
||||
// if _Fragment is already imported, it will end with number
|
||||
export const shouldTransformedToSlots = (tag: string) => !(tag.match(RegExp(`^_?${FRAGMENT}\\d*$`)) || tag === KEEP_ALIVE);
|
||||
export const shouldTransformedToSlots = (tag: string) =>
|
||||
!(tag.match(RegExp(`^_?${FRAGMENT}\\d*$`)) || tag === KEEP_ALIVE);
|
||||
|
||||
/**
|
||||
* Check if a Node is a component
|
||||
@ -42,7 +45,10 @@ export const shouldTransformedToSlots = (tag: string) => !(tag.match(RegExp(`^_?
|
||||
* @param path JSXOpeningElement
|
||||
* @returns boolean
|
||||
*/
|
||||
export const checkIsComponent = (path: NodePath<t.JSXOpeningElement>, state: State): boolean => {
|
||||
export const checkIsComponent = (
|
||||
path: NodePath<t.JSXOpeningElement>,
|
||||
state: State
|
||||
): boolean => {
|
||||
const namePath = path.get('name');
|
||||
|
||||
if (namePath.isJSXMemberExpression()) {
|
||||
@ -51,7 +57,12 @@ export const checkIsComponent = (path: NodePath<t.JSXOpeningElement>, state: Sta
|
||||
|
||||
const tag = (namePath as NodePath<t.JSXIdentifier>).node.name;
|
||||
|
||||
return !state.opts.isCustomElement?.(tag) && shouldTransformedToSlots(tag) && !htmlTags.includes(tag as htmlTags.htmlTags) && !svgTags.includes(tag);
|
||||
return (
|
||||
!state.opts.isCustomElement?.(tag) &&
|
||||
shouldTransformedToSlots(tag) &&
|
||||
!htmlTags.includes(tag as htmlTags.htmlTags) &&
|
||||
!svgTags.includes(tag)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -60,15 +71,17 @@ export const checkIsComponent = (path: NodePath<t.JSXOpeningElement>, state: Sta
|
||||
* @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 transformedObject = t.isJSXMemberExpression(objectPath)
|
||||
? transformJSXMemberExpression(path.get('object') as NodePath<t.JSXMemberExpression>)
|
||||
? transformJSXMemberExpression(
|
||||
path.get('object') as NodePath<t.JSXMemberExpression>
|
||||
)
|
||||
: t.isJSXIdentifier(objectPath)
|
||||
? t.identifier(objectPath.name)
|
||||
: t.nullLiteral();
|
||||
? t.identifier(objectPath.name)
|
||||
: t.nullLiteral();
|
||||
const transformedProperty = t.identifier(propertyPath.name);
|
||||
return t.memberExpression(transformedObject, transformedProperty);
|
||||
};
|
||||
@ -81,19 +94,24 @@ 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');
|
||||
if (namePath.isJSXIdentifier()) {
|
||||
const { name } = namePath.node;
|
||||
if (!htmlTags.includes(name as htmlTags.htmlTags) && !svgTags.includes(name)) {
|
||||
return (name === FRAGMENT
|
||||
if (
|
||||
!htmlTags.includes(name as htmlTags.htmlTags) &&
|
||||
!svgTags.includes(name)
|
||||
) {
|
||||
return name === FRAGMENT
|
||||
? createIdentifier(state, FRAGMENT)
|
||||
: path.scope.hasBinding(name)
|
||||
? t.identifier(name)
|
||||
: state.opts.isCustomElement?.(name)
|
||||
? t.stringLiteral(name)
|
||||
: t.callExpression(createIdentifier(state, 'resolveComponent'), [t.stringLiteral(name)]));
|
||||
? t.identifier(name)
|
||||
: state.opts.isCustomElement?.(name)
|
||||
? t.stringLiteral(name)
|
||||
: t.callExpression(createIdentifier(state, 'resolveComponent'), [
|
||||
t.stringLiteral(name),
|
||||
]);
|
||||
}
|
||||
|
||||
return t.stringLiteral(name);
|
||||
@ -119,7 +137,9 @@ export const getJSXAttributeName = (path: NodePath<t.JSXAttribute>): string => {
|
||||
* @param path JSXText
|
||||
* @returns StringLiteral | null
|
||||
*/
|
||||
export const transformJSXText = (path: NodePath<t.JSXText>): t.StringLiteral | null => {
|
||||
export const transformJSXText = (
|
||||
path: NodePath<t.JSXText>
|
||||
): t.StringLiteral | null => {
|
||||
const { node } = path;
|
||||
const lines = node.value.split(/\r\n|\n|\r/);
|
||||
|
||||
@ -171,10 +191,8 @@ export const transformJSXText = (path: NodePath<t.JSXText>): t.StringLiteral | n
|
||||
* @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
|
||||
@ -182,10 +200,14 @@ export const transformJSXExpressionContainer = (
|
||||
* @returns SpreadElement
|
||||
*/
|
||||
export const transformJSXSpreadChild = (
|
||||
path: NodePath<t.JSXSpreadChild>,
|
||||
path: NodePath<t.JSXSpreadChild>
|
||||
): t.SpreadElement => t.spreadElement(path.get('expression').node);
|
||||
|
||||
export const walksScope = (path: NodePath, name: string, slotFlag: SlotFlags): void => {
|
||||
export const walksScope = (
|
||||
path: NodePath,
|
||||
name: string,
|
||||
slotFlag: SlotFlags
|
||||
): void => {
|
||||
if (path.scope.hasBinding(name) && path.parentPath) {
|
||||
if (t.isJSXElement(path.parentPath.node)) {
|
||||
path.parentPath.setData('slotFlag', slotFlag);
|
||||
@ -194,9 +216,12 @@ export const walksScope = (path: NodePath, name: string, slotFlag: SlotFlags): v
|
||||
}
|
||||
};
|
||||
|
||||
export const buildIIFE = (path: NodePath<t.JSXElement>, children: t.Expression[]) => {
|
||||
export const buildIIFE = (
|
||||
path: NodePath<t.JSXElement>,
|
||||
children: t.Expression[]
|
||||
) => {
|
||||
const { parentPath } = path;
|
||||
if (t.isAssignmentExpression(parentPath)) {
|
||||
if (parentPath.isAssignmentExpression()) {
|
||||
const { left } = parentPath.node as t.AssignmentExpression;
|
||||
if (t.isIdentifier(left)) {
|
||||
return children.map((child) => {
|
||||
@ -207,11 +232,15 @@ export const buildIIFE = (path: NodePath<t.JSXElement>, children: t.Expression[]
|
||||
t.variableDeclarator(
|
||||
insertName,
|
||||
t.callExpression(
|
||||
t.functionExpression(null, [], t.blockStatement([t.returnStatement(child)])),
|
||||
[],
|
||||
),
|
||||
t.functionExpression(
|
||||
null,
|
||||
[],
|
||||
t.blockStatement([t.returnStatement(child)])
|
||||
),
|
||||
[]
|
||||
)
|
||||
),
|
||||
]),
|
||||
])
|
||||
);
|
||||
return insertName;
|
||||
}
|
||||
@ -226,7 +255,10 @@ const onRE = /^on[^a-z]/;
|
||||
|
||||
export const isOn = (key: string) => onRE.test(key);
|
||||
|
||||
const mergeAsArray = (existing: t.ObjectProperty, incoming: t.ObjectProperty) => {
|
||||
const mergeAsArray = (
|
||||
existing: t.ObjectProperty,
|
||||
incoming: t.ObjectProperty
|
||||
) => {
|
||||
if (t.isArrayExpression(existing.value)) {
|
||||
existing.value.elements.push(incoming.value as t.Expression);
|
||||
} else {
|
||||
@ -237,7 +269,10 @@ const mergeAsArray = (existing: t.ObjectProperty, incoming: t.ObjectProperty) =>
|
||||
}
|
||||
};
|
||||
|
||||
export const dedupeProperties = (properties: t.ObjectProperty[] = [], mergeProps?: boolean) => {
|
||||
export const dedupeProperties = (
|
||||
properties: t.ObjectProperty[] = [],
|
||||
mergeProps?: boolean
|
||||
) => {
|
||||
if (!mergeProps) {
|
||||
return properties;
|
||||
}
|
||||
@ -270,7 +305,7 @@ export const dedupeProperties = (properties: t.ObjectProperty[] = [], mergeProps
|
||||
* @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';
|
||||
@ -280,7 +315,9 @@ export const isConstant = (
|
||||
return elements.every((element) => element && isConstant(element));
|
||||
}
|
||||
if (t.isObjectExpression(node)) {
|
||||
return node.properties.every((property) => isConstant((property as any).value));
|
||||
return node.properties.every((property) =>
|
||||
isConstant((property as any).value)
|
||||
);
|
||||
}
|
||||
if (t.isLiteral(node)) {
|
||||
return true;
|
||||
@ -292,13 +329,21 @@ 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;
|
||||
const argument = path.get('argument') as NodePath<
|
||||
t.ObjectExpression | t.Identifier
|
||||
>;
|
||||
const properties = t.isObjectExpression(argument.node)
|
||||
? argument.node.properties
|
||||
: undefined;
|
||||
if (!properties) {
|
||||
if (argument.isIdentifier()) {
|
||||
walksScope(nodePath, (argument.node as t.Identifier).name, SlotFlags.DYNAMIC);
|
||||
walksScope(
|
||||
nodePath,
|
||||
(argument.node as t.Identifier).name,
|
||||
SlotFlags.DYNAMIC
|
||||
);
|
||||
}
|
||||
args.push(mergeProps ? argument.node : t.spreadElement(argument.node));
|
||||
} else if (mergeProps) {
|
||||
|
@ -1,13 +1,13 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`_Fragment already imported: _Fragment already imported 1`] = `
|
||||
exports[`_Fragment already imported > _Fragment already imported 1`] = `
|
||||
"import { createVNode as _createVNode, createTextVNode as _createTextVNode, Fragment as _Fragment2 } from \\"vue\\";
|
||||
import { Fragment as _Fragment } from 'vue';
|
||||
const Root1 = () => _createVNode(_Fragment2, null, [_createTextVNode(\\"root1\\")]);
|
||||
const Root2 = () => _createVNode(_Fragment, null, [_createTextVNode(\\"root2\\")]);"
|
||||
`;
|
||||
|
||||
exports[`MereProps Order: MereProps Order 1`] = `
|
||||
exports[`MereProps Order > MereProps Order 1`] = `
|
||||
"import { createVNode as _createVNode, mergeProps as _mergeProps, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
_createVNode(\\"button\\", _mergeProps({
|
||||
\\"loading\\": true
|
||||
@ -16,7 +16,7 @@ _createVNode(\\"button\\", _mergeProps({
|
||||
}), [_createTextVNode(\\"btn\\")], 16, [\\"loading\\"]);"
|
||||
`;
|
||||
|
||||
exports[`Merge class/ style attributes into array: Merge class/ style attributes into array 1`] = `
|
||||
exports[`Merge class/ style attributes into array > Merge class/ style attributes into array 1`] = `
|
||||
"import { createVNode as _createVNode } from \\"vue\\";
|
||||
_createVNode(\\"div\\", {
|
||||
\\"class\\": [\\"a\\", b],
|
||||
@ -24,22 +24,22 @@ _createVNode(\\"div\\", {
|
||||
}, null, 6);"
|
||||
`;
|
||||
|
||||
exports[`Without JSX should work: Without JSX should work 1`] = `
|
||||
exports[`Without JSX should work > Without JSX should work 1`] = `
|
||||
"import { createVNode } from 'vue';
|
||||
createVNode('div', null, ['Without JSX should work']);"
|
||||
`;
|
||||
|
||||
exports[`Without props: Without props 1`] = `
|
||||
exports[`Without props > Without props 1`] = `
|
||||
"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
_createVNode(\\"a\\", null, [_createTextVNode(\\"a\\")]);"
|
||||
`;
|
||||
|
||||
exports[`custom directive: custom directive 1`] = `
|
||||
exports[`custom directive > custom directive 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, resolveDirective as _resolveDirective, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_withDirectives(_createVNode(_resolveComponent(\\"A\\"), null, null, 512), [[_resolveDirective(\\"cus\\"), x]]);"
|
||||
`;
|
||||
|
||||
exports[`custom directive: custom directive 2`] = `
|
||||
exports[`custom directive > custom directive 2`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, resolveDirective as _resolveDirective, resolveComponent as _resolveComponent, Fragment as _Fragment } from \\"vue\\";
|
||||
_createVNode(_Fragment, null, [_withDirectives(_createVNode(_resolveComponent(\\"A\\"), null, null, 512), [[_resolveDirective(\\"xxx\\"), x]]), _withDirectives(_createVNode(_resolveComponent(\\"A\\"), null, null, 512), [[_resolveDirective(\\"xxx\\"), x]]), _withDirectives(_createVNode(_resolveComponent(\\"A\\"), null, null, 512), [[_resolveDirective(\\"xxx\\"), x, 'y']]), _withDirectives(_createVNode(_resolveComponent(\\"A\\"), null, null, 512), [[_resolveDirective(\\"xxx\\"), x, 'y', {
|
||||
a: true,
|
||||
@ -56,7 +56,7 @@ _createVNode(_Fragment, null, [_withDirectives(_createVNode(_resolveComponent(\\
|
||||
}]])]);"
|
||||
`;
|
||||
|
||||
exports[`disable object slot syntax with defaultSlot: defaultSlot 1`] = `
|
||||
exports[`disable object slot syntax with defaultSlot > defaultSlot 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_createVNode(_resolveComponent(\\"Badge\\"), null, {
|
||||
default: () => [slots.default()],
|
||||
@ -64,7 +64,7 @@ _createVNode(_resolveComponent(\\"Badge\\"), null, {
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`dynamic type in input: dynamic type in input 1`] = `
|
||||
exports[`dynamic type in input > dynamic type in input 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelDynamic as _vModelDynamic } from \\"vue\\";
|
||||
_withDirectives(_createVNode(\\"input\\", {
|
||||
\\"type\\": type,
|
||||
@ -72,7 +72,7 @@ _withDirectives(_createVNode(\\"input\\", {
|
||||
}, null, 8, [\\"type\\", \\"onUpdate:modelValue\\"]), [[_vModelDynamic, test]]);"
|
||||
`;
|
||||
|
||||
exports[`input[type="checkbox"]: input[type="checkbox"] 1`] = `
|
||||
exports[`input[type="checkbox"] > input[type="checkbox"] 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelCheckbox as _vModelCheckbox } from \\"vue\\";
|
||||
_withDirectives(_createVNode(\\"input\\", {
|
||||
\\"type\\": \\"checkbox\\",
|
||||
@ -80,7 +80,7 @@ _withDirectives(_createVNode(\\"input\\", {
|
||||
}, null, 8, [\\"onUpdate:modelValue\\"]), [[_vModelCheckbox, test]]);"
|
||||
`;
|
||||
|
||||
exports[`input[type="radio"]: input[type="radio"] 1`] = `
|
||||
exports[`input[type="radio"] > input[type="radio"] 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelRadio as _vModelRadio, Fragment as _Fragment } from \\"vue\\";
|
||||
_createVNode(_Fragment, null, [_withDirectives(_createVNode(\\"input\\", {
|
||||
\\"type\\": \\"radio\\",
|
||||
@ -95,7 +95,7 @@ _createVNode(_Fragment, null, [_withDirectives(_createVNode(\\"input\\", {
|
||||
}, null, 8, [\\"onUpdate:modelValue\\"]), [[_vModelRadio, test]])]);"
|
||||
`;
|
||||
|
||||
exports[`input[type="text"] .lazy modifier: input[type="text"] .lazy modifier 1`] = `
|
||||
exports[`input[type="text"] .lazy modifier > input[type="text"] .lazy modifier 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelText as _vModelText } from \\"vue\\";
|
||||
_withDirectives(_createVNode(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => test = $event
|
||||
@ -104,31 +104,31 @@ _withDirectives(_createVNode(\\"input\\", {
|
||||
}]]);"
|
||||
`;
|
||||
|
||||
exports[`input[type="text"]: input[type="text"] 1`] = `
|
||||
exports[`input[type="text"] > input[type="text"] 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelText as _vModelText } from \\"vue\\";
|
||||
_withDirectives(_createVNode(\\"input\\", {
|
||||
\\"onUpdate:modelValue\\": $event => test = $event
|
||||
}, null, 8, [\\"onUpdate:modelValue\\"]), [[_vModelText, test]]);"
|
||||
`;
|
||||
|
||||
exports[`isCustomElement: isCustomElement 1`] = `
|
||||
exports[`isCustomElement > isCustomElement 1`] = `
|
||||
"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
_createVNode(\\"foo\\", null, [_createVNode(\\"span\\", null, [_createTextVNode(\\"foo\\")])]);"
|
||||
`;
|
||||
|
||||
exports[`named import specifier \`Keep Alive\`: named import specifier \`Keep Alive\` 1`] = `
|
||||
exports[`named import specifier \`Keep Alive\` > named import specifier \`Keep Alive\` 1`] = `
|
||||
"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
import { KeepAlive } from 'vue';
|
||||
_createVNode(KeepAlive, null, [_createTextVNode(\\"123\\")]);"
|
||||
`;
|
||||
|
||||
exports[`namespace specifier \`Keep Alive\`: namespace specifier \`Keep Alive\` 1`] = `
|
||||
exports[`namespace specifier \`Keep Alive\` > namespace specifier \`Keep Alive\` 1`] = `
|
||||
"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
import * as Vue from 'vue';
|
||||
_createVNode(Vue.KeepAlive, null, [_createTextVNode(\\"123\\")]);"
|
||||
`;
|
||||
|
||||
exports[`override props multiple: multiple 1`] = `
|
||||
exports[`override props multiple > multiple 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_createVNode(_resolveComponent(\\"A\\"), {
|
||||
\\"loading\\": true,
|
||||
@ -142,12 +142,12 @@ _createVNode(_resolveComponent(\\"A\\"), {
|
||||
}, null);"
|
||||
`;
|
||||
|
||||
exports[`override props single: single 1`] = `
|
||||
exports[`override props single > single 1`] = `
|
||||
"import { createVNode as _createVNode } from \\"vue\\";
|
||||
_createVNode(\\"div\\", a, null);"
|
||||
`;
|
||||
|
||||
exports[`passing object slots via JSX children multiple expressions: multiple expressions 1`] = `
|
||||
exports[`passing object slots via JSX children multiple expressions > multiple expressions 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_createVNode(_resolveComponent(\\"A\\"), null, {
|
||||
default: () => [foo, bar],
|
||||
@ -155,14 +155,14 @@ _createVNode(_resolveComponent(\\"A\\"), null, {
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`passing object slots via JSX children single expression, function expression: single expression, function expression 1`] = `
|
||||
exports[`passing object slots via JSX children single expression, function expression > single expression, function expression 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_createVNode(_resolveComponent(\\"A\\"), null, {
|
||||
default: () => \\"foo\\"
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`passing object slots via JSX children single expression, non-literal value: runtime check: single expression, non-literal value: runtime check 1`] = `
|
||||
exports[`passing object slots via JSX children single expression, non-literal value: runtime check > single expression, non-literal value: runtime check 1`] = `
|
||||
"let _slot;
|
||||
import { createVNode as _createVNode, isVNode as _isVNode, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
function _isSlot(s) {
|
||||
@ -175,7 +175,7 @@ _createVNode(_resolveComponent(\\"A\\"), null, _isSlot(_slot = foo()) ? _slot :
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`reassign variable as component: reassign variable as component 1`] = `
|
||||
exports[`reassign variable as component > reassign variable as component 1`] = `
|
||||
"import { isVNode as _isVNode, createVNode as _createVNode } from \\"vue\\";
|
||||
import { defineComponent } from 'vue';
|
||||
function _isSlot(s) {
|
||||
@ -200,7 +200,7 @@ a = _createVNode(A, null, _isSlot(a) ? a : {
|
||||
});"
|
||||
`;
|
||||
|
||||
exports[`select: select 1`] = `
|
||||
exports[`select > select 1`] = `
|
||||
"import { withDirectives as _withDirectives, vModelSelect as _vModelSelect, createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
_withDirectives(_createVNode(\\"select\\", {
|
||||
\\"onUpdate:modelValue\\": $event => test = $event
|
||||
@ -213,37 +213,37 @@ _withDirectives(_createVNode(\\"select\\", {
|
||||
}, [_createTextVNode(\\"c\\")])], 8, [\\"onUpdate:modelValue\\"]), [[_vModelSelect, test]]);"
|
||||
`;
|
||||
|
||||
exports[`set pragma to custom: custom 1`] = `
|
||||
exports[`set pragma to custom > custom 1`] = `
|
||||
"import { createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
custom(\\"div\\", null, [_createTextVNode(\\"pragma\\")]);"
|
||||
`;
|
||||
|
||||
exports[`should keep \`import * as Vue from "vue"\`: should keep \`import * as Vue from "vue"\` 1`] = `
|
||||
exports[`should keep \`import * as Vue from "vue"\` > should keep \`import * as Vue from "vue"\` 1`] = `
|
||||
"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
import * as Vue from 'vue';
|
||||
_createVNode(\\"div\\", null, [_createTextVNode(\\"Vue\\")]);"
|
||||
`;
|
||||
|
||||
exports[`single no need for a mergeProps call: single no need for a mergeProps call 1`] = `
|
||||
exports[`single no need for a mergeProps call > single no need for a mergeProps call 1`] = `
|
||||
"import { createVNode as _createVNode, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
_createVNode(\\"div\\", x, [_createTextVNode(\\"single\\")], 16);"
|
||||
`;
|
||||
|
||||
exports[`specifiers should be merged into a single importDeclaration: specifiers should be merged into a single importDeclaration 1`] = `
|
||||
exports[`specifiers should be merged into a single importDeclaration > specifiers should be merged into a single importDeclaration 1`] = `
|
||||
"import { createVNode as _createVNode } from \\"vue\\";
|
||||
import { createVNode, Fragment as _Fragment } from 'vue';
|
||||
import { vShow } from 'vue';
|
||||
_createVNode(_Fragment, null, null);"
|
||||
`;
|
||||
|
||||
exports[`textarea: textarea 1`] = `
|
||||
exports[`textarea > textarea 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, vModelText as _vModelText } from \\"vue\\";
|
||||
_withDirectives(_createVNode(\\"textarea\\", {
|
||||
\\"onUpdate:modelValue\\": $event => test = $event
|
||||
}, null, 8, [\\"onUpdate:modelValue\\"]), [[_vModelText, test]]);"
|
||||
`;
|
||||
|
||||
exports[`use "@jsx" comment specify pragma: use "@jsx" comment specify pragma 1`] = `
|
||||
exports[`use "@jsx" comment specify pragma > use "@jsx" comment specify pragma 1`] = `
|
||||
"import { createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
/* @jsx custom */
|
||||
custom(\\"div\\", {
|
||||
@ -251,7 +251,7 @@ custom(\\"div\\", {
|
||||
}, [_createTextVNode(\\"Hello\\")]);"
|
||||
`;
|
||||
|
||||
exports[`use "model" as the prop name: use "model" as the prop name 1`] = `
|
||||
exports[`use "model" as the prop name > use "model" as the prop name 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_createVNode(_resolveComponent(\\"C\\"), {
|
||||
\\"model\\": foo,
|
||||
@ -259,12 +259,12 @@ _createVNode(_resolveComponent(\\"C\\"), {
|
||||
}, null, 8, [\\"model\\", \\"onUpdate:model\\"]);"
|
||||
`;
|
||||
|
||||
exports[`using v-slots without children should not be spread: using v-slots without children should not be spread 1`] = `
|
||||
exports[`using v-slots without children should not be spread > using v-slots without children should not be spread 1`] = `
|
||||
"import { createVNode as _createVNode, resolveDirective as _resolveDirective, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_createVNode(_resolveComponent(\\"A\\"), null, slots);"
|
||||
`;
|
||||
|
||||
exports[`v-model target value support variable: v-model target value support variable 1`] = `
|
||||
exports[`v-model target value support variable > v-model target value support variable 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent, Fragment as _Fragment } from \\"vue\\";
|
||||
const foo = 'foo';
|
||||
const a = () => 'a';
|
||||
@ -307,19 +307,19 @@ _createVNode(_Fragment, null, [_createVNode(_resolveComponent(\\"A\\"), {
|
||||
}, null, 16)]);"
|
||||
`;
|
||||
|
||||
exports[`v-show: v-show 1`] = `
|
||||
exports[`v-show > v-show 1`] = `
|
||||
"import { withDirectives as _withDirectives, createVNode as _createVNode, vShow as _vShow, createTextVNode as _createTextVNode } from \\"vue\\";
|
||||
_withDirectives(_createVNode(\\"div\\", null, [_createTextVNode(\\"vShow\\")], 512), [[_vShow, x]]);"
|
||||
`;
|
||||
|
||||
exports[`vHtml: vHtml 1`] = `
|
||||
exports[`vHtml > vHtml 1`] = `
|
||||
"import { createVNode as _createVNode } from \\"vue\\";
|
||||
_createVNode(\\"h1\\", {
|
||||
\\"innerHTML\\": \\"<div>foo</div>\\"
|
||||
}, null, 8, [\\"innerHTML\\"]);"
|
||||
`;
|
||||
|
||||
exports[`vModels: vModels 1`] = `
|
||||
exports[`vModels > vModels 1`] = `
|
||||
"import { createVNode as _createVNode, resolveComponent as _resolveComponent } from \\"vue\\";
|
||||
_createVNode(_resolveComponent(\\"C\\"), {
|
||||
\\"modelValue\\": foo,
|
||||
@ -336,7 +336,7 @@ _createVNode(_resolveComponent(\\"C\\"), {
|
||||
}, null, 8, [\\"modelValue\\", \\"onUpdate:modelValue\\", \\"bar\\", \\"onUpdate:bar\\"]);"
|
||||
`;
|
||||
|
||||
exports[`vText: vText 1`] = `
|
||||
exports[`vText > vText 1`] = `
|
||||
"import { createVNode as _createVNode } from \\"vue\\";
|
||||
_createVNode(\\"div\\", {
|
||||
\\"textContent\\": text
|
||||
|
@ -1,17 +1,17 @@
|
||||
import {
|
||||
type CSSProperties,
|
||||
type ComponentPublicInstance,
|
||||
Transition,
|
||||
defineComponent,
|
||||
reactive,
|
||||
ref,
|
||||
defineComponent,
|
||||
CSSProperties,
|
||||
ComponentPublicInstance,
|
||||
Transition,
|
||||
} from 'vue';
|
||||
import { shallowMount, mount, VueWrapper } from '@vue/test-utils';
|
||||
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;
|
||||
|
||||
@ -94,7 +94,7 @@ describe('Transform JSX', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toBe('<div>123</div><div>456</div>');
|
||||
expect(wrapper.html()).toBe('<div>123</div>\n<div>456</div>');
|
||||
});
|
||||
|
||||
test('nested component', () => {
|
||||
@ -125,7 +125,7 @@ describe('Transform JSX', () => {
|
||||
test('Merge class', () => {
|
||||
const wrapper = shallowMount({
|
||||
setup() {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
return () => <div class="a" {...{ class: 'b' }} />;
|
||||
},
|
||||
});
|
||||
@ -152,7 +152,7 @@ describe('Transform JSX', () => {
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toBe(
|
||||
'<div style="color: blue; width: 300px; height: 300px;"></div>',
|
||||
'<div style="color: blue; width: 300px; height: 300px;"></div>'
|
||||
);
|
||||
});
|
||||
|
||||
@ -234,8 +234,8 @@ describe('Transform JSX', () => {
|
||||
const wrapper = shallowMount({
|
||||
setup() {
|
||||
return () => (
|
||||
<a
|
||||
href="huhu"
|
||||
<button
|
||||
type="button"
|
||||
{...data}
|
||||
class={{ c: true }}
|
||||
onClick={() => calls.push(4)}
|
||||
@ -246,7 +246,7 @@ describe('Transform JSX', () => {
|
||||
});
|
||||
|
||||
expect(wrapper.attributes('id')).toBe('hehe');
|
||||
expect(wrapper.attributes('href')).toBe('huhu');
|
||||
expect(wrapper.attributes('type')).toBe('button');
|
||||
expect(wrapper.text()).toBe('2');
|
||||
expect(wrapper.classes()).toEqual(expect.arrayContaining(['a', 'b', 'c']));
|
||||
|
||||
@ -261,10 +261,10 @@ describe('directive', () => {
|
||||
const wrapper = shallowMount({
|
||||
setup() {
|
||||
const html = '<div>foo</div>';
|
||||
return () => <h1 v-html={ html }></h1>;
|
||||
return () => <h1 v-html={html}></h1>;
|
||||
},
|
||||
});
|
||||
expect(wrapper.html()).toBe('<h1><div>foo</div></h1>');
|
||||
expect(wrapper.html()).toBe('<h1>\n <div>foo</div>\n</h1>');
|
||||
});
|
||||
|
||||
test('vText', () => {
|
||||
@ -420,7 +420,7 @@ describe('variables outside slots', () => {
|
||||
</A>
|
||||
);
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
expect(wrapper.get('#textarea').element.innerHTML).toBe('0');
|
||||
@ -582,8 +582,15 @@ describe('should support passing object slots via JSX children', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toBe(
|
||||
'<span><span>A</span><!----></span><span><span>B</span><!----></span><span><span>C</span><!----></span>',
|
||||
expect(wrapper.html()).toMatchInlineSnapshot(
|
||||
`
|
||||
"<span><span>A</span>
|
||||
<!----></span>
|
||||
<span><span>B</span>
|
||||
<!----></span>
|
||||
<span><span>C</span>
|
||||
<!----></span>"
|
||||
`
|
||||
);
|
||||
});
|
||||
|
||||
@ -602,8 +609,15 @@ describe('should support passing object slots via JSX children', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(wrapper.html()).toBe(
|
||||
'<span><span>A</span><!----></span><span><span>B</span><!----></span><span><span>C</span><!----></span>',
|
||||
expect(wrapper.html()).toMatchInlineSnapshot(
|
||||
`
|
||||
"<span><span>A</span>
|
||||
<!----></span>
|
||||
<span><span>B</span>
|
||||
<!----></span>
|
||||
<span><span>C</span>
|
||||
<!----></span>"
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -1,27 +1,29 @@
|
||||
import { transform } from '@babel/core';
|
||||
import JSX, { VueJSXPluginOptions } from '../src';
|
||||
import JSX, { type VueJSXPluginOptions } from '../src';
|
||||
|
||||
interface Test {
|
||||
name: string;
|
||||
from: string;
|
||||
}
|
||||
|
||||
const transpile = (
|
||||
source: string, options: VueJSXPluginOptions = {},
|
||||
) => new Promise((resolve, reject) => transform(
|
||||
source,
|
||||
{
|
||||
filename: '',
|
||||
presets: null,
|
||||
plugins: [[JSX, options]],
|
||||
configFile: false,
|
||||
}, (error, result) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(result?.code);
|
||||
},
|
||||
));
|
||||
const transpile = (source: string, options: VueJSXPluginOptions = {}) =>
|
||||
new Promise((resolve, reject) =>
|
||||
transform(
|
||||
source,
|
||||
{
|
||||
filename: '',
|
||||
presets: null,
|
||||
plugins: [[JSX, options]],
|
||||
configFile: false,
|
||||
},
|
||||
(error, result) => {
|
||||
if (error) {
|
||||
return reject(error);
|
||||
}
|
||||
resolve(result?.code);
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
[
|
||||
{
|
||||
@ -206,34 +208,29 @@ const transpile = (
|
||||
name: 'using v-slots without children should not be spread',
|
||||
from: '<A v-slots={slots} />',
|
||||
},
|
||||
].forEach((
|
||||
{ name, from },
|
||||
) => {
|
||||
test(
|
||||
name,
|
||||
async () => {
|
||||
expect(await transpile(from, { optimize: true, enableObjectSlots: true })).toMatchSnapshot(name);
|
||||
},
|
||||
);
|
||||
].forEach(({ name, from }) => {
|
||||
test(name, async () => {
|
||||
expect(
|
||||
await transpile(from, { optimize: true, enableObjectSlots: true })
|
||||
).toMatchSnapshot(name);
|
||||
});
|
||||
});
|
||||
|
||||
const overridePropsTests: Test[] = [{
|
||||
name: 'single',
|
||||
from: '<div {...a} />',
|
||||
}, {
|
||||
name: 'multiple',
|
||||
from: '<A loading {...a} {...{ b: 1, c: { d: 2 } }} class="x" style={x} />',
|
||||
}];
|
||||
const overridePropsTests: Test[] = [
|
||||
{
|
||||
name: 'single',
|
||||
from: '<div {...a} />',
|
||||
},
|
||||
{
|
||||
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);
|
||||
},
|
||||
);
|
||||
overridePropsTests.forEach(({ name, from }) => {
|
||||
test(`override props ${name}`, async () => {
|
||||
expect(await transpile(from, { mergeProps: false })).toMatchSnapshot(name);
|
||||
});
|
||||
});
|
||||
|
||||
const slotsTests: Test[] = [
|
||||
@ -256,15 +253,12 @@ 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);
|
||||
},
|
||||
);
|
||||
slotsTests.forEach(({ name, from }) => {
|
||||
test(`passing object slots via JSX children ${name}`, async () => {
|
||||
expect(
|
||||
await transpile(from, { optimize: true, enableObjectSlots: true })
|
||||
).toMatchSnapshot(name);
|
||||
});
|
||||
});
|
||||
|
||||
const objectSlotsTests = [
|
||||
@ -274,16 +268,12 @@ const objectSlotsTests = [
|
||||
},
|
||||
];
|
||||
|
||||
objectSlotsTests.forEach(({
|
||||
name, from,
|
||||
}) => {
|
||||
test(
|
||||
`disable object slot syntax with ${name}`,
|
||||
async () => {
|
||||
expect(await transpile(from, { optimize: true, enableObjectSlots: false }))
|
||||
.toMatchSnapshot(name);
|
||||
},
|
||||
);
|
||||
objectSlotsTests.forEach(({ name, from }) => {
|
||||
test(`disable object slot syntax with ${name}`, async () => {
|
||||
expect(
|
||||
await transpile(from, { optimize: true, enableObjectSlots: false })
|
||||
).toMatchSnapshot(name);
|
||||
});
|
||||
});
|
||||
|
||||
const pragmaTests = [
|
||||
@ -293,46 +283,40 @@ const pragmaTests = [
|
||||
},
|
||||
];
|
||||
|
||||
pragmaTests.forEach(({
|
||||
name, from,
|
||||
}) => {
|
||||
test(
|
||||
`set pragma to ${name}`,
|
||||
async () => {
|
||||
expect(await transpile(from, { pragma: 'custom' }))
|
||||
.toMatchSnapshot(name);
|
||||
},
|
||||
);
|
||||
pragmaTests.forEach(({ name, from }) => {
|
||||
test(`set pragma to ${name}`, async () => {
|
||||
expect(await transpile(from, { pragma: 'custom' })).toMatchSnapshot(name);
|
||||
});
|
||||
});
|
||||
|
||||
const isCustomElementTests = [{
|
||||
name: 'isCustomElement',
|
||||
from: '<foo><span>foo</span></foo>',
|
||||
}];
|
||||
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);
|
||||
},
|
||||
);
|
||||
test(name, async () => {
|
||||
expect(
|
||||
await transpile(from, { isCustomElement: (tag) => tag === 'foo' })
|
||||
).toMatchSnapshot(name);
|
||||
});
|
||||
});
|
||||
|
||||
const fragmentTests = [{
|
||||
name: '_Fragment already imported',
|
||||
from: `
|
||||
const fragmentTests = [
|
||||
{
|
||||
name: '_Fragment already imported',
|
||||
from: `
|
||||
import { Fragment as _Fragment } from 'vue'
|
||||
const Root1 = () => <>root1</>
|
||||
const Root2 = () => <_Fragment>root2</_Fragment>
|
||||
`,
|
||||
}];
|
||||
},
|
||||
];
|
||||
|
||||
fragmentTests.forEach(({ name, from }) => {
|
||||
test(
|
||||
name,
|
||||
async () => {
|
||||
expect(await transpile(from)).toMatchSnapshot(name);
|
||||
},
|
||||
);
|
||||
test(name, async () => {
|
||||
expect(await transpile(from)).toMatchSnapshot(name);
|
||||
});
|
||||
});
|
||||
|
@ -1,17 +1,20 @@
|
||||
import { shallowMount, mount } from '@vue/test-utils';
|
||||
import { defineComponent, VNode } from '@vue/runtime-dom';
|
||||
import { mount, shallowMount } from '@vue/test-utils';
|
||||
import { type VNode, defineComponent } from '@vue/runtime-dom';
|
||||
|
||||
test('input[type="checkbox"] should work', async () => {
|
||||
const wrapper = shallowMount({
|
||||
data() {
|
||||
return {
|
||||
test: true,
|
||||
};
|
||||
const wrapper = shallowMount(
|
||||
{
|
||||
data() {
|
||||
return {
|
||||
test: true,
|
||||
};
|
||||
},
|
||||
render() {
|
||||
return <input type="checkbox" v-model={this.test} />;
|
||||
},
|
||||
},
|
||||
render() {
|
||||
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;
|
||||
@ -24,19 +27,22 @@ test('input[type="checkbox"] should work', async () => {
|
||||
});
|
||||
|
||||
test('input[type="radio"] should work', async () => {
|
||||
const wrapper = shallowMount({
|
||||
data: () => ({
|
||||
test: '1',
|
||||
}),
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<input type="radio" value="1" v-model={this.test} name="test" />
|
||||
<input type="radio" value="2" v-model={this.test} name="test" />
|
||||
</>
|
||||
);
|
||||
const wrapper = shallowMount(
|
||||
{
|
||||
data: () => ({
|
||||
test: '1',
|
||||
}),
|
||||
render() {
|
||||
return (
|
||||
<>
|
||||
<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[];
|
||||
|
||||
@ -244,9 +250,7 @@ test('Named model', async () => {
|
||||
const handleClick = () => {
|
||||
emit('update:value', 2);
|
||||
};
|
||||
return () => (
|
||||
<div onClick={ handleClick }>{ props.value }</div>
|
||||
);
|
||||
return () => <div onClick={handleClick}>{props.value}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
@ -255,7 +259,7 @@ test('Named model', async () => {
|
||||
foo: 0,
|
||||
}),
|
||||
render() {
|
||||
return <Child v-model:value={ this.foo } />;
|
||||
return <Child v-model:value={this.foo} />;
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDirs": ["./src"],
|
||||
"outDir": "dist",
|
||||
"downlevelIteration": true,
|
||||
"declaration": true,
|
||||
"jsx": "preserve",
|
||||
},
|
||||
"include": ["src/**/*", "global.d.ts"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
8
packages/babel-plugin-jsx/tsup.config.ts
Normal file
8
packages/babel-plugin-jsx/tsup.config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
platform: 'neutral',
|
||||
});
|
@ -1,2 +1 @@
|
||||
# JSX Explorer
|
||||
|
||||
|
@ -1,19 +1,14 @@
|
||||
<title>Vue JSX Explorer</title>
|
||||
<link rel="stylesheet" data-name="vs/editor/editor.main" href="https://unpkg.com/monaco-editor@0.20.0/min/vs/editor/editor.main.css">
|
||||
|
||||
<div id="header"></div>
|
||||
<div id="source" class="editor"></div>
|
||||
<div id="output" class="editor"></div>
|
||||
|
||||
<script src="https://unpkg.com/monaco-editor@0.20.0/min/vs/loader.js"></script>
|
||||
<script>
|
||||
require.config({
|
||||
paths: {
|
||||
'vs': 'https://unpkg.com/monaco-editor@0.20.0/min/vs'
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<script src="./main.js"></script>
|
||||
<script>
|
||||
require(['vs/editor/editor.main'], init /* injected by build */)
|
||||
</script>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vue JSX Explorer</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="header"></div>
|
||||
<div id="source" class="editor"></div>
|
||||
<div id="output" class="editor"></div>
|
||||
<script type="module" src="./src/index.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,18 +2,19 @@
|
||||
"name": "@vue/jsx-explorer",
|
||||
"version": "1.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.34.0"
|
||||
"@babel/core": "^7.22.5",
|
||||
"@vue/babel-plugin-jsx": "workspace:*",
|
||||
"monaco-editor": "^0.39.0",
|
||||
"vue": "^3.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19.3",
|
||||
"css-loader": "^3.6.0",
|
||||
"html-webpack-plugin": "^4.5.2",
|
||||
"style-loader": "^1.3.0",
|
||||
"ts-loader": "^8.4.0",
|
||||
"typescript": "^4.8.4",
|
||||
"vue": "3.2.41",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-dev-server": "^3.11.3"
|
||||
"vite-plugin-monaco-editor": "^1.1.0",
|
||||
"vite-plugin-node-polyfills": "^0.9.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
}
|
||||
|
||||
#header {
|
||||
|
@ -1,42 +1,40 @@
|
||||
/* eslint-disable no-console */
|
||||
// eslint-disable-next-line import/no-unresolved
|
||||
import * as m from 'monaco-editor';
|
||||
import * as monaco from 'monaco-editor';
|
||||
import { watchEffect } from 'vue';
|
||||
import { transform } from '@babel/core';
|
||||
import babelPluginJsx from '../../babel-plugin-jsx/src';
|
||||
import { initOptions, compilerOptions, VueJSXPluginOptions } from './options';
|
||||
import babelPluginJsx from '@vue/babel-plugin-jsx';
|
||||
import {
|
||||
type VueJSXPluginOptions,
|
||||
compilerOptions,
|
||||
initOptions,
|
||||
} from './options';
|
||||
import './index.css';
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
monaco: typeof m
|
||||
init: () => void
|
||||
}
|
||||
}
|
||||
main();
|
||||
|
||||
interface PersistedState {
|
||||
src: string
|
||||
options: VueJSXPluginOptions
|
||||
src: string;
|
||||
options: VueJSXPluginOptions;
|
||||
}
|
||||
|
||||
window.init = () => {
|
||||
const { monaco } = window;
|
||||
|
||||
const persistedState: PersistedState = JSON.parse(localStorage.getItem('state') || '{}');
|
||||
function main() {
|
||||
const persistedState: PersistedState = JSON.parse(
|
||||
localStorage.getItem('state') || '{}'
|
||||
);
|
||||
|
||||
Object.assign(compilerOptions, persistedState.options);
|
||||
|
||||
const sharedEditorOptions: m.editor.IStandaloneEditorConstructionOptions = {
|
||||
theme: 'vs-dark',
|
||||
fontSize: 14,
|
||||
wordWrap: 'on',
|
||||
scrollBeyondLastLine: false,
|
||||
renderWhitespace: 'selection',
|
||||
contextmenu: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
const sharedEditorOptions: monaco.editor.IStandaloneEditorConstructionOptions =
|
||||
{
|
||||
theme: 'vs-dark',
|
||||
fontSize: 14,
|
||||
wordWrap: 'on',
|
||||
scrollBeyondLastLine: false,
|
||||
renderWhitespace: 'selection',
|
||||
contextmenu: false,
|
||||
minimap: {
|
||||
enabled: false,
|
||||
},
|
||||
};
|
||||
|
||||
monaco.languages.typescript.typescriptDefaults.setCompilerOptions({
|
||||
allowJs: true,
|
||||
@ -46,7 +44,10 @@ window.init = () => {
|
||||
});
|
||||
|
||||
const editor = monaco.editor.create(document.getElementById('source')!, {
|
||||
value: decodeURIComponent(window.location.hash.slice(1)) || persistedState.src || 'const App = () => <div>Hello World</div>',
|
||||
value:
|
||||
decodeURIComponent(window.location.hash.slice(1)) ||
|
||||
persistedState.src ||
|
||||
'const App = () => <div>Hello World</div>',
|
||||
language: 'typescript',
|
||||
tabSize: 2,
|
||||
...sharedEditorOptions,
|
||||
@ -69,19 +70,23 @@ window.init = () => {
|
||||
localStorage.setItem('state', state);
|
||||
window.location.hash = encodeURIComponent(src);
|
||||
console.clear();
|
||||
transform(src, {
|
||||
babelrc: false,
|
||||
plugins: [[babelPluginJsx, compilerOptions]],
|
||||
ast: true,
|
||||
}, (err, result = {}) => {
|
||||
const res = result!;
|
||||
if (!err) {
|
||||
console.log('AST', res.ast!);
|
||||
output.setValue(res.code!);
|
||||
} else {
|
||||
output.setValue(err.message!);
|
||||
transform(
|
||||
src,
|
||||
{
|
||||
babelrc: false,
|
||||
plugins: [[babelPluginJsx, compilerOptions]],
|
||||
ast: true,
|
||||
},
|
||||
(err, result = {}) => {
|
||||
const res = result!;
|
||||
if (!err) {
|
||||
console.log('AST', res.ast!);
|
||||
output.setValue(res.code!);
|
||||
} else {
|
||||
output.setValue(err.message!);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
};
|
||||
|
||||
// handle resize
|
||||
@ -95,11 +100,9 @@ window.init = () => {
|
||||
|
||||
// update compile output when input changes
|
||||
editor.onDidChangeModelContent(debounce(reCompile));
|
||||
};
|
||||
}
|
||||
|
||||
function debounce<T extends(...args: any[]) => any>(
|
||||
fn: T,
|
||||
delay = 300): T {
|
||||
function debounce<T extends (...args: any[]) => any>(fn: T, delay = 300): T {
|
||||
let prevTimer: number | null = null;
|
||||
return ((...args: any[]) => {
|
||||
if (prevTimer) {
|
||||
|
@ -1,7 +1,5 @@
|
||||
import {
|
||||
h, reactive, createApp,
|
||||
} from 'vue';
|
||||
import { VueJSXPluginOptions } from '../../babel-plugin-jsx/src';
|
||||
import { createApp, h, reactive } from 'vue';
|
||||
import { type VueJSXPluginOptions } from '@vue/babel-plugin-jsx';
|
||||
|
||||
export { VueJSXPluginOptions };
|
||||
|
||||
@ -19,16 +17,15 @@ const App = {
|
||||
h(
|
||||
'a',
|
||||
{
|
||||
href: 'https://app.netlify.com/sites/vue-next-jsx-explorer/deploys',
|
||||
href: 'https://app.netlify.com/sites/vue-jsx-explorer/deploys',
|
||||
target: '_blank',
|
||||
},
|
||||
'History',
|
||||
'History'
|
||||
),
|
||||
|
||||
h('div', { id: 'options-wrapper' }, [
|
||||
h('div', { id: 'options-label' }, 'Options ↘'),
|
||||
h('ul', { id: 'options' }, [
|
||||
|
||||
// mergeProps
|
||||
h('li', [
|
||||
h('input', {
|
||||
@ -37,7 +34,9 @@ const App = {
|
||||
name: 'mergeProps',
|
||||
checked: compilerOptions.mergeProps,
|
||||
onChange(e: Event) {
|
||||
compilerOptions.mergeProps = (e.target as HTMLInputElement).checked;
|
||||
compilerOptions.mergeProps = (
|
||||
e.target as HTMLInputElement
|
||||
).checked;
|
||||
},
|
||||
}),
|
||||
h('label', { for: 'mergeProps' }, 'mergeProps'),
|
||||
@ -50,7 +49,9 @@ const App = {
|
||||
id: 'optimize',
|
||||
checked: compilerOptions.optimize,
|
||||
onChange(e: Event) {
|
||||
compilerOptions.optimize = (e.target as HTMLInputElement).checked;
|
||||
compilerOptions.optimize = (
|
||||
e.target as HTMLInputElement
|
||||
).checked;
|
||||
},
|
||||
}),
|
||||
h('label', { for: 'optimize' }, 'optimize'),
|
||||
@ -63,7 +64,9 @@ const App = {
|
||||
id: 'transformOn',
|
||||
checked: compilerOptions.transformOn,
|
||||
onChange(e: Event) {
|
||||
compilerOptions.transformOn = (e.target as HTMLInputElement).checked;
|
||||
compilerOptions.transformOn = (
|
||||
e.target as HTMLInputElement
|
||||
).checked;
|
||||
},
|
||||
}),
|
||||
h('label', { for: 'transformOn' }, 'transformOn'),
|
||||
@ -73,10 +76,12 @@ const App = {
|
||||
h('li', [
|
||||
h('input', {
|
||||
type: 'checkbox',
|
||||
id: 'transformOn',
|
||||
id: 'enableObjectSlots',
|
||||
checked: compilerOptions.enableObjectSlots,
|
||||
onChange(e: Event) {
|
||||
compilerOptions.enableObjectSlots = (e.target as HTMLInputElement).checked;
|
||||
compilerOptions.enableObjectSlots = (
|
||||
e.target as HTMLInputElement
|
||||
).checked;
|
||||
},
|
||||
}),
|
||||
h('label', { for: 'enableObjectSlots' }, 'enableObjectSlots'),
|
||||
|
14
packages/jsx-explorer/vite.config.ts
Normal file
14
packages/jsx-explorer/vite.config.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import MonacoEditorPlugin from 'vite-plugin-monaco-editor';
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
MonacoEditorPlugin({}),
|
||||
nodePolyfills({
|
||||
globals: {
|
||||
process: true,
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
5732
pnpm-lock.yaml
generated
Normal file
5732
pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
packages:
|
||||
- packages/*
|
@ -1,12 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"config:base",
|
||||
"schedule:weekly",
|
||||
"group:allNonMajor",
|
||||
":semanticCommitTypeAll(chore)"
|
||||
],
|
||||
"rangeStrategy": "bump",
|
||||
"labels": ["dependencies"],
|
||||
"ignoreDeps": ["@types/node", "@vue/test-utils"]
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
const webpack = require('webpack');
|
||||
const WebpackDevServer = require('webpack-dev-server');
|
||||
const webpackConfig = require('./webpack.base.conf');
|
||||
|
||||
const compiler = webpack(webpackConfig);
|
||||
|
||||
const devServerOptions = {
|
||||
inline: true,
|
||||
open: true,
|
||||
hot: true,
|
||||
overlay: false,
|
||||
};
|
||||
|
||||
const server = new WebpackDevServer(compiler, devServerOptions);
|
||||
|
||||
server.listen(8080, '127.0.0.1');
|
@ -1,13 +0,0 @@
|
||||
const webpack = require('webpack');
|
||||
const webpackConfig = require('./webpack.base.conf');
|
||||
|
||||
webpack(Object.assign(webpackConfig, { mode: 'production', devtool: false }), (err, stats) => {
|
||||
if (err) throw err;
|
||||
process.stdout.write(`${stats.toString({
|
||||
colors: true,
|
||||
modules: false,
|
||||
children: false,
|
||||
chunks: false,
|
||||
chunkModules: false,
|
||||
})}\n\n`);
|
||||
});
|
@ -1,48 +0,0 @@
|
||||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
mode: 'development',
|
||||
devtool: false,
|
||||
context: path.join(__dirname, '../packages/jsx-explorer'),
|
||||
entry: './src/index.ts',
|
||||
output: {
|
||||
publicPath: './',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
loader: 'ts-loader',
|
||||
exclude: /node_modules/,
|
||||
options: {
|
||||
transpileOnly: true,
|
||||
compilerOptions: { downlevelIteration: true },
|
||||
},
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
'style-loader', 'css-loader',
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.mjs$/,
|
||||
include: /node_modules/,
|
||||
type: 'javascript/auto'
|
||||
}
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'index.html',
|
||||
filename: 'index.html',
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
extensions: ['.ts', '.js', '.mjs'],
|
||||
},
|
||||
node: {
|
||||
fs: 'empty',
|
||||
},
|
||||
};
|
@ -1,26 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"target": "es2015",
|
||||
"module": "commonjs",
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"moduleResolution": "node",
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"experimentalDecorators": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"removeComments": false,
|
||||
"jsx": "preserve",
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom"
|
||||
],
|
||||
"types": ["node", "jest"],
|
||||
"types": ["vitest/globals"],
|
||||
"skipLibCheck": true,
|
||||
"paths": {
|
||||
"@vue/babel-plugin-jsx": ["./packages/babel-plugin-jsx/src"]
|
||||
}
|
||||
},
|
||||
"include": [
|
||||
"global.d.ts",
|
||||
"packages/*/src",
|
||||
"packages/*/test",
|
||||
]
|
||||
"include": ["packages/*/src", "packages/*/test"]
|
||||
}
|
||||
|
22
vitest.config.ts
Normal file
22
vitest.config.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { defineConfig } from 'vitest/config';
|
||||
import { babel } from '@rollup/plugin-babel';
|
||||
import Jsx from './packages/babel-plugin-jsx/src';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
babel({
|
||||
babelHelpers: 'bundled',
|
||||
extensions: ['.tsx', '.jsx'],
|
||||
plugins: [
|
||||
[
|
||||
Jsx,
|
||||
{ optimize: true, isCustomElement: (tag: string) => /^x-/.test(tag) },
|
||||
],
|
||||
],
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
globals: true,
|
||||
environment: 'jsdom',
|
||||
},
|
||||
});
|
Loading…
Reference in New Issue
Block a user