refactor: upgrade project setup (#646)

This commit is contained in:
三咲智子 Kevin Deng 2023-06-22 12:14:02 +08:00 committed by GitHub
parent 687be8aca8
commit dbba3205e4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 6861 additions and 12397 deletions

View File

@ -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

View File

@ -1 +1,3 @@
dist
dist
coverage
node_modules

View File

@ -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,
},
],
},
};

View File

@ -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 -->

View File

@ -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
View 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
View 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

View File

@ -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
View 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
View File

@ -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
View File

@ -0,0 +1,5 @@
node_modules
dist
coverage
pnpm-lock.yaml
CHANGELOG.md

3
.prettierrc Normal file
View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

3
global.d.ts vendored
View File

@ -1,3 +0,0 @@
declare module '*.js';
declare module '@babel/helper-module-imports';
declare module '@babel/plugin-syntax-jsx';

View File

@ -1,8 +0,0 @@
{
"npmClient": "yarn",
"useWorkspaces": true,
"packages": [
"packages/*"
],
"version": "1.1.1"
}

6
netlify.toml Normal file
View File

@ -0,0 +1,6 @@
[build.environment]
NODE_VERSION = "18"
[build]
command = "pnpm run build"
publish = "packages/jsx-explorer/dist"

View File

@ -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"
}
}

View File

@ -0,0 +1,4 @@
declare function transformOn(
obj: Record<string, any>
): Record<`on${string}`, any>;
export default transformOn;

View File

@ -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;
};

View File

@ -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"
}

View File

@ -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>
</>
);
},

View File

@ -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>
</>
);
},

View File

@ -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) }],
],
};

View File

@ -1,2 +0,0 @@
declare module '@babel/helper-module-imports';
declare module '@babel/plugin-syntax-jsx';

View File

@ -1,11 +0,0 @@
module.exports = {
setupFiles: ['./test/setup.ts'],
transform: {
'\\.(ts|tsx)$': 'ts-jest',
},
globals: {
'ts-jest': {
babelConfig: true,
},
},
};

View File

@ -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"

View File

@ -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'))
);
}
},

View File

@ -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 {

View File

@ -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;

View File

@ -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)
)
)
);
},
},
});
};

View File

@ -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));
},
},
});
};

View File

@ -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) {

View File

@ -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

View File

@ -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>"
`
);
});
});

View File

@ -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);
});
});

View File

@ -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} />;
},
});

View File

@ -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"]
}

View File

@ -0,0 +1,8 @@
import { defineConfig } from 'tsup';
export default defineConfig({
entry: ['src/index.ts'],
format: ['cjs', 'esm'],
dts: true,
platform: 'neutral',
});

View File

@ -1,2 +1 @@
# JSX Explorer

View File

@ -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>

View File

@ -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"
}
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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'),

View 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 Normal file

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@ -0,0 +1,2 @@
packages:
- packages/*

View File

@ -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"]
}

View File

@ -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');

View File

@ -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`);
});

View File

@ -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',
},
};

View File

@ -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
View 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',
},
});

11439
yarn.lock

File diff suppressed because it is too large Load Diff