refactor: lint

This commit is contained in:
Kevin Deng
2025-11-26 17:12:35 +08:00
parent 620450b5ba
commit 5279b2eb69
33 changed files with 2271 additions and 449 deletions

View File

@@ -10,5 +10,5 @@ jobs:
steps: steps:
- uses: actions-cool/emoji-helper@040b841cb25e2e6f50151c73b5ce12fee57019d2 # v1.0.0 - uses: actions-cool/emoji-helper@040b841cb25e2e6f50151c73b5ce12fee57019d2 # v1.0.0
with: with:
type: 'release' type: release
emoji: '+1, laugh, heart, hooray, rocket, eyes' emoji: '+1, laugh, heart, hooray, rocket, eyes'

View File

@@ -12,7 +12,7 @@ jobs:
if: github.event.label.name == 'help wanted' if: github.event.label.name == 'help wanted'
uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3 uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3
with: with:
actions: 'create-comment' actions: create-comment
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
body: | body: |
Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to send us a Pull Request for it. Please be sure to fill in the default template in the Pull Request, provide changelog/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution! Hello @${{ github.event.issue.user.login }}. We totally like your proposal/feedback, welcome to send us a Pull Request for it. Please be sure to fill in the default template in the Pull Request, provide changelog/documentation/test cases if needed and make sure CI passed, we will review it soon. We appreciate your effort in advance and looking forward to your contribution!
@@ -21,7 +21,7 @@ jobs:
if: github.event.label.name == 'need reproduction' if: github.event.label.name == 'need reproduction'
uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3 uses: actions-cool/issues-helper@45d75b6cf72bf4f254be6230cb887ad002702491 # v3.6.3
with: with:
actions: 'create-comment' actions: create-comment
issue-number: ${{ github.event.issue.number }} issue-number: ${{ github.event.issue.number }}
body: | body: |
Hello @${{ github.event.issue.user.login }}. In order to facilitate location and troubleshooting, we need you to provide a realistic example. Please forking these link [codesandbox](https://codesandbox.io/s/magical-vaughan-byzhk?file=/src/App.jsx) or provide your GitHub repository. Hello @${{ github.event.issue.user.login }}. In order to facilitate location and troubleshooting, we need you to provide a realistic example. Please forking these link [codesandbox](https://codesandbox.io/s/magical-vaughan-byzhk?file=/src/App.jsx) or provide your GitHub repository.

View File

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

View File

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

View File

@@ -1,67 +1,11 @@
// @ts-check import { sxzz } from '@sxzz/eslint-config'
import { builtinModules } from 'node:module'
import tseslint from 'typescript-eslint'
import importX from 'eslint-plugin-import-x'
import eslint from '@eslint/js'
import eslintConfigPrettier from 'eslint-config-prettier'
export default tseslint.config( export default sxzz(
eslint.configs.recommended, {},
{ {
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
extends: [...tseslint.configs.recommended],
plugins: {
import: importX,
},
languageOptions: {
parserOptions: {
sourceType: 'module',
ecmaFeatures: {
jsx: true,
},
},
},
rules: { rules: {
eqeqeq: ['warn', 'always', { null: 'never' }], 'import/no-default-export': 'off',
'no-debugger': ['error'], 'unicorn/filename-case': 'off',
'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,
},
],
}, },
}, },
eslintConfigPrettier,
{
ignores: ['**/dist/', '**/coverage/'],
},
) )

View File

@@ -4,6 +4,11 @@
"private": true, "private": true,
"packageManager": "pnpm@10.18.1", "packageManager": "pnpm@10.18.1",
"type": "module", "type": "module",
"keywords": [
"vue",
"jsx"
],
"license": "MIT",
"scripts": { "scripts": {
"dev": "pnpm -C packages/jsx-explorer run dev", "dev": "pnpm -C packages/jsx-explorer run dev",
"build": "tsdown", "build": "tsdown",
@@ -14,15 +19,11 @@
"typecheck": "tsc", "typecheck": "tsc",
"release": "bumpp -r" "release": "bumpp -r"
}, },
"license": "MIT",
"keywords": [
"vue",
"jsx"
],
"devDependencies": { "devDependencies": {
"@babel/plugin-syntax-typescript": "^7.27.1", "@babel/plugin-syntax-typescript": "^7.27.1",
"@eslint/js": "^9.37.0",
"@rollup/plugin-babel": "^6.0.4", "@rollup/plugin-babel": "^6.0.4",
"@sxzz/eslint-config": "^7.3.2",
"@sxzz/prettier-config": "^2.2.5",
"@types/babel__core": "^7.20.5", "@types/babel__core": "^7.20.5",
"@types/babel__helper-module-imports": "^7.18.3", "@types/babel__helper-module-imports": "^7.18.3",
"@types/babel__helper-plugin-utils": "^7.10.3", "@types/babel__helper-plugin-utils": "^7.10.3",
@@ -31,15 +32,13 @@
"@vue/babel-plugin-jsx": "workspace:*", "@vue/babel-plugin-jsx": "workspace:*",
"bumpp": "^10.3.1", "bumpp": "^10.3.1",
"eslint": "^9.37.0", "eslint": "^9.37.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import-x": "^4.16.1",
"jsdom": "^27.0.0", "jsdom": "^27.0.0",
"prettier": "3.6.2", "prettier": "3.6.2",
"tsdown": "^0.15.6", "tsdown": "^0.16.7",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "~5.9.3", "typescript": "~5.9.3",
"typescript-eslint": "^8.45.0",
"vite": "^7.1.9", "vite": "^7.1.9",
"vitest": "^3.2.4" "vitest": "^3.2.4"
} },
"prettier": "@sxzz/prettier-config"
} }

View File

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

View File

@@ -6,4 +6,5 @@ function transformOn(obj) {
return result return result
} }
export { transformOn as default, transformOn as 'module.exports' } export default transformOn
export { transformOn as 'module.exports' }

View File

@@ -1,17 +1,17 @@
{ {
"name": "@vue/babel-helper-vue-transform-on", "name": "@vue/babel-helper-vue-transform-on",
"version": "2.0.1", "version": "2.0.1",
"type": "module",
"description": "to help transform on", "description": "to help transform on",
"author": "Amour1688 <lcz_1996@foxmail.com>", "type": "module",
"license": "MIT", "license": "MIT",
"exports": {
".": "./index.mjs",
"./package.json": "./package.json"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/vuejs/babel-plugin-jsx.git", "url": "git+https://github.com/vuejs/babel-plugin-jsx.git",
"directory": "packages/babel-helper-vue-transform-on" "directory": "packages/babel-helper-vue-transform-on"
},
"author": "Amour1688 <lcz_1996@foxmail.com>",
"exports": {
".": "./index.mjs",
"./package.json": "./package.json"
} }
} }

View File

@@ -17,7 +17,7 @@ npm install @vue/babel-plugin-jsx -D
配置 Babel 配置 Babel
```js ```json
{ {
"plugins": ["@vue/babel-plugin-jsx"] "plugins": ["@vue/babel-plugin-jsx"]
} }
@@ -102,7 +102,7 @@ const App = {
``` ```
```jsx ```jsx
import { withModifiers, defineComponent } from 'vue' import { defineComponent, withModifiers } from 'vue'
const App = defineComponent({ const App = defineComponent({
setup() { setup() {
@@ -169,13 +169,13 @@ const App = {
``` ```
```jsx ```jsx
<input v-model={[val, ['modifier']]} /> <input v-model={[val, ['modifier']]} />;
// 或者 // 或者
<input v-model_modifier={val} /> <input v-model_modifier={val} />
``` ```
```jsx ```jsx
<A v-model={[val, 'argument', ['modifier']]} /> <A v-model={[val, 'argument', ['modifier']]} />;
// 或者 // 或者
<input v-model:argument_modifier={val} /> <input v-model:argument_modifier={val} />
``` ```
@@ -227,7 +227,7 @@ h(A, {
modifier: true, modifier: true,
}, },
'onUpdate:modelValue': ($event) => (foo = $event), 'onUpdate:modelValue': ($event) => (foo = $event),
bar: bar, bar,
barModifiers: { barModifiers: {
modifier: true, modifier: true,
}, },
@@ -284,7 +284,7 @@ const App = {
// or // or
const App = { const App2 = {
setup() { setup() {
const slots = { const slots = {
default: () => <div>A</div>, default: () => <div>A</div>,
@@ -295,7 +295,7 @@ const App = {
} }
// 或者,当 `enableObjectSlots` 不是 `false` 时,您可以使用对象插槽 // 或者,当 `enableObjectSlots` 不是 `false` 时,您可以使用对象插槽
const App = { const App3 = {
setup() { setup() {
return () => ( return () => (
<> <>

View File

@@ -106,7 +106,7 @@ const App = {
``` ```
```jsx ```jsx
import { withModifiers, defineComponent } from 'vue' import { defineComponent, withModifiers } from 'vue'
const App = defineComponent({ const App = defineComponent({
setup() { setup() {
@@ -173,13 +173,13 @@ const App = {
``` ```
```jsx ```jsx
<input v-model={[val, ['modifier']]} /> <input v-model={[val, ['modifier']]} />;
// Or // Or
<input v-model_modifier={val} /> <input v-model_modifier={val} />
``` ```
```jsx ```jsx
<A v-model={[val, 'argument', ['modifier']]} /> <A v-model={[val, 'argument', ['modifier']]} />;
// Or // Or
<input v-model:argument_modifier={val} /> <input v-model:argument_modifier={val} />
``` ```
@@ -231,7 +231,7 @@ h(A, {
modifier: true, modifier: true,
}, },
'onUpdate:modelValue': ($event) => (foo = $event), 'onUpdate:modelValue': ($event) => (foo = $event),
bar: bar, bar,
barModifiers: { barModifiers: {
modifier: true, modifier: true,
}, },
@@ -288,7 +288,7 @@ const App = {
// or // or
const App = { const App2 = {
setup() { setup() {
const slots = { const slots = {
default: () => <div>A</div>, default: () => <div>A</div>,
@@ -299,7 +299,7 @@ const App = {
} }
// or you can use object slots when `enableObjectSlots` is not false. // or you can use object slots when `enableObjectSlots` is not false.
const App = { const App3 = {
setup() { setup() {
return () => ( return () => (
<> <>

View File

@@ -2,37 +2,45 @@
"name": "@vue/babel-plugin-jsx", "name": "@vue/babel-plugin-jsx",
"version": "2.0.1", "version": "2.0.1",
"description": "Babel plugin for Vue 3 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",
"type": "module", "type": "module",
"main": "./dist/index.mjs", "license": "MIT",
"module": "./dist/index.mjs", "homepage": "https://github.com/vuejs/babel-plugin-jsx/tree/dev/packages/babel-plugin-jsx#readme",
"types": "./dist/index.d.mts", "bugs": {
"exports": { "url": "https://github.com/vuejs/babel-plugin-jsx/issues"
".": {
"dev": "./src/index.ts",
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"publishConfig": {
"exports": {
".": "./dist/index.mjs",
"./package.json": "./package.json"
}
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/vuejs/babel-plugin-jsx.git", "url": "git+https://github.com/vuejs/babel-plugin-jsx.git",
"directory": "packages/babel-plugin-jsx" "directory": "packages/babel-plugin-jsx"
}, },
"bugs": { "author": "Amour1688 <lcz_1996@foxmail.com>",
"url": "https://github.com/vuejs/babel-plugin-jsx/issues"
},
"files": [ "files": [
"dist" "dist"
], ],
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"dev": "./src/index.ts",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"publishConfig": {
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
}
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
}
},
"dependencies": { "dependencies": {
"@babel/helper-module-imports": "^7.27.1", "@babel/helper-module-imports": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1",
@@ -52,13 +60,5 @@
"@vue/test-utils": "^2.4.6", "@vue/test-utils": "^2.4.6",
"regenerator-runtime": "^0.14.1", "regenerator-runtime": "^0.14.1",
"vue": "catalog:" "vue": "catalog:"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
"optional": true
}
} }
} }

View File

@@ -1,15 +1,15 @@
import t from '@babel/types' import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports'
import type * as BabelCore from '@babel/core' import { declare } from '@babel/helper-plugin-utils'
import _template from '@babel/template'
// @ts-expect-error // @ts-expect-error
import _syntaxJsx from '@babel/plugin-syntax-jsx' import _syntaxJsx from '@babel/plugin-syntax-jsx'
import { addNamed, addNamespace, isModule } from '@babel/helper-module-imports' import _template from '@babel/template'
import { type NodePath, type Visitor } from '@babel/traverse' import t from '@babel/types'
import ResolveType from '@vue/babel-plugin-resolve-type' import ResolveType from '@vue/babel-plugin-resolve-type'
import { declare } from '@babel/helper-plugin-utils'
import transformVueJSX from './transform-vue-jsx'
import sugarFragment from './sugar-fragment' import sugarFragment from './sugar-fragment'
import transformVueJSX from './transform-vue-jsx'
import type { State, VueJSXPluginOptions } from './interface' import type { State, VueJSXPluginOptions } from './interface'
import type * as BabelCore from '@babel/core'
import type { NodePath, Visitor } from '@babel/traverse'
export { VueJSXPluginOptions } export { VueJSXPluginOptions }
@@ -30,7 +30,7 @@ const hasJSX = (parentPath: NodePath<t.Program>) => {
return fileHasJSX return fileHasJSX
} }
const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+([^\s]+)/ const JSX_ANNOTATION_REGEX = /\*?\s*@jsx\s+(\S+)/
/* #__NO_SIDE_EFFECTS__ */ /* #__NO_SIDE_EFFECTS__ */
function interopDefault(m: any) { function interopDefault(m: any) {
@@ -55,7 +55,7 @@ const plugin: (
resolveType = ResolveType(api, opt.resolveType, dirname) resolveType = ResolveType(api, opt.resolveType, dirname)
} }
return { return {
...(resolveType || {}), ...resolveType,
name: 'babel-plugin-jsx', name: 'babel-plugin-jsx',
inherits: /*#__PURE__*/ interopDefault(syntaxJsx), inherits: /*#__PURE__*/ interopDefault(syntaxJsx),
visitor: { visitor: {
@@ -115,9 +115,9 @@ const plugin: (
return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s)); return typeof s === 'function' || (Object.prototype.toString.call(s) === '[object Object]' && !${isVNodeName}(s));
} }
` `
const lastImport = (path.get('body') as NodePath[]) const lastImport = (path.get('body') as NodePath[]).findLast(
.filter((p) => p.isImportDeclaration()) (p) => p.isImportDeclaration(),
.pop() )
if (lastImport) { if (lastImport) {
lastImport.insertAfter(ast) lastImport.insertAfter(ast)
} }
@@ -160,16 +160,13 @@ const plugin: (
` `
const nodePaths = path.get('body') as NodePath[] const nodePaths = path.get('body') as NodePath[]
const lastImport = nodePaths const lastImport = nodePaths.findLast(
.filter( (p) =>
(p) => p.isVariableDeclaration() &&
p.isVariableDeclaration() && p.node.declarations.some(
p.node.declarations.some( (d) => (d.id as t.Identifier)?.name === sourceName.name,
(d) => ),
(d.id as t.Identifier)?.name === sourceName.name, )
),
)
.pop()
if (lastImport) { if (lastImport) {
lastImport.insertAfter(ast) lastImport.insertAfter(ast)
} }

View File

@@ -1,6 +1,6 @@
import type t from '@babel/types'
import type * as BabelCore from '@babel/core' import type * as BabelCore from '@babel/core'
import { type Options } from '@vue/babel-plugin-resolve-type' import type t from '@babel/types'
import type { Options } from '@vue/babel-plugin-resolve-type'
export type Slots = t.Identifier | t.ObjectExpression | null export type Slots = t.Identifier | t.ObjectExpression | null

View File

@@ -1,7 +1,7 @@
import t from '@babel/types' import t from '@babel/types'
import { type NodePath } from '@babel/traverse'
import { createIdentifier } from './utils' import { createIdentifier } from './utils'
import type { State } from './interface' import type { State } from './interface'
import type { NodePath } from '@babel/traverse'
export type Tag = export type Tag =
| t.Identifier | t.Identifier
@@ -187,8 +187,7 @@ const resolveDirective = (
} }
return modelToUse return modelToUse
} }
const referenceName = const referenceName = `v${directiveName[0].toUpperCase()}${directiveName.slice(1)}`
'v' + directiveName[0].toUpperCase() + directiveName.slice(1)
if (path.scope.references[referenceName]) { if (path.scope.references[referenceName]) {
return t.identifier(referenceName) return t.identifier(referenceName)
} }

View File

@@ -1,6 +1,6 @@
// https://github.com/vuejs/core/blob/main/packages/shared/src/patchFlags.ts // https://github.com/vuejs/core/blob/main/packages/shared/src/patchFlags.ts
export const enum PatchFlags { export enum PatchFlags {
TEXT = 1, TEXT = 1,
CLASS = 1 << 1, CLASS = 1 << 1,
STYLE = 1 << 2, STYLE = 1 << 2,

View File

@@ -1,5 +1,5 @@
// https://github.com/vuejs/core/blob/main/packages/shared/src/slotFlags.ts // https://github.com/vuejs/core/blob/main/packages/shared/src/slotFlags.ts
const enum SlotFlags { export enum SlotFlags {
/** /**
* Stable slots that only reference slot props or context state. The slot * Stable slots that only reference slot props or context state. The slot
* can fully capture its own dependencies so when passed down the parent won't * can fully capture its own dependencies so when passed down the parent won't
@@ -20,5 +20,3 @@ const enum SlotFlags {
*/ */
FORWARDED = 3, FORWARDED = 3,
} }
export default SlotFlags

View File

@@ -1,7 +1,7 @@
import t from '@babel/types' import t from '@babel/types'
import { type NodePath, type Visitor } from '@babel/traverse' import { createIdentifier, FRAGMENT } from './utils'
import type { State } from './interface' import type { State } from './interface'
import { FRAGMENT, createIdentifier } from './utils' import type { NodePath, Visitor } from '@babel/traverse'
const transformFragment = ( const transformFragment = (
path: NodePath<t.JSXFragment>, path: NodePath<t.JSXFragment>,

View File

@@ -1,6 +1,8 @@
import t from '@babel/types'
import { type NodePath, type Visitor } from '@babel/traverse'
import { addDefault } from '@babel/helper-module-imports' import { addDefault } from '@babel/helper-module-imports'
import t from '@babel/types'
import parseDirectives from './parseDirectives'
import { PatchFlags } from './patchFlags'
import { SlotFlags } from './slotFlags'
import { import {
buildIIFE, buildIIFE,
checkIsComponent, checkIsComponent,
@@ -18,10 +20,8 @@ import {
transformText, transformText,
walksScope, walksScope,
} from './utils' } from './utils'
import SlotFlags from './slotFlags'
import { PatchFlags } from './patchFlags'
import parseDirectives from './parseDirectives'
import type { Slots, State } from './interface' import type { Slots, State } from './interface'
import type { NodePath, Visitor } from '@babel/traverse'
const xlinkRE = /^xlink([A-Z])/ const xlinkRE = /^xlink([A-Z])/
@@ -229,15 +229,15 @@ const buildProps = (path: NodePath<t.JSXElement>, state: State) => {
), ),
) )
if (!isDynamic) { if (isDynamic) {
dynamicPropNames.add((updateName as t.StringLiteral).value)
} else {
hasDynamicKeys = true hasDynamicKeys = true
} else {
dynamicPropNames.add((updateName as t.StringLiteral).value)
} }
}) })
} }
} else { } else {
if (name.match(xlinkRE)) { if (xlinkRE.test(name)) {
name = name.replace( name = name.replace(
xlinkRE, xlinkRE,
(_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`, (_, firstCharacter) => `xlink:${firstCharacter.toLowerCase()}`,

View File

@@ -1,19 +1,12 @@
import t from '@babel/types' import t from '@babel/types'
import { type NodePath } from '@babel/traverse'
import { isHTMLTag, isSVGTag } from '@vue/shared' import { isHTMLTag, isSVGTag } from '@vue/shared'
import { SlotFlags } from './slotFlags'
import type { State } from './interface' import type { State } from './interface'
import SlotFlags from './slotFlags' import type { NodePath } from '@babel/traverse'
export const JSX_HELPER_KEY = 'JSX_HELPER_KEY' export const JSX_HELPER_KEY = 'JSX_HELPER_KEY'
export const FRAGMENT = 'Fragment' export const FRAGMENT = 'Fragment'
export const KEEP_ALIVE = 'KeepAlive' export const KEEP_ALIVE = 'KeepAlive'
/**
* create Identifier
* @param path NodePath
* @param state
* @param name string
* @returns MemberExpression
*/
export const createIdentifier = ( export const createIdentifier = (
state: State, state: State,
name: string, name: string,
@@ -34,14 +27,10 @@ export const isDirective = (src: string): boolean =>
*/ */
// if _Fragment is already imported, it will end with number // if _Fragment is already imported, it will end with number
export const shouldTransformedToSlots = (tag: string) => export const shouldTransformedToSlots = (tag: string) =>
!(tag.match(RegExp(`^_?${FRAGMENT}\\d*$`)) || tag === KEEP_ALIVE) !new RegExp(String.raw`^_?${FRAGMENT}\d*$`).test(tag) && tag !== KEEP_ALIVE
/** /**
* Check if a Node is a component * Check if a Node is a component
*
* @param t
* @param path JSXOpeningElement
* @returns boolean
*/ */
export const checkIsComponent = ( export const checkIsComponent = (
path: NodePath<t.JSXOpeningElement>, path: NodePath<t.JSXOpeningElement>,
@@ -136,7 +125,7 @@ export const transformJSXText = (
path: NodePath<t.JSXText | t.StringLiteral>, path: NodePath<t.JSXText | t.StringLiteral>,
): t.StringLiteral | null => { ): t.StringLiteral | null => {
const str = transformText(path.node.value) const str = transformText(path.node.value)
return str !== '' ? t.stringLiteral(str) : null return str === '' ? null : t.stringLiteral(str)
} }
export const transformText = (text: string) => { export const transformText = (text: string) => {
@@ -144,8 +133,8 @@ export const transformText = (text: string) => {
let lastNonEmptyLine = 0 let lastNonEmptyLine = 0
for (let i = 0; i < lines.length; i++) { for (const [i, line] of lines.entries()) {
if (lines[i].match(/[^ \t]/)) { if (/[^ \t]/.test(line)) {
lastNonEmptyLine = i lastNonEmptyLine = i
} }
} }
@@ -160,16 +149,16 @@ export const transformText = (text: string) => {
const isLastNonEmptyLine = i === lastNonEmptyLine const isLastNonEmptyLine = i === lastNonEmptyLine
// replace rendered whitespace tabs with spaces // replace rendered whitespace tabs with spaces
let trimmedLine = line.replace(/\t/g, ' ') let trimmedLine = line.replaceAll('\t', ' ')
// trim whitespace touching a newline // trim whitespace touching a newline
if (!isFirstLine) { if (!isFirstLine) {
trimmedLine = trimmedLine.replace(/^[ ]+/, '') trimmedLine = trimmedLine.replace(/^ +/, '')
} }
// trim whitespace touching an endline // trim whitespace touching an endline
if (!isLastLine) { if (!isLastLine) {
trimmedLine = trimmedLine.replace(/[ ]+$/, '') trimmedLine = trimmedLine.replace(/ +$/, '')
} }
if (trimmedLine) { if (trimmedLine) {

View File

@@ -1,12 +1,12 @@
import { mount, shallowMount, type VueWrapper } from '@vue/test-utils'
import { import {
type CSSProperties,
type ComponentPublicInstance,
Transition,
defineComponent, defineComponent,
reactive, reactive,
ref, ref,
Transition,
type ComponentPublicInstance,
type CSSProperties,
} from 'vue' } from 'vue'
import { type VueWrapper, mount, shallowMount } from '@vue/test-utils'
const patchFlagExpect = ( const patchFlagExpect = (
wrapper: VueWrapper<ComponentPublicInstance>, wrapper: VueWrapper<ComponentPublicInstance>,
@@ -129,21 +129,21 @@ describe('Transform JSX', () => {
return () => <div class="a" {...{ class: 'b' }} /> return () => <div class="a" {...{ class: 'b' }} />
}, },
}) })
expect(wrapper.classes().sort()).toEqual(['a', 'b'].sort()) expect(wrapper.classes().toSorted()).toEqual(['a', 'b'].toSorted())
}) })
test('Merge style', () => { test('Merge style', () => {
const propsA = { const propsA = {
style: { style: {
color: 'red', color: 'red',
} as CSSProperties, } satisfies CSSProperties,
} }
const propsB = { const propsB = {
style: { style: {
color: 'blue', color: 'blue',
width: '300px', width: '300px',
height: '300px', height: '300px',
} as CSSProperties, } satisfies CSSProperties,
} }
const wrapper = shallowMount({ const wrapper = shallowMount({
setup() { setup() {
@@ -369,7 +369,10 @@ describe('PatchFlags', () => {
setup() { setup() {
const foo = ref(0) const foo = ref(0)
return () => ( return () => (
<button value={`${foo.value}`} onClick={() => foo.value++}></button> <button
value={String(foo.value)}
onClick={() => foo.value++}
></button>
) )
}, },
}) })
@@ -397,7 +400,7 @@ describe('PatchFlags', () => {
await wrapper.trigger('click') await wrapper.trigger('click')
expect(wrapper.classes().sort()).toEqual(['b', 'static'].sort()) expect(wrapper.classes().toSorted()).toEqual(['b', 'static'].toSorted())
}) })
}) })
@@ -428,7 +431,7 @@ describe('variables outside slots', () => {
}, },
render() { render() {
const attrs = { const attrs = {
innerHTML: `${this.val}`, innerHTML: String(this.val),
} }
return ( return (
<A inc={this.inc}> <A inc={this.inc}>
@@ -466,7 +469,7 @@ describe('variables outside slots', () => {
}, },
render() { render() {
const attrs = { const attrs = {
innerHTML: `${this.val}`, innerHTML: String(this.val),
} }
const textarea = <textarea id="textarea" {...attrs} /> const textarea = <textarea id="textarea" {...attrs} />
return ( return (

View File

@@ -1,5 +1,5 @@
import { mount, shallowMount } from '@vue/test-utils' import { mount, shallowMount } from '@vue/test-utils'
import { type VNode, defineComponent } from 'vue' import { defineComponent, type VNode } from 'vue'
test('input[type="checkbox"] should work', async () => { test('input[type="checkbox"] should work', async () => {
const wrapper = shallowMount( const wrapper = shallowMount(

View File

@@ -2,38 +2,38 @@
"name": "@vue/babel-plugin-resolve-type", "name": "@vue/babel-plugin-resolve-type",
"version": "2.0.1", "version": "2.0.1",
"description": "Babel plugin for resolving Vue types.", "description": "Babel plugin for resolving Vue types.",
"author": "Kevin Deng <sxzz@sxzz.moe>",
"funding": "https://github.com/sponsors/sxzz",
"homepage": "https://github.com/vuejs/babel-plugin-jsx/tree/dev/packages/babel-plugin-resolve-type#readme",
"license": "MIT",
"type": "module", "type": "module",
"main": "./dist/index.mjs", "license": "MIT",
"module": "./dist/index.mjs", "homepage": "https://github.com/vuejs/babel-plugin-jsx/tree/dev/packages/babel-plugin-resolve-type#readme",
"types": "./dist/index.d.mts", "bugs": {
"exports": { "url": "https://github.com/vuejs/babel-plugin-jsx/issues"
".": {
"dev": "./src/index.ts",
"default": "./dist/index.mjs"
},
"./package.json": "./package.json"
},
"publishConfig": {
"exports": {
".": "./dist/index.mjs",
"./package.json": "./package.json"
}
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/vuejs/babel-plugin-jsx.git", "url": "git+https://github.com/vuejs/babel-plugin-jsx.git",
"directory": "packages/babel-plugin-resolve-type" "directory": "packages/babel-plugin-resolve-type"
}, },
"bugs": { "author": "Kevin Deng <sxzz@sxzz.moe>",
"url": "https://github.com/vuejs/babel-plugin-jsx/issues" "funding": "https://github.com/sponsors/sxzz",
},
"files": [ "files": [
"dist" "dist"
], ],
"main": "./dist/index.js",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"dev": "./src/index.ts",
"default": "./dist/index.js"
},
"./package.json": "./package.json"
},
"publishConfig": {
"exports": {
".": "./dist/index.js",
"./package.json": "./package.json"
}
},
"peerDependencies": { "peerDependencies": {
"@babel/core": "^7.0.0-0" "@babel/core": "^7.0.0-0"
}, },

View File

@@ -1,14 +1,14 @@
import type * as BabelCore from '@babel/core'
import { parseExpression } from '@babel/parser'
import {
type SimpleTypeResolveContext,
type SimpleTypeResolveOptions,
extractRuntimeEmits,
extractRuntimeProps,
} from '@vue/compiler-sfc'
import { codeFrameColumns } from '@babel/code-frame' import { codeFrameColumns } from '@babel/code-frame'
import { addNamed } from '@babel/helper-module-imports' import { addNamed } from '@babel/helper-module-imports'
import { declare } from '@babel/helper-plugin-utils' import { declare } from '@babel/helper-plugin-utils'
import { parseExpression } from '@babel/parser'
import {
extractRuntimeEmits,
extractRuntimeProps,
type SimpleTypeResolveContext,
type SimpleTypeResolveOptions,
} from '@vue/compiler-sfc'
import type * as BabelCore from '@babel/core'
export { SimpleTypeResolveOptions as Options } export { SimpleTypeResolveOptions as Options }
@@ -27,7 +27,7 @@ const plugin: (
const filename = file.opts.filename || 'unknown.js' const filename = file.opts.filename || 'unknown.js'
helpers = new Set() helpers = new Set()
ctx = { ctx = {
filename: filename, filename,
source: file.code, source: file.code,
options, options,
ast: file.ast.program.body, ast: file.ast.program.body,
@@ -75,12 +75,15 @@ const plugin: (
if (!t.isIdentifier(node.callee, { name: 'defineComponent' })) return if (!t.isIdentifier(node.callee, { name: 'defineComponent' })) return
if (!checkDefineComponent(path)) return if (!checkDefineComponent(path)) return
// eslint-disable-next-line baseline-js/use-baseline
const comp = node.arguments[0] const comp = node.arguments[0]
if (!comp || !t.isFunction(comp)) return if (!comp || !t.isFunction(comp)) return
// eslint-disable-next-line baseline-js/use-baseline
let options = node.arguments[1] let options = node.arguments[1]
if (!options) { if (!options) {
options = t.objectExpression([]) options = t.objectExpression([])
// eslint-disable-next-line baseline-js/use-baseline
node.arguments.push(options) node.arguments.push(options)
} }
@@ -91,9 +94,12 @@ const plugin: (
emitsGenerics = node.typeParameters.params[1] emitsGenerics = node.typeParameters.params[1]
} }
// eslint-disable-next-line baseline-js/use-baseline
node.arguments[1] = node.arguments[1] =
processProps(comp, propsGenerics, options) || options processProps(comp, propsGenerics, options) || options
// eslint-disable-next-line baseline-js/use-baseline
node.arguments[1] = node.arguments[1] =
// eslint-disable-next-line baseline-js/use-baseline
processEmits(comp, emitsGenerics, node.arguments[1]) || options processEmits(comp, emitsGenerics, node.arguments[1]) || options
}, },
VariableDeclarator(path) { VariableDeclarator(path) {
@@ -125,6 +131,7 @@ const plugin: (
if (args.length === 0) return if (args.length === 0) return
if (args.length === 1) { if (args.length === 1) {
// eslint-disable-next-line baseline-js/use-baseline
init.node.arguments.push(t.objectExpression([])) init.node.arguments.push(t.objectExpression([]))
} }
args[1] = addProperty(t, args[1], nameProperty) args[1] = addProperty(t, args[1], nameProperty)
@@ -148,12 +155,10 @@ const plugin: (
ctx!.propsTypeDecl = getTypeAnnotation(props.left) ctx!.propsTypeDecl = getTypeAnnotation(props.left)
} }
ctx!.propsRuntimeDefaults = props.right ctx!.propsRuntimeDefaults = props.right
} else if (generics) {
ctx!.propsTypeDecl = resolveTypeReference(generics)
} else { } else {
if (generics) { ctx!.propsTypeDecl = getTypeAnnotation(props)
ctx!.propsTypeDecl = resolveTypeReference(generics)
} else {
ctx!.propsTypeDecl = getTypeAnnotation(props)
}
} }
if (!ctx!.propsTypeDecl) return if (!ctx!.propsTypeDecl) return
@@ -305,7 +310,7 @@ function checkDefineComponent(
return ( return (
defineCompImport.type === 'ImportDeclaration' && defineCompImport.type === 'ImportDeclaration' &&
/^@?vue(\/|$)/.test(defineCompImport.source.value) /^@?vue(?:\/|$)/.test(defineCompImport.source.value)
) )
} }

View File

@@ -1,8 +1,8 @@
{ {
"name": "@vue/jsx-explorer", "name": "@vue/jsx-explorer",
"version": "2.0.1", "version": "2.0.1",
"type": "module",
"private": true, "private": true,
"type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "vite build", "build": "vite build",

View File

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

View File

@@ -1,13 +1,13 @@
import * as monaco from 'monaco-editor'
import { watchEffect } from 'vue'
import { transform } from '@babel/standalone'
import babelPluginJsx from '@vue/babel-plugin-jsx'
// @ts-expect-error missing types // @ts-expect-error missing types
import typescript from '@babel/plugin-syntax-typescript' import typescript from '@babel/plugin-syntax-typescript'
import { transform } from '@babel/standalone'
import babelPluginJsx from '@vue/babel-plugin-jsx'
import * as monaco from 'monaco-editor'
import { watchEffect } from 'vue'
import { import {
type VueJSXPluginOptions,
compilerOptions, compilerOptions,
initOptions, initOptions,
type VueJSXPluginOptions,
} from './options' } from './options'
import './editor.worker' import './editor.worker'
import './index.css' import './index.css'
@@ -53,10 +53,10 @@ function main() {
isolatedModules: true, isolatedModules: true,
}) })
const editor = monaco.editor.create(document.getElementById('source')!, { const editor = monaco.editor.create(document.querySelector('#source')!, {
...sharedEditorOptions, ...sharedEditorOptions,
model: monaco.editor.createModel( model: monaco.editor.createModel(
decodeURIComponent(window.location.hash.slice(1)) || decodeURIComponent(globalThis.location.hash.slice(1)) ||
persistedState.src || persistedState.src ||
`import { defineComponent } from 'vue' `import { defineComponent } from 'vue'
@@ -66,7 +66,7 @@ const App = defineComponent((props) => <div>Hello World</div>)`,
), ),
}) })
const output = monaco.editor.create(document.getElementById('output')!, { const output = monaco.editor.create(document.querySelector('#output')!, {
readOnly: true, readOnly: true,
...sharedEditorOptions, ...sharedEditorOptions,
model: monaco.editor.createModel( model: monaco.editor.createModel(
@@ -83,7 +83,7 @@ const App = defineComponent((props) => <div>Hello World</div>)`,
options: compilerOptions, options: compilerOptions,
}) })
localStorage.setItem('state', state) localStorage.setItem('state', state)
window.location.hash = encodeURIComponent(src) globalThis.location.hash = encodeURIComponent(src)
console.clear() console.clear()
try { try {
const res = transform(src, { const res = transform(src, {
@@ -94,11 +94,11 @@ const App = defineComponent((props) => <div>Hello World</div>)`,
], ],
ast: true, ast: true,
}) })
console.log('AST', res.ast!) console.info('AST', res.ast!)
output.setValue(res.code!) output.setValue(res.code!)
} catch (err: any) { } catch (error: any) {
console.error(err) console.error(error)
output.setValue(err.message!) output.setValue(error.message!)
} }
} }
@@ -116,12 +116,12 @@ const App = defineComponent((props) => <div>Hello World</div>)`,
} }
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 let prevTimer: ReturnType<typeof setTimeout> | null = null
return ((...args: any[]) => { return ((...args: any[]) => {
if (prevTimer) { if (prevTimer) {
clearTimeout(prevTimer) clearTimeout(prevTimer)
} }
prevTimer = window.setTimeout(() => { prevTimer = globalThis.setTimeout(() => {
fn(...args) fn(...args)
prevTimer = null prevTimer = null
}, delay) }, delay)

View File

@@ -1,5 +1,5 @@
import { createApp, defineComponent, reactive } from 'vue' import { createApp, defineComponent, reactive } from 'vue'
import { type VueJSXPluginOptions } from '@vue/babel-plugin-jsx' import type { VueJSXPluginOptions } from '@vue/babel-plugin-jsx'
export { VueJSXPluginOptions } export { VueJSXPluginOptions }
@@ -107,5 +107,5 @@ const App = defineComponent({
}) })
export function initOptions() { export function initOptions() {
createApp(App).mount(document.getElementById('header')!) createApp(App).mount(document.querySelector('#header')!)
} }

View File

@@ -1,5 +1,5 @@
import { defineConfig } from 'vite'
import VueJSX from '@vitejs/plugin-vue-jsx' import VueJSX from '@vitejs/plugin-vue-jsx'
import { defineConfig } from 'vite'
export default defineConfig({ export default defineConfig({
resolve: { resolve: {

2238
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +1,19 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "jsx": "preserve",
"jsxImportSource": "vue",
"lib": ["ES2023", "DOM", "DOM.Iterable"], "lib": ["ES2023", "DOM", "DOM.Iterable"],
"customConditions": ["dev"],
"module": "ESNext",
"moduleResolution": "bundler", "moduleResolution": "bundler",
"resolveJsonModule": true,
"types": ["vitest/globals"],
"allowJs": true, "allowJs": true,
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"jsx": "preserve",
"jsxImportSource": "vue",
"types": ["vitest/globals"],
"skipLibCheck": true,
"noEmit": true, "noEmit": true,
"customConditions": ["dev"] "esModuleInterop": true,
"skipLibCheck": true
} }
} }

View File

@@ -13,5 +13,5 @@ export default defineConfig({
exports: { exports: {
devExports: 'dev', devExports: 'dev',
}, },
fixedExtension: true, publint: 'ci-only',
}) })

View File

@@ -1,5 +1,5 @@
import { defineConfig } from 'vitest/config'
import { babel } from '@rollup/plugin-babel' import { babel } from '@rollup/plugin-babel'
import { defineConfig } from 'vitest/config'
import Jsx from './packages/babel-plugin-jsx/src' import Jsx from './packages/babel-plugin-jsx/src'
export default defineConfig({ export default defineConfig({
@@ -16,7 +16,10 @@ export default defineConfig({
plugins: [ plugins: [
[ [
Jsx, Jsx,
{ optimize: true, isCustomElement: (tag: string) => /^x-/.test(tag) }, {
optimize: true,
isCustomElement: (tag: string) => tag.startsWith('x-'),
},
], ],
], ],
}), }),