mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into minor
This commit is contained in:
commit
1d8727ec97
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
Messages must be matched by the following regex:
|
Messages must be matched by the following regex:
|
||||||
|
|
||||||
``` js
|
```regexp
|
||||||
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/
|
/^(revert: )?(feat|fix|docs|dx|style|refactor|perf|test|workflow|build|ci|chore|types|wip)(\(.+\))?: .{1,50}/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
@ -41,13 +41,3 @@ jobs:
|
||||||
with:
|
with:
|
||||||
name: size-data
|
name: size-data
|
||||||
path: temp/size
|
path: temp/size
|
||||||
|
|
||||||
- name: Save PR number
|
|
||||||
if: ${{github.event_name == 'pull_request'}}
|
|
||||||
run: echo ${{ github.event.number }} > ./pr.txt
|
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
|
||||||
if: ${{github.event_name == 'pull_request'}}
|
|
||||||
with:
|
|
||||||
name: pr-number
|
|
||||||
path: pr.txt
|
|
||||||
|
|
|
@ -35,18 +35,6 @@ jobs:
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download PR number
|
|
||||||
uses: dawidd6/action-download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: pr-number
|
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
|
||||||
|
|
||||||
- name: Read PR Number
|
|
||||||
id: pr-number
|
|
||||||
uses: juliangruber/read-file-action@v1
|
|
||||||
with:
|
|
||||||
path: ./pr.txt
|
|
||||||
|
|
||||||
- name: Download Size Data
|
- name: Download Size Data
|
||||||
uses: dawidd6/action-download-artifact@v3
|
uses: dawidd6/action-download-artifact@v3
|
||||||
with:
|
with:
|
||||||
|
@ -77,7 +65,6 @@ jobs:
|
||||||
uses: actions-cool/maintain-one-comment@v3
|
uses: actions-cool/maintain-one-comment@v3
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
number: ${{ steps.pr-number.outputs.content }}
|
|
||||||
body: |
|
body: |
|
||||||
${{ steps.size-report.outputs.content }}
|
${{ steps.size-report.outputs.content }}
|
||||||
<!-- VUE_CORE_SIZE -->
|
<!-- VUE_CORE_SIZE -->
|
||||||
|
|
13
CHANGELOG.md
13
CHANGELOG.md
|
@ -1,3 +1,16 @@
|
||||||
|
## [3.4.27](https://github.com/vuejs/core/compare/v3.4.26...v3.4.27) (2024-05-06)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compat:** include legacy scoped slots ([#10868](https://github.com/vuejs/core/issues/10868)) ([8366126](https://github.com/vuejs/core/commit/83661264a4ced3cb2ff6800904a86dd9e82bbfe2)), closes [#8869](https://github.com/vuejs/core/issues/8869)
|
||||||
|
* **compiler-core:** add support for arrow aysnc function with unbracketed ([#5789](https://github.com/vuejs/core/issues/5789)) ([ca7d421](https://github.com/vuejs/core/commit/ca7d421e8775f6813f8943d32ab485e0c542f98b)), closes [#5788](https://github.com/vuejs/core/issues/5788)
|
||||||
|
* **compiler-dom:** restrict createStaticVNode usage with option elements ([#10846](https://github.com/vuejs/core/issues/10846)) ([0e3d617](https://github.com/vuejs/core/commit/0e3d6178b02d0386d779720ae2cc4eac1d1ec990)), closes [#6568](https://github.com/vuejs/core/issues/6568) [#7434](https://github.com/vuejs/core/issues/7434)
|
||||||
|
* **compiler-sfc:** handle keyof operator ([#10874](https://github.com/vuejs/core/issues/10874)) ([10d34a5](https://github.com/vuejs/core/commit/10d34a5624775f20437ccad074a97270ef74c3fb)), closes [#10871](https://github.com/vuejs/core/issues/10871)
|
||||||
|
* **hydration:** handle edge case of style mismatch without style attribute ([f2c1412](https://github.com/vuejs/core/commit/f2c1412e46a8fad3e13403bfa78335c4f704f21c)), closes [#10786](https://github.com/vuejs/core/issues/10786)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.5.0-alpha.2](https://github.com/vuejs/core/compare/v3.4.26...v3.5.0-alpha.2) (2024-05-04)
|
# [3.5.0-alpha.2](https://github.com/vuejs/core/compare/v3.4.26...v3.5.0-alpha.2) (2024-05-04)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,12 @@ export default tseslint.config(
|
||||||
message:
|
message:
|
||||||
'Our output target is ES2016, so async/await syntax should be avoided.',
|
'Our output target is ES2016, so async/await syntax should be avoided.',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
selector: 'ChainExpression',
|
||||||
|
message:
|
||||||
|
'Our output target is ES2016, and optional chaining results in ' +
|
||||||
|
'verbose helpers and should be avoided.',
|
||||||
|
},
|
||||||
],
|
],
|
||||||
'sort-imports': ['error', { ignoreDeclarationSort: true }],
|
'sort-imports': ['error', { ignoreDeclarationSort: true }],
|
||||||
|
|
||||||
|
@ -134,7 +140,7 @@ export default tseslint.config(
|
||||||
{
|
{
|
||||||
files: [
|
files: [
|
||||||
'eslint.config.js',
|
'eslint.config.js',
|
||||||
'rollup.config.js',
|
'rollup*.config.js',
|
||||||
'scripts/**',
|
'scripts/**',
|
||||||
'./*.{js,ts}',
|
'./*.{js,ts}',
|
||||||
'packages/*/*.js',
|
'packages/*/*.js',
|
||||||
|
|
40
package.json
40
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.0-alpha.2",
|
"version": "3.5.0-alpha.2",
|
||||||
"packageManager": "pnpm@9.0.6",
|
"packageManager": "pnpm@9.1.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -59,58 +59,58 @@
|
||||||
"node": ">=18.12.0"
|
"node": ">=18.12.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/parser": "^7.24.4",
|
"@babel/parser": "^7.24.6",
|
||||||
"@babel/types": "^7.24.0",
|
"@babel/types": "^7.24.6",
|
||||||
"@codspeed/vitest-plugin": "^3.1.0",
|
"@codspeed/vitest-plugin": "^3.1.0",
|
||||||
"@rollup/plugin-alias": "^5.1.0",
|
"@rollup/plugin-alias": "^5.1.0",
|
||||||
"@rollup/plugin-commonjs": "^25.0.7",
|
"@rollup/plugin-commonjs": "^25.0.8",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@rollup/plugin-terser": "^0.4.4",
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/minimist": "^1.2.5",
|
"@types/minimist": "^1.2.5",
|
||||||
"@types/node": "^20.12.7",
|
"@types/node": "^20.12.12",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@vitest/coverage-istanbul": "^1.5.2",
|
"@vitest/coverage-istanbul": "^1.5.2",
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^4.1.0",
|
"conventional-changelog-cli": "^4.1.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.20.2",
|
"esbuild": "^0.21.4",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.1.1",
|
"eslint": "^9.3.0",
|
||||||
"eslint-plugin-import-x": "^0.5.0",
|
"eslint-plugin-import-x": "^0.5.1",
|
||||||
"eslint-plugin-vitest": "^0.5.4",
|
"eslint-plugin-vitest": "^0.5.4",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"execa": "^8.0.1",
|
"execa": "^8.0.1",
|
||||||
"jsdom": "^24.0.0",
|
"jsdom": "^24.0.0",
|
||||||
"lint-staged": "^15.2.2",
|
"lint-staged": "^15.2.5",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"magic-string": "^0.30.10",
|
"magic-string": "^0.30.10",
|
||||||
"markdown-table": "^3.0.3",
|
"markdown-table": "^3.0.3",
|
||||||
"marked": "^12.0.2",
|
"marked": "^12.0.2",
|
||||||
"minimist": "^1.2.8",
|
"minimist": "^1.2.8",
|
||||||
"npm-run-all2": "^6.1.2",
|
"npm-run-all2": "^6.2.0",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.1",
|
||||||
"prettier": "^3.2.5",
|
"prettier": "^3.2.5",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~22.7.1",
|
"puppeteer": "~22.7.1",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.7",
|
||||||
"rollup": "^4.17.1",
|
"rollup": "^4.18.0",
|
||||||
"rollup-plugin-dts": "^6.1.0",
|
"rollup-plugin-dts": "^6.1.1",
|
||||||
"rollup-plugin-esbuild": "^6.1.1",
|
"rollup-plugin-esbuild": "^6.1.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
"semver": "^7.6.0",
|
"semver": "^7.6.2",
|
||||||
"serve": "^14.2.3",
|
"serve": "^14.2.3",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"terser": "^5.30.4",
|
"terser": "^5.31.0",
|
||||||
"todomvc-app-css": "^2.4.3",
|
"todomvc-app-css": "^2.4.3",
|
||||||
"tslib": "^2.6.2",
|
"tslib": "^2.6.2",
|
||||||
"tsx": "^4.7.3",
|
"tsx": "^4.11.0",
|
||||||
"typescript": "~5.4.5",
|
"typescript": "~5.4.5",
|
||||||
"typescript-eslint": "^7.7.1",
|
"typescript-eslint": "^7.10.0",
|
||||||
"vite": "^5.2.10",
|
"vite": "^5.2.11",
|
||||||
"vitest": "^1.5.2"
|
"vitest": "^1.5.2"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
|
|
|
@ -1284,6 +1284,18 @@ describe('compiler: element transform', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('<math> should be forced into blocks', () => {
|
||||||
|
const ast = parse(`<div><math/></div>`)
|
||||||
|
transform(ast, {
|
||||||
|
nodeTransforms: [transformElement],
|
||||||
|
})
|
||||||
|
expect((ast as any).children[0].children[0].codegenNode).toMatchObject({
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
tag: `"math"`,
|
||||||
|
isBlock: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('force block for runtime custom directive w/ children', () => {
|
test('force block for runtime custom directive w/ children', () => {
|
||||||
const { node } = parseWithElementTransform(`<div v-foo>hello</div>`)
|
const { node } = parseWithElementTransform(`<div v-foo>hello</div>`)
|
||||||
expect(node.isBlock).toBe(true)
|
expect(node.isBlock).toBe(true)
|
||||||
|
|
|
@ -286,6 +286,23 @@ describe('compiler: transform v-on', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should NOT wrap as function if expression is already function expression (async)', () => {
|
||||||
|
const { node } = parseWithVOn(
|
||||||
|
`<div @click="async $event => await foo($event)"/>`,
|
||||||
|
)
|
||||||
|
expect((node.codegenNode as VNodeCall).props).toMatchObject({
|
||||||
|
properties: [
|
||||||
|
{
|
||||||
|
key: { content: `onClick` },
|
||||||
|
value: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: `async $event => await foo($event)`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('should NOT wrap as function if expression is already function expression (with newlines)', () => {
|
test('should NOT wrap as function if expression is already function expression (with newlines)', () => {
|
||||||
const { node } = parseWithVOn(
|
const { node } = parseWithVOn(
|
||||||
`<div @click="
|
`<div @click="
|
||||||
|
@ -630,6 +647,39 @@ describe('compiler: transform v-on', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('inline async arrow function with no bracket expression handler', () => {
|
||||||
|
const { root, node } = parseWithVOn(
|
||||||
|
`<div v-on:click="async e => await foo(e)" />`,
|
||||||
|
{
|
||||||
|
prefixIdentifiers: true,
|
||||||
|
cacheHandlers: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(root.cached).toBe(1)
|
||||||
|
const vnodeCall = node.codegenNode as VNodeCall
|
||||||
|
// should not treat cached handler as dynamicProp, so no flags
|
||||||
|
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||||
|
expect(
|
||||||
|
(vnodeCall.props as ObjectExpression).properties[0].value,
|
||||||
|
).toMatchObject({
|
||||||
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
|
index: 0,
|
||||||
|
value: {
|
||||||
|
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||||
|
children: [
|
||||||
|
`async `,
|
||||||
|
{ content: `e` },
|
||||||
|
` => await `,
|
||||||
|
{ content: `_ctx.foo` },
|
||||||
|
`(`,
|
||||||
|
{ content: `e` },
|
||||||
|
`)`,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('inline async function expression handler', () => {
|
test('inline async function expression handler', () => {
|
||||||
const { root, node } = parseWithVOn(
|
const { root, node } = parseWithVOn(
|
||||||
`<div v-on:click="async function () { await foo() } " />`,
|
`<div v-on:click="async function () { await foo() } " />`,
|
||||||
|
|
|
@ -46,13 +46,13 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.24.4",
|
"@babel/parser": "^7.24.6",
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"entities": "^4.5.0",
|
"entities": "^4.5.0",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.24.0"
|
"@babel/types": "^7.24.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -571,7 +571,7 @@ export interface ForRenderListExpression extends CallExpression {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ForIteratorExpression extends FunctionExpression {
|
export interface ForIteratorExpression extends FunctionExpression {
|
||||||
returns: BlockCodegenNode
|
returns?: BlockCodegenNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// AST Utilities ---------------------------------------------------------------
|
// AST Utilities ---------------------------------------------------------------
|
||||||
|
|
|
@ -53,6 +53,7 @@ export function walkIdentifiers(
|
||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
node.type === 'ObjectProperty' &&
|
node.type === 'ObjectProperty' &&
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
parent?.type === 'ObjectPattern'
|
parent?.type === 'ObjectPattern'
|
||||||
) {
|
) {
|
||||||
// mark property in destructure pattern
|
// mark property in destructure pattern
|
||||||
|
@ -407,6 +408,7 @@ function isReferenced(node: Node, parent: Node, grandparent?: Node): boolean {
|
||||||
// no: export { NODE as foo } from "foo";
|
// no: export { NODE as foo } from "foo";
|
||||||
case 'ExportSpecifier':
|
case 'ExportSpecifier':
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
if (grandparent?.source) {
|
if (grandparent?.source) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -168,6 +168,8 @@ function parseFilter(node: SimpleExpressionNode, context: TransformContext) {
|
||||||
expression = wrapFilter(expression, filters[i], context)
|
expression = wrapFilter(expression, filters[i], context)
|
||||||
}
|
}
|
||||||
node.content = expression
|
node.content = expression
|
||||||
|
// reset ast since the content is replaced
|
||||||
|
node.ast = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -174,7 +174,8 @@ export function getConstantType(
|
||||||
if (
|
if (
|
||||||
codegenNode.isBlock &&
|
codegenNode.isBlock &&
|
||||||
node.tag !== 'svg' &&
|
node.tag !== 'svg' &&
|
||||||
node.tag !== 'foreignObject'
|
node.tag !== 'foreignObject' &&
|
||||||
|
node.tag !== 'math'
|
||||||
) {
|
) {
|
||||||
return ConstantTypes.NOT_CONSTANT
|
return ConstantTypes.NOT_CONSTANT
|
||||||
}
|
}
|
||||||
|
|
|
@ -117,7 +117,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
||||||
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
// updates inside get proper isSVG flag at runtime. (#639, #643)
|
||||||
// This is technically web-specific, but splitting the logic out of core
|
// This is technically web-specific, but splitting the logic out of core
|
||||||
// leads to too much unnecessary complexity.
|
// leads to too much unnecessary complexity.
|
||||||
(tag === 'svg' || tag === 'foreignObject'))
|
(tag === 'svg' || tag === 'foreignObject' || tag === 'math'))
|
||||||
|
|
||||||
// props
|
// props
|
||||||
if (props.length > 0) {
|
if (props.length > 0) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { hasScopeRef, isMemberExpression } from '../utils'
|
||||||
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
||||||
|
|
||||||
const fnExpRE =
|
const fnExpRE =
|
||||||
/^\s*([\w$_]+|(async\s*)?\([^)]*?\))\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
/^\s*(async\s*)?(\([^)]*?\)|[\w$_]+)\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||||
|
|
||||||
export interface VOnDirectiveNode extends DirectiveNode {
|
export interface VOnDirectiveNode extends DirectiveNode {
|
||||||
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
|
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
|
||||||
|
|
|
@ -1,5 +1,24 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
|
||||||
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
const _hoisted_1 = /*#__PURE__*/_createElementVNode("select", null, [
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||||
|
/*#__PURE__*/_createElementVNode("option", { value: 1 })
|
||||||
|
], -1 /* HOISTED */)
|
||||||
|
const _hoisted_2 = [
|
||||||
|
_hoisted_1
|
||||||
|
]
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
|
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
@ -20,6 +39,19 @@ return function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`stringify static html > should work for <option> elements with string values 1`] = `
|
||||||
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||||
|
const _hoisted_2 = [
|
||||||
|
_hoisted_1
|
||||||
|
]
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
|
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
|
|
@ -485,4 +485,51 @@ describe('stringify static html', () => {
|
||||||
expect(code).toMatch(`<code>text1</code>`)
|
expect(code).toMatch(`<code>text1</code>`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should work for <option> elements with string values', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><select>${repeat(
|
||||||
|
`<option value="1" />`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</select></div>`,
|
||||||
|
)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||||
|
callee: CREATE_STATIC,
|
||||||
|
arguments: [
|
||||||
|
JSON.stringify(
|
||||||
|
`<select>${repeat(
|
||||||
|
`<option value="1"></option>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</select>`,
|
||||||
|
),
|
||||||
|
'1',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should bail for <option> elements with number values', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><select>${repeat(
|
||||||
|
`<option :value="1" />`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</select></div>`,
|
||||||
|
)
|
||||||
|
expect(ast.hoists).toMatchObject([
|
||||||
|
{
|
||||||
|
type: NodeTypes.VNODE_CALL,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
type TextCallNode,
|
type TextCallNode,
|
||||||
type TransformContext,
|
type TransformContext,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
|
isStaticArgOf,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-core'
|
||||||
import {
|
import {
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
|
@ -200,6 +201,7 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
||||||
// probably only need to check for most common case
|
// probably only need to check for most common case
|
||||||
// i.e. non-phrasing-content tags inside `<p>`
|
// i.e. non-phrasing-content tags inside `<p>`
|
||||||
function walk(node: ElementNode): boolean {
|
function walk(node: ElementNode): boolean {
|
||||||
|
const isOptionTag = node.tag === 'option' && node.ns === Namespaces.HTML
|
||||||
for (let i = 0; i < node.props.length; i++) {
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
const p = node.props[i]
|
const p = node.props[i]
|
||||||
// bail on non-attr bindings
|
// bail on non-attr bindings
|
||||||
|
@ -225,6 +227,16 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
||||||
) {
|
) {
|
||||||
return bail()
|
return bail()
|
||||||
}
|
}
|
||||||
|
// <option :value="1"> cannot be safely stringified
|
||||||
|
if (
|
||||||
|
isOptionTag &&
|
||||||
|
isStaticArgOf(p.arg, 'value') &&
|
||||||
|
p.exp &&
|
||||||
|
p.exp.ast &&
|
||||||
|
p.exp.ast.type !== 'StringLiteral'
|
||||||
|
) {
|
||||||
|
return bail()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < node.children.length; i++) {
|
for (let i = 0; i < node.children.length; i++) {
|
||||||
|
|
|
@ -304,3 +304,19 @@ return () => {}
|
||||||
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`sfc reactive props destructure > rest spread non-inline 1`] = `
|
||||||
|
"import { createPropsRestProxy as _createPropsRestProxy } from 'vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['foo', 'bar'],
|
||||||
|
setup(__props, { expose: __expose }) {
|
||||||
|
__expose();
|
||||||
|
|
||||||
|
const rest = _createPropsRestProxy(__props, ["foo"])
|
||||||
|
|
||||||
|
return { rest }
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
|
@ -264,6 +264,27 @@ describe('sfc reactive props destructure', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('rest spread non-inline', () => {
|
||||||
|
const { content, bindings } = compile(
|
||||||
|
`
|
||||||
|
<script setup>
|
||||||
|
const { foo, ...rest } = defineProps(['foo', 'bar'])
|
||||||
|
</script>
|
||||||
|
<template>{{ rest.bar }}</template>
|
||||||
|
`,
|
||||||
|
{ inlineTemplate: false },
|
||||||
|
)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`const rest = _createPropsRestProxy(__props, ["foo"])`,
|
||||||
|
)
|
||||||
|
assertCode(content)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
foo: BindingTypes.PROPS,
|
||||||
|
bar: BindingTypes.PROPS,
|
||||||
|
rest: BindingTypes.SETUP_REACTIVE_CONST,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// #6960
|
// #6960
|
||||||
test('computed static key', () => {
|
test('computed static key', () => {
|
||||||
const { content, bindings } = compile(`
|
const { content, bindings } = compile(`
|
||||||
|
|
|
@ -447,6 +447,42 @@ describe('resolveType', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('keyof', () => {
|
||||||
|
const files = {
|
||||||
|
'/foo.ts': `export type IMP = { ${1}: 1 };`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const { props } = resolve(
|
||||||
|
`
|
||||||
|
import { IMP } from './foo'
|
||||||
|
interface Foo { foo: 1, ${1}: 1 }
|
||||||
|
type Bar = { bar: 1 }
|
||||||
|
declare const obj: Bar
|
||||||
|
declare const set: Set<any>
|
||||||
|
declare const arr: Array<any>
|
||||||
|
|
||||||
|
defineProps<{
|
||||||
|
imp: keyof IMP,
|
||||||
|
foo: keyof Foo,
|
||||||
|
bar: keyof Bar,
|
||||||
|
obj: keyof typeof obj,
|
||||||
|
set: keyof typeof set,
|
||||||
|
arr: keyof typeof arr
|
||||||
|
}>()
|
||||||
|
`,
|
||||||
|
files,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(props).toStrictEqual({
|
||||||
|
imp: ['Number'],
|
||||||
|
foo: ['String', 'Number'],
|
||||||
|
bar: ['String'],
|
||||||
|
obj: ['String'],
|
||||||
|
set: ['String'],
|
||||||
|
arr: ['String', 'Number'],
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('ExtractPropTypes (element-plus)', () => {
|
test('ExtractPropTypes (element-plus)', () => {
|
||||||
const { props, raw } = resolve(
|
const { props, raw } = resolve(
|
||||||
`
|
`
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
||||||
|
import { parse as babelParse } from '@babel/parser'
|
||||||
import {
|
import {
|
||||||
type SFCTemplateCompileOptions,
|
type SFCTemplateCompileOptions,
|
||||||
compileTemplate,
|
compileTemplate,
|
||||||
|
@ -452,6 +453,36 @@ test('prefixing edge case for reused AST ssr mode', () => {
|
||||||
).not.toThrowError()
|
).not.toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10852
|
||||||
|
test('non-identifier expression in legacy filter syntax', () => {
|
||||||
|
const src = `
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
Today is
|
||||||
|
{{ new Date() | formatDate }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
`
|
||||||
|
|
||||||
|
const { descriptor } = parse(src)
|
||||||
|
const compilationResult = compileTemplate({
|
||||||
|
id: 'xxx',
|
||||||
|
filename: 'test.vue',
|
||||||
|
ast: descriptor.template!.ast,
|
||||||
|
source: descriptor.template!.content,
|
||||||
|
ssr: false,
|
||||||
|
compilerOptions: {
|
||||||
|
compatConfig: {
|
||||||
|
MODE: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(() => {
|
||||||
|
babelParse(compilationResult.code, { sourceType: 'module' })
|
||||||
|
}).not.toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
interface Pos {
|
interface Pos {
|
||||||
line: number
|
line: number
|
||||||
column: number
|
column: number
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.24.4",
|
"@babel/parser": "^7.24.6",
|
||||||
"@vue/compiler-core": "workspace:*",
|
"@vue/compiler-core": "workspace:*",
|
||||||
"@vue/compiler-dom": "workspace:*",
|
"@vue/compiler-dom": "workspace:*",
|
||||||
"@vue/compiler-ssr": "workspace:*",
|
"@vue/compiler-ssr": "workspace:*",
|
||||||
|
@ -53,15 +53,15 @@
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.24.0",
|
"@babel/types": "^7.24.6",
|
||||||
"@vue/consolidate": "^1.0.0",
|
"@vue/consolidate": "^1.0.0",
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "10.1.0",
|
"lru-cache": "10.1.0",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "^9.0.4",
|
"minimatch": "^9.0.4",
|
||||||
"postcss-modules": "^6.0.0",
|
"postcss-modules": "^6.0.0",
|
||||||
"postcss-selector-parser": "^6.0.16",
|
"postcss-selector-parser": "^6.1.0",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.75.0"
|
"sass": "^1.77.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -524,8 +524,14 @@ export function compileScript(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// defineProps / defineEmits
|
// defineProps
|
||||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
||||||
|
if (ctx.propsDestructureRestId) {
|
||||||
|
setupBindings[ctx.propsDestructureRestId] =
|
||||||
|
BindingTypes.SETUP_REACTIVE_CONST
|
||||||
|
}
|
||||||
|
|
||||||
|
// defineEmits
|
||||||
const isDefineEmits =
|
const isDefineEmits =
|
||||||
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
||||||
!isDefineEmits &&
|
!isDefineEmits &&
|
||||||
|
|
|
@ -37,10 +37,23 @@ export function processDefineOptions(
|
||||||
(prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
|
(prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&
|
||||||
prop.key.type === 'Identifier'
|
prop.key.type === 'Identifier'
|
||||||
) {
|
) {
|
||||||
if (prop.key.name === 'props') propsOption = prop
|
switch (prop.key.name) {
|
||||||
if (prop.key.name === 'emits') emitsOption = prop
|
case 'props':
|
||||||
if (prop.key.name === 'expose') exposeOption = prop
|
propsOption = prop
|
||||||
if (prop.key.name === 'slots') slotsOption = prop
|
break
|
||||||
|
|
||||||
|
case 'emits':
|
||||||
|
emitsOption = prop
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'expose':
|
||||||
|
exposeOption = prop
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'slots':
|
||||||
|
slotsOption = prop
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1448,6 +1448,7 @@ export function inferRuntimeType(
|
||||||
ctx: TypeResolveContext,
|
ctx: TypeResolveContext,
|
||||||
node: Node & MaybeWithScope,
|
node: Node & MaybeWithScope,
|
||||||
scope = node._ownerScope || ctxToScope(ctx),
|
scope = node._ownerScope || ctxToScope(ctx),
|
||||||
|
isKeyOf = false,
|
||||||
): string[] {
|
): string[] {
|
||||||
try {
|
try {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
@ -1467,8 +1468,18 @@ export function inferRuntimeType(
|
||||||
const types = new Set<string>()
|
const types = new Set<string>()
|
||||||
const members =
|
const members =
|
||||||
node.type === 'TSTypeLiteral' ? node.members : node.body.body
|
node.type === 'TSTypeLiteral' ? node.members : node.body.body
|
||||||
|
|
||||||
for (const m of members) {
|
for (const m of members) {
|
||||||
|
if (isKeyOf) {
|
||||||
if (
|
if (
|
||||||
|
m.type === 'TSPropertySignature' &&
|
||||||
|
m.key.type === 'NumericLiteral'
|
||||||
|
) {
|
||||||
|
types.add('Number')
|
||||||
|
} else {
|
||||||
|
types.add('String')
|
||||||
|
}
|
||||||
|
} else if (
|
||||||
m.type === 'TSCallSignatureDeclaration' ||
|
m.type === 'TSCallSignatureDeclaration' ||
|
||||||
m.type === 'TSConstructSignatureDeclaration'
|
m.type === 'TSConstructSignatureDeclaration'
|
||||||
) {
|
) {
|
||||||
|
@ -1477,6 +1488,7 @@ export function inferRuntimeType(
|
||||||
types.add('Object')
|
types.add('Object')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return types.size ? Array.from(types) : ['Object']
|
return types.size ? Array.from(types) : ['Object']
|
||||||
}
|
}
|
||||||
case 'TSPropertySignature':
|
case 'TSPropertySignature':
|
||||||
|
@ -1512,9 +1524,22 @@ export function inferRuntimeType(
|
||||||
case 'TSTypeReference': {
|
case 'TSTypeReference': {
|
||||||
const resolved = resolveTypeReference(ctx, node, scope)
|
const resolved = resolveTypeReference(ctx, node, scope)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope)
|
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.typeName.type === 'Identifier') {
|
if (node.typeName.type === 'Identifier') {
|
||||||
|
if (isKeyOf) {
|
||||||
|
switch (node.typeName.name) {
|
||||||
|
case 'String':
|
||||||
|
case 'Array':
|
||||||
|
case 'ArrayLike':
|
||||||
|
case 'ReadonlyArray':
|
||||||
|
return ['String', 'Number']
|
||||||
|
default:
|
||||||
|
return ['String']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch (node.typeName.name) {
|
switch (node.typeName.name) {
|
||||||
case 'Array':
|
case 'Array':
|
||||||
case 'Function':
|
case 'Function':
|
||||||
|
@ -1634,7 +1659,7 @@ export function inferRuntimeType(
|
||||||
// typeof only support identifier in local scope
|
// typeof only support identifier in local scope
|
||||||
const matched = scope.declares[id.name]
|
const matched = scope.declares[id.name]
|
||||||
if (matched) {
|
if (matched) {
|
||||||
return inferRuntimeType(ctx, matched, matched._ownerScope)
|
return inferRuntimeType(ctx, matched, matched._ownerScope, isKeyOf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
@ -1642,7 +1667,12 @@ export function inferRuntimeType(
|
||||||
|
|
||||||
// e.g. readonly
|
// e.g. readonly
|
||||||
case 'TSTypeOperator': {
|
case 'TSTypeOperator': {
|
||||||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
return inferRuntimeType(
|
||||||
|
ctx,
|
||||||
|
node.typeAnnotation,
|
||||||
|
scope,
|
||||||
|
node.operator === 'keyof',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
|
@ -1501,8 +1501,7 @@ describe('should work when props type is incompatible with setup returned type '
|
||||||
|
|
||||||
describe('withKeys and withModifiers as pro', () => {
|
describe('withKeys and withModifiers as pro', () => {
|
||||||
const onKeydown = withKeys(e => {}, [''])
|
const onKeydown = withKeys(e => {}, [''])
|
||||||
// @ts-expect-error invalid modifiers
|
const onClick = withModifiers(e => {}, [])
|
||||||
const onClick = withModifiers(e => {}, [''])
|
|
||||||
;<input onKeydown={onKeydown} onClick={onClick} />
|
;<input onKeydown={onKeydown} onClick={onClick} />
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -12,10 +12,13 @@ const source = ref('foo')
|
||||||
const source2 = computed(() => source.value)
|
const source2 = computed(() => source.value)
|
||||||
const source3 = () => 1
|
const source3 = () => 1
|
||||||
|
|
||||||
|
type OnCleanup = (fn: () => void) => void
|
||||||
|
|
||||||
// lazy watcher will have consistent types for oldValue.
|
// lazy watcher will have consistent types for oldValue.
|
||||||
watch(source, (value, oldValue) => {
|
watch(source, (value, oldValue, onCleanup) => {
|
||||||
expectType<string>(value)
|
expectType<string>(value)
|
||||||
expectType<string>(oldValue)
|
expectType<string>(oldValue)
|
||||||
|
expectType<OnCleanup>(onCleanup)
|
||||||
})
|
})
|
||||||
|
|
||||||
watch([source, source2, source3], (values, oldValues) => {
|
watch([source, source2, source3], (values, oldValues) => {
|
||||||
|
@ -92,9 +95,10 @@ defineComponent({
|
||||||
created() {
|
created() {
|
||||||
this.$watch(
|
this.$watch(
|
||||||
() => this.a,
|
() => this.a,
|
||||||
(v, ov) => {
|
(v, ov, onCleanup) => {
|
||||||
expectType<number>(v)
|
expectType<number>(v)
|
||||||
expectType<number>(ov)
|
expectType<number>(ov)
|
||||||
|
expectType<OnCleanup>(onCleanup)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
ref,
|
ref,
|
||||||
toRef,
|
toRef,
|
||||||
toRefs,
|
toRefs,
|
||||||
|
toValue,
|
||||||
} from '../src/index'
|
} from '../src/index'
|
||||||
import { computed } from '@vue/runtime-dom'
|
import { computed } from '@vue/runtime-dom'
|
||||||
import { customRef, shallowRef, triggerRef, unref } from '../src/ref'
|
import { customRef, shallowRef, triggerRef, unref } from '../src/ref'
|
||||||
|
@ -251,6 +252,18 @@ describe('reactivity/ref', () => {
|
||||||
x: 1,
|
x: 1,
|
||||||
})
|
})
|
||||||
const x = toRef(a, 'x')
|
const x = toRef(a, 'x')
|
||||||
|
|
||||||
|
const b = ref({ y: 1 })
|
||||||
|
|
||||||
|
const c = toRef(b)
|
||||||
|
|
||||||
|
const d = toRef({ z: 1 })
|
||||||
|
|
||||||
|
expect(isRef(d)).toBe(true)
|
||||||
|
expect(d.value.z).toBe(1)
|
||||||
|
|
||||||
|
expect(c).toBe(b)
|
||||||
|
|
||||||
expect(isRef(x)).toBe(true)
|
expect(isRef(x)).toBe(true)
|
||||||
expect(x.value).toBe(1)
|
expect(x.value).toBe(1)
|
||||||
|
|
||||||
|
@ -453,4 +466,16 @@ describe('reactivity/ref', () => {
|
||||||
r.value = obj
|
r.value = obj
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('toValue', () => {
|
||||||
|
const a = ref(1)
|
||||||
|
const b = computed(() => a.value + 1)
|
||||||
|
const c = () => a.value + 2
|
||||||
|
const d = 4
|
||||||
|
|
||||||
|
expect(toValue(a)).toBe(1)
|
||||||
|
expect(toValue(b)).toBe(2)
|
||||||
|
expect(toValue(c)).toBe(3)
|
||||||
|
expect(toValue(d)).toBe(4)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -248,7 +248,11 @@ function createReactiveObject(
|
||||||
) {
|
) {
|
||||||
if (!isObject(target)) {
|
if (!isObject(target)) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
warn(`value cannot be made reactive: ${String(target)}`)
|
warn(
|
||||||
|
`value cannot be made ${isReadonly ? 'readonly' : 'reactive'}: ${String(
|
||||||
|
target,
|
||||||
|
)}`,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,6 +40,8 @@ describe('api: lifecycle hooks', () => {
|
||||||
}
|
}
|
||||||
render(h(Comp), root)
|
render(h(Comp), root)
|
||||||
expect(fn).toHaveBeenCalledTimes(1)
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
// #10863
|
||||||
|
expect(fn).toHaveBeenCalledWith()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('onMounted', () => {
|
it('onMounted', () => {
|
||||||
|
|
|
@ -97,6 +97,30 @@ describe('api: watch', () => {
|
||||||
expect(spy).toBeCalledWith([1], [1], expect.anything())
|
expect(spy).toBeCalledWith([1], [1], expect.anything())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not call functions inside a reactive source array', () => {
|
||||||
|
const spy1 = vi.fn()
|
||||||
|
const array = reactive([spy1])
|
||||||
|
const spy2 = vi.fn()
|
||||||
|
watch(array, spy2, { immediate: true })
|
||||||
|
expect(spy1).toBeCalledTimes(0)
|
||||||
|
expect(spy2).toBeCalledWith([spy1], undefined, expect.anything())
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not unwrap refs in a reactive source array', async () => {
|
||||||
|
const val = ref({ foo: 1 })
|
||||||
|
const array = reactive([val])
|
||||||
|
const spy = vi.fn()
|
||||||
|
watch(array, spy, { immediate: true })
|
||||||
|
expect(spy).toBeCalledTimes(1)
|
||||||
|
expect(spy).toBeCalledWith([val], undefined, expect.anything())
|
||||||
|
|
||||||
|
// deep by default
|
||||||
|
val.value.foo++
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toBeCalledTimes(2)
|
||||||
|
expect(spy).toBeCalledWith([val], [val], expect.anything())
|
||||||
|
})
|
||||||
|
|
||||||
it('should not fire if watched getter result did not change', async () => {
|
it('should not fire if watched getter result did not change', async () => {
|
||||||
const spy = vi.fn()
|
const spy = vi.fn()
|
||||||
const n = ref(0)
|
const n = ref(0)
|
||||||
|
@ -187,6 +211,24 @@ describe('api: watch', () => {
|
||||||
expect(dummy).toBe(1)
|
expect(dummy).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('directly watching reactive array with explicit deep: false', async () => {
|
||||||
|
const val = ref(1)
|
||||||
|
const array: any[] = reactive([val])
|
||||||
|
const spy = vi.fn()
|
||||||
|
watch(array, spy, { immediate: true, deep: false })
|
||||||
|
expect(spy).toBeCalledTimes(1)
|
||||||
|
expect(spy).toBeCalledWith([val], undefined, expect.anything())
|
||||||
|
|
||||||
|
val.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toBeCalledTimes(1)
|
||||||
|
|
||||||
|
array[1] = 2
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toBeCalledTimes(2)
|
||||||
|
expect(spy).toBeCalledWith([val, 2], [val, 2], expect.anything())
|
||||||
|
})
|
||||||
|
|
||||||
// #9916
|
// #9916
|
||||||
it('watching shallow reactive array with deep: false', async () => {
|
it('watching shallow reactive array with deep: false', async () => {
|
||||||
class foo {
|
class foo {
|
||||||
|
@ -891,6 +933,52 @@ describe('api: watch', () => {
|
||||||
expect(dummy).toEqual([1, 2])
|
expect(dummy).toEqual([1, 2])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('deep with symbols', async () => {
|
||||||
|
const symbol1 = Symbol()
|
||||||
|
const symbol2 = Symbol()
|
||||||
|
const symbol3 = Symbol()
|
||||||
|
const symbol4 = Symbol()
|
||||||
|
|
||||||
|
const raw: any = {
|
||||||
|
[symbol1]: {
|
||||||
|
[symbol2]: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(raw, symbol3, {
|
||||||
|
writable: true,
|
||||||
|
enumerable: false,
|
||||||
|
value: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const state = reactive(raw)
|
||||||
|
const spy = vi.fn()
|
||||||
|
|
||||||
|
watch(() => state, spy, { deep: true })
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
state[symbol1][symbol2] = 2
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// Non-enumerable properties don't trigger deep watchers
|
||||||
|
state[symbol3] = 3
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
// Adding a new symbol property
|
||||||
|
state[symbol4] = 1
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledTimes(2)
|
||||||
|
|
||||||
|
// Removing a symbol property
|
||||||
|
delete state[symbol4]
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledTimes(3)
|
||||||
|
})
|
||||||
|
|
||||||
it('immediate', async () => {
|
it('immediate', async () => {
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
const cb = vi.fn()
|
const cb = vi.fn()
|
||||||
|
@ -1517,4 +1605,20 @@ describe('api: watch', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('circular reference', async () => {
|
||||||
|
const obj = { a: 1 }
|
||||||
|
// @ts-expect-error
|
||||||
|
obj.b = obj
|
||||||
|
const foo = ref(obj)
|
||||||
|
const spy = vi.fn()
|
||||||
|
|
||||||
|
watch(foo, spy, { deep: true })
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
foo.value.b.a = 2
|
||||||
|
await nextTick()
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(foo.value.a).toBe(2)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2021,7 +2021,7 @@ describe('Suspense', () => {
|
||||||
viewRef.value = 0
|
viewRef.value = 0
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
expect(serializeInner(root)).toBe('<!---->')
|
expect(serializeInner(root)).toBe('<div>sync</div>')
|
||||||
|
|
||||||
await Promise.all(deps)
|
await Promise.all(deps)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
@ -2035,6 +2035,56 @@ describe('Suspense', () => {
|
||||||
expect(serializeInner(root)).toBe(`<div>sync</div>`)
|
expect(serializeInner(root)).toBe(`<div>sync</div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10899
|
||||||
|
test('KeepAlive + Suspense switch before branch resolves', async () => {
|
||||||
|
const Async1 = defineAsyncComponent({
|
||||||
|
render() {
|
||||||
|
return h('div', 'async1')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const Async2 = defineAsyncComponent({
|
||||||
|
render() {
|
||||||
|
return h('div', 'async2')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const components = [Async1, Async2]
|
||||||
|
const viewRef = ref(0)
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(KeepAlive, null, {
|
||||||
|
default: () => {
|
||||||
|
return h(Suspense, null, {
|
||||||
|
default: h(components[viewRef.value]),
|
||||||
|
fallback: h('div', 'loading'),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
expect(serializeInner(root)).toBe(`<div>loading</div>`)
|
||||||
|
|
||||||
|
// switch to Async2 before Async1 resolves
|
||||||
|
viewRef.value = 1
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe(`<div>loading</div>`)
|
||||||
|
|
||||||
|
await Promise.all(deps)
|
||||||
|
await nextTick()
|
||||||
|
expect(serializeInner(root)).toBe('<div>async2</div>')
|
||||||
|
|
||||||
|
viewRef.value = 0
|
||||||
|
await nextTick()
|
||||||
|
await Promise.all(deps)
|
||||||
|
expect(serializeInner(root)).toBe(`<div>async1</div>`)
|
||||||
|
|
||||||
|
viewRef.value = 1
|
||||||
|
await nextTick()
|
||||||
|
await Promise.all(deps)
|
||||||
|
expect(serializeInner(root)).toBe(`<div>async2</div>`)
|
||||||
|
})
|
||||||
|
|
||||||
// #6416 follow up / #10017
|
// #6416 follow up / #10017
|
||||||
test('Suspense patched during HOC async component re-mount', async () => {
|
test('Suspense patched during HOC async component re-mount', async () => {
|
||||||
const key = ref('k')
|
const key = ref('k')
|
||||||
|
|
|
@ -1160,6 +1160,21 @@ describe('SSR hydration', () => {
|
||||||
expect((vnode as any).component?.subTree.children[0].el).toBe(text)
|
expect((vnode as any).component?.subTree.children[0].el).toBe(text)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #7215
|
||||||
|
test('empty text node', () => {
|
||||||
|
const Comp = {
|
||||||
|
render(this: any) {
|
||||||
|
return h('p', [''])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const { container } = mountWithHydration('<p></p>', () => h(Comp))
|
||||||
|
expect(container.childNodes.length).toBe(1)
|
||||||
|
const p = container.childNodes[0]
|
||||||
|
expect(p.childNodes.length).toBe(1)
|
||||||
|
const text = p.childNodes[0]
|
||||||
|
expect(text.nodeType).toBe(3)
|
||||||
|
})
|
||||||
|
|
||||||
test('app.unmount()', async () => {
|
test('app.unmount()', async () => {
|
||||||
const container = document.createElement('DIV')
|
const container = document.createElement('DIV')
|
||||||
container.innerHTML = '<button></button>'
|
container.innerHTML = '<button></button>'
|
||||||
|
@ -1527,6 +1542,13 @@ describe('SSR hydration', () => {
|
||||||
expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
|
expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('style mismatch when no style attribute is present', () => {
|
||||||
|
mountWithHydration(`<div></div>`, () =>
|
||||||
|
h('div', { style: { color: 'red' } }),
|
||||||
|
)
|
||||||
|
expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('style mismatch w/ v-show', () => {
|
test('style mismatch w/ v-show', () => {
|
||||||
mountWithHydration(`<div style="color:red;display:none"></div>`, () =>
|
mountWithHydration(`<div style="color:red;display:none"></div>`, () =>
|
||||||
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
|
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
|
||||||
|
|
|
@ -291,6 +291,16 @@ describe('vnode', () => {
|
||||||
const cloned8 = cloneVNode(original4)
|
const cloned8 = cloneVNode(original4)
|
||||||
expect(cloned8.ref).toMatchObject({ i: mockInstance2, r, k: 'foo' })
|
expect(cloned8.ref).toMatchObject({ i: mockInstance2, r, k: 'foo' })
|
||||||
|
|
||||||
|
// @ts-expect-error #8230
|
||||||
|
const original5 = createVNode('div', { ref: 111, ref_key: 'foo' })
|
||||||
|
expect(original5.ref).toMatchObject({
|
||||||
|
i: mockInstance2,
|
||||||
|
r: '111',
|
||||||
|
k: 'foo',
|
||||||
|
})
|
||||||
|
const cloned9 = cloneVNode(original5)
|
||||||
|
expect(cloned9.ref).toMatchObject({ i: mockInstance2, r: '111', k: 'foo' })
|
||||||
|
|
||||||
setCurrentRenderingInstance(null)
|
setCurrentRenderingInstance(null)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -68,10 +68,15 @@ export function injectHook(
|
||||||
|
|
||||||
export const createHook =
|
export const createHook =
|
||||||
<T extends Function = () => any>(lifecycle: LifecycleHooks) =>
|
<T extends Function = () => any>(lifecycle: LifecycleHooks) =>
|
||||||
(hook: T, target: ComponentInternalInstance | null = currentInstance) =>
|
(hook: T, target: ComponentInternalInstance | null = currentInstance) => {
|
||||||
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
|
// post-create lifecycle registrations are noops during SSR (except for serverPrefetch)
|
||||||
(!isInSSRComponentSetup || lifecycle === LifecycleHooks.SERVER_PREFETCH) &&
|
if (
|
||||||
|
!isInSSRComponentSetup ||
|
||||||
|
lifecycle === LifecycleHooks.SERVER_PREFETCH
|
||||||
|
) {
|
||||||
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
|
injectHook(lifecycle, (...args: unknown[]) => hook(...args), target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
|
export const onBeforeMount = createHook(LifecycleHooks.BEFORE_MOUNT)
|
||||||
export const onMounted = createHook(LifecycleHooks.MOUNTED)
|
export const onMounted = createHook(LifecycleHooks.MOUNTED)
|
||||||
|
|
|
@ -66,7 +66,7 @@ type MapSources<T, Immediate> = {
|
||||||
: never
|
: never
|
||||||
}
|
}
|
||||||
|
|
||||||
type OnCleanup = (cleanupFn: () => void) => void
|
export type OnCleanup = (cleanupFn: () => void) => void
|
||||||
|
|
||||||
export interface WatchOptionsBase extends DebuggerOptions {
|
export interface WatchOptionsBase extends DebuggerOptions {
|
||||||
flush?: 'pre' | 'post' | 'sync'
|
flush?: 'pre' | 'post' | 'sync'
|
||||||
|
@ -499,6 +499,11 @@ export function traverse(
|
||||||
for (const key in value) {
|
for (const key in value) {
|
||||||
traverse(value[key], depth, seen)
|
traverse(value[key], depth, seen)
|
||||||
}
|
}
|
||||||
|
for (const key of Object.getOwnPropertySymbols(value)) {
|
||||||
|
if (Object.prototype.propertyIsEnumerable.call(value, key)) {
|
||||||
|
traverse(value[key as any], depth, seen)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,8 +36,7 @@ import {
|
||||||
legacyresolveScopedSlots,
|
legacyresolveScopedSlots,
|
||||||
} from './renderHelpers'
|
} from './renderHelpers'
|
||||||
import { resolveFilter } from '../helpers/resolveAssets'
|
import { resolveFilter } from '../helpers/resolveAssets'
|
||||||
import type { InternalSlots, Slots } from '../componentSlots'
|
import type { Slots } from '../componentSlots'
|
||||||
import type { ContextualRenderFn } from '../componentRenderContext'
|
|
||||||
import { resolveMergedOptions } from '../componentOptions'
|
import { resolveMergedOptions } from '../componentOptions'
|
||||||
|
|
||||||
export type LegacyPublicInstance = ComponentPublicInstance &
|
export type LegacyPublicInstance = ComponentPublicInstance &
|
||||||
|
@ -106,14 +105,7 @@ export function installCompatInstanceProperties(map: PublicPropertiesMap) {
|
||||||
|
|
||||||
$scopedSlots: i => {
|
$scopedSlots: i => {
|
||||||
assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i)
|
assertCompatEnabled(DeprecationTypes.INSTANCE_SCOPED_SLOTS, i)
|
||||||
const res: InternalSlots = {}
|
return __DEV__ ? shallowReadonly(i.slots) : i.slots
|
||||||
for (const key in i.slots) {
|
|
||||||
const fn = i.slots[key]!
|
|
||||||
if (!(fn as ContextualRenderFn)._ns /* non-scoped slot */) {
|
|
||||||
res[key] = fn
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
},
|
},
|
||||||
|
|
||||||
$on: i => on.bind(null, i),
|
$on: i => on.bind(null, i),
|
||||||
|
|
|
@ -1057,7 +1057,7 @@ export function finishComponentSetup(
|
||||||
: ``) /* should not happen */,
|
: ``) /* should not happen */,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
warn(`Component is missing template or render function.`)
|
warn(`Component is missing template or render function: `, Component)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
} from './component'
|
} from './component'
|
||||||
import { nextTick, queueJob } from './scheduler'
|
import { nextTick, queueJob } from './scheduler'
|
||||||
import {
|
import {
|
||||||
|
type OnCleanup,
|
||||||
type WatchOptions,
|
type WatchOptions,
|
||||||
type WatchStopHandle,
|
type WatchStopHandle,
|
||||||
instanceWatch,
|
instanceWatch,
|
||||||
|
@ -317,8 +318,8 @@ export type ComponentPublicInstance<
|
||||||
$watch<T extends string | ((...args: any) => any)>(
|
$watch<T extends string | ((...args: any) => any)>(
|
||||||
source: T,
|
source: T,
|
||||||
cb: T extends (...args: any) => infer R
|
cb: T extends (...args: any) => infer R
|
||||||
? (...args: [R, R]) => any
|
? (...args: [R, R, OnCleanup]) => any
|
||||||
: (...args: any) => any,
|
: (...args: [any, any, OnCleanup]) => any,
|
||||||
options?: WatchOptions,
|
options?: WatchOptions,
|
||||||
): WatchStopHandle
|
): WatchStopHandle
|
||||||
} & ExposedKeys<
|
} & ExposedKeys<
|
||||||
|
@ -486,9 +487,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
return desc.get.call(instance.proxy)
|
return desc.get.call(instance.proxy)
|
||||||
} else {
|
} else {
|
||||||
const val = globalProperties[key]
|
const val = globalProperties[key]
|
||||||
return isFunction(val)
|
return isFunction(val) ? extend(val.bind(instance.proxy), val) : val
|
||||||
? Object.assign(val.bind(instance.proxy), val)
|
|
||||||
: val
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return globalProperties[key]
|
return globalProperties[key]
|
||||||
|
|
|
@ -228,9 +228,17 @@ const KeepAliveImpl: ComponentOptions = {
|
||||||
const cacheSubtree = () => {
|
const cacheSubtree = () => {
|
||||||
// fix #1621, the pendingCacheKey could be 0
|
// fix #1621, the pendingCacheKey could be 0
|
||||||
if (pendingCacheKey != null) {
|
if (pendingCacheKey != null) {
|
||||||
|
// if KeepAlive child is a Suspense, it needs to be cached after Suspense resolves
|
||||||
|
// avoid caching vnode that not been mounted
|
||||||
|
if (isSuspense(instance.subTree.type)) {
|
||||||
|
queuePostRenderEffect(() => {
|
||||||
|
cache.set(pendingCacheKey!, getInnerChild(instance.subTree))
|
||||||
|
}, instance.subTree.suspense)
|
||||||
|
} else {
|
||||||
cache.set(pendingCacheKey, getInnerChild(instance.subTree))
|
cache.set(pendingCacheKey, getInnerChild(instance.subTree))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
onMounted(cacheSubtree)
|
onMounted(cacheSubtree)
|
||||||
onUpdated(cacheSubtree)
|
onUpdated(cacheSubtree)
|
||||||
|
|
||||||
|
@ -305,11 +313,11 @@ const KeepAliveImpl: ComponentOptions = {
|
||||||
rawVNode.ssContent = vnode
|
rawVNode.ssContent = vnode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// #1513 it's possible for the returned vnode to be cloned due to attr
|
// #1511 it's possible for the returned vnode to be cloned due to attr
|
||||||
// fallthrough or scopeId, so the vnode here may not be the final vnode
|
// fallthrough or scopeId, so the vnode here may not be the final vnode
|
||||||
// that is mounted. Instead of caching it directly, we store the pending
|
// that is mounted. Instead of caching it directly, we store the pending
|
||||||
// key and cache `instance.subTree` (the normalized vnode) in
|
// key and cache `instance.subTree` (the normalized vnode) in
|
||||||
// beforeMount/beforeUpdate hooks.
|
// mounted/updated hooks.
|
||||||
pendingCacheKey = key
|
pendingCacheKey = key
|
||||||
|
|
||||||
if (cachedVNode) {
|
if (cachedVNode) {
|
||||||
|
|
|
@ -47,8 +47,7 @@ const resolveTarget = <T = RendererElement>(
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
const target = select(targetSelector)
|
const target = select(targetSelector)
|
||||||
if (!target) {
|
if (__DEV__ && !target && !isTeleportDisabled(props)) {
|
||||||
__DEV__ &&
|
|
||||||
warn(
|
warn(
|
||||||
`Failed to locate Teleport target with selector "${targetSelector}". ` +
|
`Failed to locate Teleport target with selector "${targetSelector}". ` +
|
||||||
`Note the target element must exist before the component is mounted - ` +
|
`Note the target element must exist before the component is mounted - ` +
|
||||||
|
|
|
@ -63,6 +63,7 @@ export function setDevtoolsHook(hook: DevtoolsHook, target: any) {
|
||||||
// some envs mock window but not fully
|
// some envs mock window but not fully
|
||||||
window.HTMLElement &&
|
window.HTMLElement &&
|
||||||
// also exclude jsdom
|
// also exclude jsdom
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
!window.navigator?.userAgent?.includes('jsdom')
|
!window.navigator?.userAgent?.includes('jsdom')
|
||||||
) {
|
) {
|
||||||
const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
|
const replay = (target.__VUE_DEVTOOLS_HOOK_REPLAY__ =
|
||||||
|
|
|
@ -43,7 +43,7 @@ export function renderList<T>(
|
||||||
source: T,
|
source: T,
|
||||||
renderItem: <K extends keyof T>(
|
renderItem: <K extends keyof T>(
|
||||||
value: T[K],
|
value: T[K],
|
||||||
key: K,
|
key: string,
|
||||||
index: number,
|
index: number,
|
||||||
) => VNodeChild,
|
) => VNodeChild,
|
||||||
): VNodeChild[]
|
): VNodeChild[]
|
||||||
|
|
|
@ -541,7 +541,9 @@ export function createHydrationFunctions(
|
||||||
optimized,
|
optimized,
|
||||||
)
|
)
|
||||||
} else if (vnode.type === Text && !vnode.children) {
|
} else if (vnode.type === Text && !vnode.children) {
|
||||||
continue
|
// #7215 create a TextNode for empty text node
|
||||||
|
// because server rendered HTML won't contain a text node
|
||||||
|
insert((vnode.el = createText('')), container)
|
||||||
} else {
|
} else {
|
||||||
hasMismatch = true
|
hasMismatch = true
|
||||||
if (
|
if (
|
||||||
|
@ -727,8 +729,8 @@ function propHasMismatch(
|
||||||
): boolean {
|
): boolean {
|
||||||
let mismatchType: string | undefined
|
let mismatchType: string | undefined
|
||||||
let mismatchKey: string | undefined
|
let mismatchKey: string | undefined
|
||||||
let actual: any
|
let actual: string | boolean | null | undefined
|
||||||
let expected: any
|
let expected: string | boolean | null | undefined
|
||||||
if (key === 'class') {
|
if (key === 'class') {
|
||||||
// classes might be in different order, but that doesn't affect cascade
|
// classes might be in different order, but that doesn't affect cascade
|
||||||
// so we just need to check if the class lists contain the same classes.
|
// so we just need to check if the class lists contain the same classes.
|
||||||
|
@ -739,7 +741,7 @@ function propHasMismatch(
|
||||||
}
|
}
|
||||||
} else if (key === 'style') {
|
} else if (key === 'style') {
|
||||||
// style might be in different order, but that doesn't affect cascade
|
// style might be in different order, but that doesn't affect cascade
|
||||||
actual = el.getAttribute('style')
|
actual = el.getAttribute('style') || ''
|
||||||
expected = isString(clientValue)
|
expected = isString(clientValue)
|
||||||
? clientValue
|
? clientValue
|
||||||
: stringifyStyle(normalizeStyle(clientValue))
|
: stringifyStyle(normalizeStyle(clientValue))
|
||||||
|
@ -755,11 +757,14 @@ function propHasMismatch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
const root = instance?.subTree
|
const root = instance?.subTree
|
||||||
if (
|
if (
|
||||||
vnode === root ||
|
vnode === root ||
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
(root?.type === Fragment && (root.children as VNode[]).includes(vnode))
|
(root?.type === Fragment && (root.children as VNode[]).includes(vnode))
|
||||||
) {
|
) {
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
const cssVars = instance?.getCssVars?.()
|
const cssVars = instance?.getCssVars?.()
|
||||||
for (const key in cssVars) {
|
for (const key in cssVars) {
|
||||||
expectedMap.set(`--${key}`, String(cssVars[key]))
|
expectedMap.set(`--${key}`, String(cssVars[key]))
|
||||||
|
@ -840,7 +845,9 @@ function toStyleMap(str: string): Map<string, string> {
|
||||||
const styleMap: Map<string, string> = new Map()
|
const styleMap: Map<string, string> = new Map()
|
||||||
for (const item of str.split(';')) {
|
for (const item of str.split(';')) {
|
||||||
let [key, value] = item.split(':')
|
let [key, value] = item.split(':')
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
key = key?.trim()
|
key = key?.trim()
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
value = value?.trim()
|
value = value?.trim()
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
styleMap.set(key, value)
|
styleMap.set(key, value)
|
||||||
|
|
|
@ -45,6 +45,7 @@ export function warn(msg: string, ...args: any[]) {
|
||||||
instance,
|
instance,
|
||||||
ErrorCodes.APP_WARN_HANDLER,
|
ErrorCodes.APP_WARN_HANDLER,
|
||||||
[
|
[
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
msg + args.map(a => a.toString?.() ?? JSON.stringify(a)).join(''),
|
msg + args.map(a => a.toString?.() ?? JSON.stringify(a)).join(''),
|
||||||
instance && instance.proxy,
|
instance && instance.proxy,
|
||||||
trace
|
trace
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
type Ref,
|
type Ref,
|
||||||
type VueElement,
|
type VueElement,
|
||||||
|
createApp,
|
||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
defineCustomElement,
|
defineCustomElement,
|
||||||
|
@ -60,6 +61,54 @@ describe('defineCustomElement', () => {
|
||||||
expect(e.shadowRoot!.innerHTML).toBe('')
|
expect(e.shadowRoot!.innerHTML).toBe('')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10610
|
||||||
|
test('When elements move, avoid prematurely disconnecting MutationObserver', async () => {
|
||||||
|
const CustomInput = defineCustomElement({
|
||||||
|
props: ['value'],
|
||||||
|
emits: ['update'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
return () =>
|
||||||
|
h('input', {
|
||||||
|
type: 'number',
|
||||||
|
value: props.value,
|
||||||
|
onInput: (e: InputEvent) => {
|
||||||
|
const num = (e.target! as HTMLInputElement).valueAsNumber
|
||||||
|
emit('update', Number.isNaN(num) ? null : num)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
},
|
||||||
|
})
|
||||||
|
customElements.define('my-el-input', CustomInput)
|
||||||
|
const num = ref('12')
|
||||||
|
const containerComp = defineComponent({
|
||||||
|
setup() {
|
||||||
|
return () => {
|
||||||
|
return h('div', [
|
||||||
|
h('my-el-input', {
|
||||||
|
value: num.value,
|
||||||
|
onUpdate: ($event: CustomEvent) => {
|
||||||
|
num.value = $event.detail[0]
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
h('div', { id: 'move' }),
|
||||||
|
])
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const app = createApp(containerComp)
|
||||||
|
app.mount(container)
|
||||||
|
const myInputEl = container.querySelector('my-el-input')!
|
||||||
|
const inputEl = myInputEl.shadowRoot!.querySelector('input')!
|
||||||
|
await nextTick()
|
||||||
|
expect(inputEl.value).toBe('12')
|
||||||
|
const moveEl = container.querySelector('#move')!
|
||||||
|
moveEl.append(myInputEl)
|
||||||
|
await nextTick()
|
||||||
|
myInputEl.removeAttribute('value')
|
||||||
|
await nextTick()
|
||||||
|
expect(inputEl.value).toBe('')
|
||||||
|
})
|
||||||
|
|
||||||
test('should not unmount on move', async () => {
|
test('should not unmount on move', async () => {
|
||||||
container.innerHTML = `<div><my-element></my-element></div>`
|
container.innerHTML = `<div><my-element></my-element></div>`
|
||||||
const e = container.childNodes[0].childNodes[0] as VueElement
|
const e = container.childNodes[0].childNodes[0] as VueElement
|
||||||
|
|
|
@ -256,7 +256,13 @@ describe('vModel', () => {
|
||||||
it('should support modifiers', async () => {
|
it('should support modifiers', async () => {
|
||||||
const component = defineComponent({
|
const component = defineComponent({
|
||||||
data() {
|
data() {
|
||||||
return { number: null, trim: null, lazy: null, trimNumber: null }
|
return {
|
||||||
|
number: null,
|
||||||
|
trim: null,
|
||||||
|
lazy: null,
|
||||||
|
trimNumber: null,
|
||||||
|
trimLazy: null,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
render() {
|
render() {
|
||||||
return [
|
return [
|
||||||
|
@ -284,6 +290,19 @@ describe('vModel', () => {
|
||||||
trim: true,
|
trim: true,
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
withVModel(
|
||||||
|
h('input', {
|
||||||
|
class: 'trim-lazy',
|
||||||
|
'onUpdate:modelValue': (val: any) => {
|
||||||
|
this.trimLazy = val
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
this.trim,
|
||||||
|
{
|
||||||
|
trim: true,
|
||||||
|
lazy: true,
|
||||||
|
},
|
||||||
|
),
|
||||||
withVModel(
|
withVModel(
|
||||||
h('input', {
|
h('input', {
|
||||||
class: 'trim-number',
|
class: 'trim-number',
|
||||||
|
@ -317,6 +336,7 @@ describe('vModel', () => {
|
||||||
const number = root.querySelector('.number')
|
const number = root.querySelector('.number')
|
||||||
const trim = root.querySelector('.trim')
|
const trim = root.querySelector('.trim')
|
||||||
const trimNumber = root.querySelector('.trim-number')
|
const trimNumber = root.querySelector('.trim-number')
|
||||||
|
const trimLazy = root.querySelector('.trim-lazy')
|
||||||
const lazy = root.querySelector('.lazy')
|
const lazy = root.querySelector('.lazy')
|
||||||
const data = root._vnode.component.data
|
const data = root._vnode.component.data
|
||||||
|
|
||||||
|
@ -340,6 +360,11 @@ describe('vModel', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(data.trimNumber).toEqual(1.2)
|
expect(data.trimNumber).toEqual(1.2)
|
||||||
|
|
||||||
|
trimLazy.value = ' ddd '
|
||||||
|
triggerEvent('change', trimLazy)
|
||||||
|
await nextTick()
|
||||||
|
expect(data.trimLazy).toEqual('ddd')
|
||||||
|
|
||||||
lazy.value = 'foo'
|
lazy.value = 'foo'
|
||||||
triggerEvent('change', lazy)
|
triggerEvent('change', lazy)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { nodeOps, svgNS } from '../src/nodeOps'
|
import { defineComponent, h, nextTick, ref } from 'vue'
|
||||||
|
import { mathmlNS, nodeOps, svgNS } from '../src/nodeOps'
|
||||||
|
import { render } from '@vue/runtime-dom'
|
||||||
describe('runtime-dom: node-ops', () => {
|
describe('runtime-dom: node-ops', () => {
|
||||||
test("the <select>'s multiple attr should be set in createElement", () => {
|
test("the <select>'s multiple attr should be set in createElement", () => {
|
||||||
const el = nodeOps.createElement('select', undefined, undefined, {
|
const el = nodeOps.createElement('select', undefined, undefined, {
|
||||||
|
@ -106,5 +107,38 @@ describe('runtime-dom: node-ops', () => {
|
||||||
expect(nodes[0]).toBe(parent.firstChild)
|
expect(nodes[0]).toBe(parent.firstChild)
|
||||||
expect(nodes[1]).toBe(parent.childNodes[parent.childNodes.length - 2])
|
expect(nodes[1]).toBe(parent.childNodes[parent.childNodes.length - 2])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('The math elements should keep their MathML namespace', async () => {
|
||||||
|
let root = document.createElement('div') as any
|
||||||
|
|
||||||
|
let countRef: any
|
||||||
|
const component = defineComponent({
|
||||||
|
data() {
|
||||||
|
return { value: 0 }
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const count = ref(0)
|
||||||
|
countRef = count
|
||||||
|
return {
|
||||||
|
count,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<math>
|
||||||
|
<mrow class="bar" v-if="count % 2">Bar</mrow>
|
||||||
|
<msup class="foo" v-else>Foo</msup>
|
||||||
|
</math>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
})
|
||||||
|
render(h(component), root)
|
||||||
|
const foo = root.querySelector('.foo')
|
||||||
|
expect(foo.namespaceURI).toBe(mathmlNS)
|
||||||
|
countRef.value++
|
||||||
|
await nextTick()
|
||||||
|
const bar = root.querySelector('.bar')
|
||||||
|
expect(bar.namespaceURI).toBe(mathmlNS)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -199,12 +199,12 @@ export class VueElement extends BaseClass {
|
||||||
|
|
||||||
disconnectedCallback() {
|
disconnectedCallback() {
|
||||||
this._connected = false
|
this._connected = false
|
||||||
|
nextTick(() => {
|
||||||
|
if (!this._connected) {
|
||||||
if (this._ob) {
|
if (this._ob) {
|
||||||
this._ob.disconnect()
|
this._ob.disconnect()
|
||||||
this._ob = null
|
this._ob = null
|
||||||
}
|
}
|
||||||
nextTick(() => {
|
|
||||||
if (!this._connected) {
|
|
||||||
render(null, this.shadowRoot!)
|
render(null, this.shadowRoot!)
|
||||||
this._instance = null
|
this._instance = null
|
||||||
}
|
}
|
||||||
|
|
|
@ -406,7 +406,7 @@ export interface DataHTMLAttributes extends HTMLAttributes {
|
||||||
|
|
||||||
export interface DetailsHTMLAttributes extends HTMLAttributes {
|
export interface DetailsHTMLAttributes extends HTMLAttributes {
|
||||||
open?: Booleanish
|
open?: Booleanish
|
||||||
onToggle?: Event
|
onToggle?: (payload: ToggleEvent) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DelHTMLAttributes extends HTMLAttributes {
|
export interface DelHTMLAttributes extends HTMLAttributes {
|
||||||
|
|
|
@ -266,7 +266,7 @@ export function renderVNodeChildren(
|
||||||
push: PushFn,
|
push: PushFn,
|
||||||
children: VNodeArrayChildren,
|
children: VNodeArrayChildren,
|
||||||
parentComponent: ComponentInternalInstance,
|
parentComponent: ComponentInternalInstance,
|
||||||
slotScopeId: string | undefined,
|
slotScopeId?: string,
|
||||||
) {
|
) {
|
||||||
for (let i = 0; i < children.length; i++) {
|
for (let i = 0; i < children.length; i++) {
|
||||||
renderVNode(push, normalizeVNode(children[i]), parentComponent, slotScopeId)
|
renderVNode(push, normalizeVNode(children[i]), parentComponent, slotScopeId)
|
||||||
|
@ -277,7 +277,7 @@ function renderElementVNode(
|
||||||
push: PushFn,
|
push: PushFn,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
parentComponent: ComponentInternalInstance,
|
parentComponent: ComponentInternalInstance,
|
||||||
slotScopeId: string | undefined,
|
slotScopeId?: string,
|
||||||
) {
|
) {
|
||||||
const tag = vnode.type as string
|
const tag = vnode.type as string
|
||||||
let { props, children, shapeFlag, scopeId, dirs } = vnode
|
let { props, children, shapeFlag, scopeId, dirs } = vnode
|
||||||
|
@ -362,7 +362,7 @@ function renderTeleportVNode(
|
||||||
push: PushFn,
|
push: PushFn,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
parentComponent: ComponentInternalInstance,
|
parentComponent: ComponentInternalInstance,
|
||||||
slotScopeId: string | undefined,
|
slotScopeId?: string,
|
||||||
) {
|
) {
|
||||||
const target = vnode.props && vnode.props.to
|
const target = vnode.props && vnode.props.to
|
||||||
const disabled = vnode.props && vnode.props.disabled
|
const disabled = vnode.props && vnode.props.disabled
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"vite": "^5.2.10"
|
"vite": "^5.2.11"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^4.1.2",
|
"@vue/repl": "^4.1.2",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Vite Vue Starter
|
# Vite Vue Starter
|
||||||
|
|
||||||
This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) version 18+, 20+.
|
This is a project template using [Vite](https://vitejs.dev/). It requires [Node.js](https://nodejs.org) version 18+ or 20+.
|
||||||
|
|
||||||
To start:
|
To start:
|
||||||
|
|
||||||
|
@ -11,4 +11,8 @@ npm run dev
|
||||||
# if using yarn:
|
# if using yarn:
|
||||||
yarn
|
yarn
|
||||||
yarn dev
|
yarn dev
|
||||||
|
|
||||||
|
# if using pnpm:
|
||||||
|
pnpm install
|
||||||
|
pnpm run dev
|
||||||
```
|
```
|
||||||
|
|
|
@ -12,6 +12,6 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.4",
|
"@vitejs/plugin-vue": "^5.0.4",
|
||||||
"vite": "^5.2.10"
|
"vite": "^5.2.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -133,9 +133,9 @@ export const toHandlerKey = cacheStringFunction(<T extends string>(str: T) => {
|
||||||
export const hasChanged = (value: any, oldValue: any): boolean =>
|
export const hasChanged = (value: any, oldValue: any): boolean =>
|
||||||
!Object.is(value, oldValue)
|
!Object.is(value, oldValue)
|
||||||
|
|
||||||
export const invokeArrayFns = (fns: Function[], arg?: any) => {
|
export const invokeArrayFns = (fns: Function[], ...arg: any[]) => {
|
||||||
for (let i = 0; i < fns.length; i++) {
|
for (let i = 0; i < fns.length; i++) {
|
||||||
fns[i](arg)
|
fns[i](...arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,8 +51,8 @@ export function stringifyStyle(
|
||||||
}
|
}
|
||||||
for (const key in styles) {
|
for (const key in styles) {
|
||||||
const value = styles[key]
|
const value = styles[key]
|
||||||
const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key)
|
|
||||||
if (isString(value) || typeof value === 'number') {
|
if (isString(value) || typeof value === 'number') {
|
||||||
|
const normalizedKey = key.startsWith(`--`) ? key : hyphenate(key)
|
||||||
// only render valid values
|
// only render valid values
|
||||||
ret += `${normalizedKey}:${value};`
|
ret += `${normalizedKey}:${value};`
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,7 +284,7 @@ describe('INSTANCE_SCOPED_SLOTS', () => {
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should not include legacy slot usage in $scopedSlots', () => {
|
test('should include legacy slot usage in $scopedSlots', () => {
|
||||||
let normalSlots: Slots
|
let normalSlots: Slots
|
||||||
let scopedSlots: Slots
|
let scopedSlots: Slots
|
||||||
new Vue({
|
new Vue({
|
||||||
|
@ -301,7 +301,7 @@ describe('INSTANCE_SCOPED_SLOTS', () => {
|
||||||
}).$mount()
|
}).$mount()
|
||||||
|
|
||||||
expect('default' in normalSlots!).toBe(true)
|
expect('default' in normalSlots!).toBe(true)
|
||||||
expect('default' in scopedSlots!).toBe(false)
|
expect('default' in scopedSlots!).toBe(true)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message,
|
deprecationData[DeprecationTypes.INSTANCE_SCOPED_SLOTS].message,
|
||||||
|
|
|
@ -52,7 +52,7 @@
|
||||||
},
|
},
|
||||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/vue-compat#readme",
|
"homepage": "https://github.com/vuejs/core/tree/main/packages/vue-compat#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/parser": "^7.24.4",
|
"@babel/parser": "^7.24.6",
|
||||||
"estree-walker": "^2.0.2",
|
"estree-walker": "^2.0.2",
|
||||||
"source-map-js": "^1.2.0"
|
"source-map-js": "^1.2.0"
|
||||||
},
|
},
|
||||||
|
|
1287
pnpm-lock.yaml
1287
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -41,6 +41,8 @@ export default defineConfig({
|
||||||
'packages/runtime-dom/src/components/Transition*',
|
'packages/runtime-dom/src/components/Transition*',
|
||||||
// mostly entries
|
// mostly entries
|
||||||
'packages/vue-compat/**',
|
'packages/vue-compat/**',
|
||||||
|
'packages/sfc-playground/**',
|
||||||
|
'scripts/**',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue