mirror of https://github.com/vuejs/core.git
Merge tag 'v3.5.13'
This commit is contained in:
commit
f6ce3f9d43
|
@ -14,5 +14,6 @@
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
},
|
||||||
|
"editor.formatOnSave": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
https://vuejs.org/funding.json
|
33
CHANGELOG.md
33
CHANGELOG.md
|
@ -1,3 +1,36 @@
|
||||||
|
## [3.5.13](https://github.com/vuejs/core/compare/v3.5.12...v3.5.13) (2024-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-core:** handle v-memo + v-for with functional key ([#12014](https://github.com/vuejs/core/issues/12014)) ([99009ee](https://github.com/vuejs/core/commit/99009eee0efc238392daba93792d478525b21afa)), closes [#12013](https://github.com/vuejs/core/issues/12013)
|
||||||
|
* **compiler-dom:** properly stringify template string style ([#12392](https://github.com/vuejs/core/issues/12392)) ([2d78539](https://github.com/vuejs/core/commit/2d78539da35322aea5f821b3cf9b02d006abac72)), closes [#12391](https://github.com/vuejs/core/issues/12391)
|
||||||
|
* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
|
||||||
|
* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
|
||||||
|
* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
|
||||||
|
* **reactiivty:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
|
||||||
|
* **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370)
|
||||||
|
* **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533)
|
||||||
|
* **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6))
|
||||||
|
* **ssr:** avoid updating subtree of async component if it is resolved ([#12363](https://github.com/vuejs/core/issues/12363)) ([da7ad5e](https://github.com/vuejs/core/commit/da7ad5e3d24f3e108401188d909d27a4910da095)), closes [#12362](https://github.com/vuejs/core/issues/12362)
|
||||||
|
* **ssr:** ensure v-text updates correctly with custom directives in SSR output ([#12311](https://github.com/vuejs/core/issues/12311)) ([1f75d4e](https://github.com/vuejs/core/commit/1f75d4e6dfe18121ebe443cd3e8105d54f727893)), closes [#12309](https://github.com/vuejs/core/issues/12309)
|
||||||
|
* **ssr:** handle initial selected state for select with v-model + v-for option ([#12399](https://github.com/vuejs/core/issues/12399)) ([4f8d807](https://github.com/vuejs/core/commit/4f8d8078221ee52deed266677a227ad2a6d8dd22)), closes [#12395](https://github.com/vuejs/core/issues/12395)
|
||||||
|
* **teleport:** handle deferred teleport update before mounted ([#12168](https://github.com/vuejs/core/issues/12168)) ([8bff142](https://github.com/vuejs/core/commit/8bff142f99b646e9dd15897ec75368fbf34f1534)), closes [#12161](https://github.com/vuejs/core/issues/12161)
|
||||||
|
* **templateRef:** set ref on cached async component which wrapped in KeepAlive ([#12290](https://github.com/vuejs/core/issues/12290)) ([983eb50](https://github.com/vuejs/core/commit/983eb50a17eac76f1bba4394ad0316c62b72191d)), closes [#4999](https://github.com/vuejs/core/issues/4999) [#5004](https://github.com/vuejs/core/issues/5004)
|
||||||
|
* **test:** update snapshot ([#12169](https://github.com/vuejs/core/issues/12169)) ([828d4a4](https://github.com/vuejs/core/commit/828d4a443919fa2aa4e2e92fbd03a5f04b258eea))
|
||||||
|
* **Transition:** fix transition memory leak edge case ([#12182](https://github.com/vuejs/core/issues/12182)) ([660132d](https://github.com/vuejs/core/commit/660132df6c6a8c14bf75e593dc47d2fdada30322)), closes [#12181](https://github.com/vuejs/core/issues/12181)
|
||||||
|
* **transition:** reflow before leave-active class after leave-from ([#12288](https://github.com/vuejs/core/issues/12288)) ([4b479db](https://github.com/vuejs/core/commit/4b479db61d233b054561402ae94ef08550073ea1)), closes [#2593](https://github.com/vuejs/core/issues/2593)
|
||||||
|
* **types:** defineEmits w/ interface declaration ([#12343](https://github.com/vuejs/core/issues/12343)) ([1022eab](https://github.com/vuejs/core/commit/1022eabaa1aaf8436876f5ec5573cb1e4b3959a6)), closes [#8457](https://github.com/vuejs/core/issues/8457)
|
||||||
|
* **v-once:** setting hasOnce to current block only when in v-once ([#12374](https://github.com/vuejs/core/issues/12374)) ([37300fc](https://github.com/vuejs/core/commit/37300fc26190a7299efddbf98800ffd96d5cad96)), closes [#12371](https://github.com/vuejs/core/issues/12371)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **reactivity:** do not track inner key `__v_skip`` ([#11690](https://github.com/vuejs/core/issues/11690)) ([d637bd6](https://github.com/vuejs/core/commit/d637bd6c0164c2883e6eabd3c2f1f8c258dedfb1))
|
||||||
|
* **runtime-core:** use feature flag for call to resolveMergedOptions ([#12163](https://github.com/vuejs/core/issues/12163)) ([1755ac0](https://github.com/vuejs/core/commit/1755ac0a108ba3486bd8397e56d3bdcd69196594))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.5.12](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12) (2024-10-11)
|
## [3.5.12](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12) (2024-10-11)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"packageManager": "pnpm@9.12.3",
|
"packageManager": "pnpm@9.12.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
@ -22,7 +22,10 @@
|
||||||
"test-dts": "run-s build-dts test-dts-only",
|
"test-dts": "run-s build-dts test-dts-only",
|
||||||
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
||||||
"test-coverage": "vitest run --project unit --coverage",
|
"test-coverage": "vitest run --project unit --coverage",
|
||||||
"test-bench": "vitest bench",
|
"prebench": "node scripts/build.js -pf esm-browser reactivity",
|
||||||
|
"prebench-compare": "node scripts/build.js -pf esm-browser reactivity",
|
||||||
|
"bench": "vitest bench --project=unit --outputJson=temp/bench.json",
|
||||||
|
"bench-compare": "vitest bench --project=unit --compare=temp/bench.json",
|
||||||
"release": "node scripts/release.js",
|
"release": "node scripts/release.js",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||||
|
|
|
@ -306,6 +306,14 @@ describe('defineEmits w/ type declaration', () => {
|
||||||
emit2('baz')
|
emit2('baz')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('defineEmits w/ interface declaration', () => {
|
||||||
|
interface Emits {
|
||||||
|
foo: [value: string]
|
||||||
|
}
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
emit('foo', 'hi')
|
||||||
|
})
|
||||||
|
|
||||||
describe('defineEmits w/ alt type declaration', () => {
|
describe('defineEmits w/ alt type declaration', () => {
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
foo: [id: string]
|
foo: [id: string]
|
||||||
|
|
|
@ -48,6 +48,7 @@ function resetVueVersion() {
|
||||||
|
|
||||||
async function copyLink(e: MouseEvent) {
|
async function copyLink(e: MouseEvent) {
|
||||||
if (e.metaKey) {
|
if (e.metaKey) {
|
||||||
|
resetVueVersion()
|
||||||
// hidden logic for going to local debug from play.vuejs.org
|
// hidden logic for going to local debug from play.vuejs.org
|
||||||
window.location.href = 'http://localhost:5173/' + window.location.hash
|
window.location.href = 'http://localhost:5173/' + window.location.hash
|
||||||
return
|
return
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform > element v-for key expression prefixing + v-memo 1`] = `
|
||||||
|
"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
|
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, ___, _cached) => {
|
||||||
|
const _memo = (_ctx.getLetter(data))
|
||||||
|
if (_cached && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached
|
||||||
|
const _item = (_openBlock(), _createElementBlock("span", {
|
||||||
|
key: _ctx.getId(data)
|
||||||
|
}))
|
||||||
|
_item.memo = _memo
|
||||||
|
return _item
|
||||||
|
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: v-memo transform > on component 1`] = `
|
exports[`compiler: v-memo transform > on component 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
|
||||||
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
||||||
return _cache[0] || (
|
return _cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -28,7 +28,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -47,7 +47,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -66,7 +66,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
|
|
@ -53,4 +53,12 @@ describe('compiler: v-memo transform', () => {
|
||||||
),
|
),
|
||||||
).toMatchSnapshot()
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('element v-for key expression prefixing + v-memo', () => {
|
||||||
|
expect(
|
||||||
|
compile(
|
||||||
|
`<span v-for="data of tableData" :key="getId(data)" v-memo="getLetter(data)"></span>`,
|
||||||
|
),
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
|
|
@ -425,6 +425,7 @@ export interface CacheExpression extends Node {
|
||||||
index: number
|
index: number
|
||||||
value: JSChildNode
|
value: JSChildNode
|
||||||
needPauseTracking: boolean
|
needPauseTracking: boolean
|
||||||
|
inVOnce: boolean
|
||||||
needArraySpread: boolean
|
needArraySpread: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -781,12 +782,14 @@ export function createCacheExpression(
|
||||||
index: number,
|
index: number,
|
||||||
value: JSChildNode,
|
value: JSChildNode,
|
||||||
needPauseTracking: boolean = false,
|
needPauseTracking: boolean = false,
|
||||||
|
inVOnce: boolean = false,
|
||||||
): CacheExpression {
|
): CacheExpression {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index,
|
index,
|
||||||
value,
|
value,
|
||||||
needPauseTracking: needPauseTracking,
|
needPauseTracking: needPauseTracking,
|
||||||
|
inVOnce,
|
||||||
needArraySpread: false,
|
needArraySpread: false,
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1034,7 +1034,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||||
push(`_cache[${node.index}] || (`)
|
push(`_cache[${node.index}] || (`)
|
||||||
if (needPauseTracking) {
|
if (needPauseTracking) {
|
||||||
indent()
|
indent()
|
||||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
push(`${helper(SET_BLOCK_TRACKING)}(-1`)
|
||||||
|
if (node.inVOnce) push(`, true`)
|
||||||
|
push(`),`)
|
||||||
newline()
|
newline()
|
||||||
push(`(`)
|
push(`(`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,7 +116,7 @@ export interface TransformContext
|
||||||
addIdentifiers(exp: ExpressionNode | string): void
|
addIdentifiers(exp: ExpressionNode | string): void
|
||||||
removeIdentifiers(exp: ExpressionNode | string): void
|
removeIdentifiers(exp: ExpressionNode | string): void
|
||||||
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
||||||
cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
|
cache(exp: JSChildNode, isVNode?: boolean, inVOnce?: boolean): CacheExpression
|
||||||
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
||||||
|
|
||||||
// 2.x Compat only
|
// 2.x Compat only
|
||||||
|
@ -297,11 +297,12 @@ export function createTransformContext(
|
||||||
identifier.hoisted = exp
|
identifier.hoisted = exp
|
||||||
return identifier
|
return identifier
|
||||||
},
|
},
|
||||||
cache(exp, isVNode = false) {
|
cache(exp, isVNode = false, inVOnce = false) {
|
||||||
const cacheExp = createCacheExpression(
|
const cacheExp = createCacheExpression(
|
||||||
context.cached.length,
|
context.cached.length,
|
||||||
exp,
|
exp,
|
||||||
isVNode,
|
isVNode,
|
||||||
|
inVOnce,
|
||||||
)
|
)
|
||||||
context.cached.push(cacheExp)
|
context.cached.push(cacheExp)
|
||||||
return cacheExp
|
return cacheExp
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
isStaticPropertyKey,
|
isStaticPropertyKey,
|
||||||
walkIdentifiers,
|
walkIdentifiers,
|
||||||
} from '../babelUtils'
|
} from '../babelUtils'
|
||||||
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
|
import { advancePositionWithClone, findDir, isSimpleIdentifier } from '../utils'
|
||||||
import {
|
import {
|
||||||
genPropsAccessExp,
|
genPropsAccessExp,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
|
@ -55,6 +55,7 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||||
)
|
)
|
||||||
} else if (node.type === NodeTypes.ELEMENT) {
|
} else if (node.type === NodeTypes.ELEMENT) {
|
||||||
// handle directives on element
|
// handle directives on element
|
||||||
|
const memo = findDir(node, 'memo')
|
||||||
for (let i = 0; i < node.props.length; i++) {
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
const dir = node.props[i]
|
const dir = node.props[i]
|
||||||
// do not process for v-on & v-for since they are special handled
|
// do not process for v-on & v-for since they are special handled
|
||||||
|
@ -66,7 +67,14 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
exp &&
|
exp &&
|
||||||
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
!(dir.name === 'on' && arg)
|
!(dir.name === 'on' && arg) &&
|
||||||
|
// key has been processed in transformFor(vMemo + vFor)
|
||||||
|
!(
|
||||||
|
memo &&
|
||||||
|
arg &&
|
||||||
|
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
arg.content === 'key'
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
dir.exp = processExpression(
|
dir.exp = processExpression(
|
||||||
exp,
|
exp,
|
||||||
|
|
|
@ -63,17 +63,27 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
|
||||||
const isTemplate = isTemplateNode(node)
|
const isTemplate = isTemplateNode(node)
|
||||||
const memo = findDir(node, 'memo')
|
const memo = findDir(node, 'memo')
|
||||||
const keyProp = findProp(node, `key`, false, true)
|
const keyProp = findProp(node, `key`, false, true)
|
||||||
if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
|
const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
|
||||||
|
if (isDirKey && !keyProp.exp) {
|
||||||
// resolve :key shorthand #10882
|
// resolve :key shorthand #10882
|
||||||
transformBindShorthand(keyProp, context)
|
transformBindShorthand(keyProp, context)
|
||||||
}
|
}
|
||||||
const keyExp =
|
let keyExp =
|
||||||
keyProp &&
|
keyProp &&
|
||||||
(keyProp.type === NodeTypes.ATTRIBUTE
|
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||||
? keyProp.value
|
? keyProp.value
|
||||||
? createSimpleExpression(keyProp.value.content, true)
|
? createSimpleExpression(keyProp.value.content, true)
|
||||||
: undefined
|
: undefined
|
||||||
: keyProp.exp)
|
: keyProp.exp)
|
||||||
|
|
||||||
|
if (memo && keyExp && isDirKey) {
|
||||||
|
if (!__BROWSER__) {
|
||||||
|
keyProp.exp = keyExp = processExpression(
|
||||||
|
keyExp as SimpleExpressionNode,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
const keyProperty =
|
const keyProperty =
|
||||||
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,11 @@ export const transformOnce: NodeTransform = (node, context) => {
|
||||||
context.inVOnce = false
|
context.inVOnce = false
|
||||||
const cur = context.currentNode as ElementNode | IfNode | ForNode
|
const cur = context.currentNode as ElementNode | IfNode | ForNode
|
||||||
if (cur.codegenNode) {
|
if (cur.codegenNode) {
|
||||||
cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */)
|
cur.codegenNode = context.cache(
|
||||||
|
cur.codegenNode,
|
||||||
|
true /* isVNode */,
|
||||||
|
true /* inVOnce */,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,16 @@ return function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`stringify static html > serializing template string style 1`] = `
|
||||||
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||||
|
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||||
|
])))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should bail for <option> elements with null values 1`] = `
|
exports[`stringify static html > should bail for <option> elements with null values 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,27 @@ describe('stringify static html', () => {
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12391
|
||||||
|
test('serializing template string style', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><div :style="\`color:red;\`">${repeat(
|
||||||
|
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</div></div>`,
|
||||||
|
)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.cached).toMatchObject([
|
||||||
|
cachedArrayStaticNodeMatcher(
|
||||||
|
`<div style="color:red;">${repeat(
|
||||||
|
`<span class="foo bar">1 + false</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</div>`,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('escape', () => {
|
test('escape', () => {
|
||||||
const { ast, code } = compileWithStringify(
|
const { ast, code } = compileWithStringify(
|
||||||
`<div><div>${repeat(
|
`<div><div>${repeat(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
"module": "dist/compiler-sfc.esm-browser.js",
|
"module": "dist/compiler-sfc.esm-browser.js",
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "~9.0.5",
|
"minimatch": "~9.0.5",
|
||||||
"postcss-modules": "^6.0.0",
|
"postcss-modules": "^6.0.0",
|
||||||
"postcss-selector-parser": "^6.1.2",
|
"postcss-selector-parser": "^7.0.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.80.6"
|
"sass": "^1.80.6"
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,8 +189,7 @@ function rewriteSelector(
|
||||||
// global: replace with inner selector and do not inject [id].
|
// global: replace with inner selector and do not inject [id].
|
||||||
// ::v-global(.foo) -> .foo
|
// ::v-global(.foo) -> .foo
|
||||||
if (value === ':global' || value === '::v-global') {
|
if (value === ':global' || value === '::v-global') {
|
||||||
selectorRoot.insertAfter(selector, n.nodes[0])
|
selector.replaceWith(n.nodes[0])
|
||||||
selectorRoot.removeChild(selector)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,6 +337,39 @@ describe('ssr: element', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('custom dir with v-text', () => {
|
||||||
|
expect(getCompiledString(`<div v-xxx v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_ssrGetDirectiveProps(_ctx, _directive_xxx))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom dir with v-text and normal attrs', () => {
|
||||||
|
expect(getCompiledString(`<div class="test" v-xxx v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_mergeProps({ class: "test" }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mulptiple custom dirs with v-text', () => {
|
||||||
|
expect(getCompiledString(`<div v-xxx v-yyy v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_mergeProps(_ssrGetDirectiveProps(_ctx, _directive_xxx), _ssrGetDirectiveProps(_ctx, _directive_yyy)))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
test('custom dir with object v-bind', () => {
|
test('custom dir with object v-bind', () => {
|
||||||
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
|
|
|
@ -52,6 +52,52 @@ describe('ssr: v-model', () => {
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
compileWithWrapper(
|
||||||
|
`<select v-model="model"><option v-for="i in items" :value="i"></option></select>`,
|
||||||
|
).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select><!--[-->\`)
|
||||||
|
_ssrRenderList(_ctx.items, (i) => {
|
||||||
|
_push(\`<option\${
|
||||||
|
_ssrRenderAttr("value", i)
|
||||||
|
}\${
|
||||||
|
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||||
|
? _ssrLooseContain(_ctx.model, i)
|
||||||
|
: _ssrLooseEqual(_ctx.model, i))) ? " selected" : ""
|
||||||
|
}></option>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!--]--></select></div>\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
compileWithWrapper(
|
||||||
|
`<select v-model="model"><option v-if="true" :value="i"></option></select>`,
|
||||||
|
).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select>\`)
|
||||||
|
if (true) {
|
||||||
|
_push(\`<option\${
|
||||||
|
_ssrRenderAttr("value", _ctx.i)
|
||||||
|
}\${
|
||||||
|
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||||
|
? _ssrLooseContain(_ctx.model, _ctx.i)
|
||||||
|
: _ssrLooseEqual(_ctx.model, _ctx.i))) ? " selected" : ""
|
||||||
|
}></option>\`)
|
||||||
|
} else {
|
||||||
|
_push(\`<!---->\`)
|
||||||
|
}
|
||||||
|
_push(\`</select></div>\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileWithWrapper(
|
compileWithWrapper(
|
||||||
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
|
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-ssr",
|
"description": "@vue/compiler-ssr",
|
||||||
"main": "dist/compiler-ssr.cjs.js",
|
"main": "dist/compiler-ssr.cjs.js",
|
||||||
"types": "dist/compiler-ssr.d.ts",
|
"types": "dist/compiler-ssr.d.ts",
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
createSequenceExpression,
|
createSequenceExpression,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createTemplateLiteral,
|
createTemplateLiteral,
|
||||||
|
findDir,
|
||||||
hasDynamicKeyVBind,
|
hasDynamicKeyVBind,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
isStaticExp,
|
isStaticExp,
|
||||||
|
@ -164,6 +165,9 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
} else if (directives.length && !node.children.length) {
|
} else if (directives.length && !node.children.length) {
|
||||||
|
// v-text directive has higher priority than the merged props
|
||||||
|
const vText = findDir(node, 'text')
|
||||||
|
if (!vText) {
|
||||||
const tempId = `_temp${context.temps++}`
|
const tempId = `_temp${context.temps++}`
|
||||||
propsExp.arguments = [
|
propsExp.arguments = [
|
||||||
createAssignmentExpression(
|
createAssignmentExpression(
|
||||||
|
@ -183,6 +187,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (needTagForRuntime) {
|
if (needTagForRuntime) {
|
||||||
propsExp.arguments.push(`"${node.tag}"`)
|
propsExp.arguments.push(`"${node.tag}"`)
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
type ExpressionNode,
|
type ExpressionNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
type PlainElementNode,
|
type PlainElementNode,
|
||||||
|
type TemplateChildNode,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createConditionalExpression,
|
createConditionalExpression,
|
||||||
createDOMCompilerError,
|
createDOMCompilerError,
|
||||||
|
@ -162,11 +163,18 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
checkDuplicatedValue()
|
checkDuplicatedValue()
|
||||||
node.children = [createInterpolation(model, model.loc)]
|
node.children = [createInterpolation(model, model.loc)]
|
||||||
} else if (node.tag === 'select') {
|
} else if (node.tag === 'select') {
|
||||||
node.children.forEach(child => {
|
const processChildren = (children: TemplateChildNode[]) => {
|
||||||
|
children.forEach(child => {
|
||||||
if (child.type === NodeTypes.ELEMENT) {
|
if (child.type === NodeTypes.ELEMENT) {
|
||||||
processOption(child as PlainElementNode)
|
processOption(child as PlainElementNode)
|
||||||
|
} else if (child.type === NodeTypes.FOR) {
|
||||||
|
processChildren(child.children)
|
||||||
|
} else if (child.type === NodeTypes.IF) {
|
||||||
|
child.branches.forEach(b => processChildren(b.children))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
processChildren(node.children)
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createDOMCompilerError(
|
createDOMCompilerError(
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { type ComputedRef, type Ref, computed, effect, ref } from '../src'
|
import type { ComputedRef, Ref } from '../src'
|
||||||
|
import { computed, effect, ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
|
declare module '../dist/reactivity.esm-browser.prod' {
|
||||||
|
function computed(...args: any[]): any
|
||||||
|
}
|
||||||
|
|
||||||
describe('computed', () => {
|
describe('computed', () => {
|
||||||
bench('create computed', () => {
|
bench('create computed', () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { type Ref, effect, ref } from '../src'
|
import type { Ref } from '../src'
|
||||||
|
import { effect, ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
describe('effect', () => {
|
describe('effect', () => {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { effect, reactive, shallowReadArray } from '../src'
|
import {
|
||||||
|
effect,
|
||||||
|
reactive,
|
||||||
|
shallowReadArray,
|
||||||
|
} from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { type ComputedRef, computed, reactive } from '../src'
|
import type { ComputedRef } from '../src'
|
||||||
|
import { computed, reactive } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
function createMap(obj: Record<string, any>) {
|
function createMap(obj: Record<string, any>) {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { reactive } from '../src'
|
import { reactive } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
bench('create reactive obj', () => {
|
bench('create reactive obj', () => {
|
||||||
reactive({ a: 1 })
|
reactive({ a: 1 })
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { ref } from '../src/index'
|
import { ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
describe('ref', () => {
|
describe('ref', () => {
|
||||||
bench('create ref', () => {
|
bench('create ref', () => {
|
||||||
|
|
|
@ -176,7 +176,7 @@ describe('reactivity/effect/scope', () => {
|
||||||
|
|
||||||
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
||||||
|
|
||||||
expect(scope.effects.length).toBe(1)
|
expect(scope.effects.length).toBe(0)
|
||||||
|
|
||||||
counter.num = 7
|
counter.num = 7
|
||||||
expect(dummy).toBe(0)
|
expect(dummy).toBe(0)
|
||||||
|
@ -322,4 +322,44 @@ describe('reactivity/effect/scope', () => {
|
||||||
scope.resume()
|
scope.resume()
|
||||||
expect(fnSpy).toHaveBeenCalledTimes(3)
|
expect(fnSpy).toHaveBeenCalledTimes(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('removing a watcher while stopping its effectScope', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const scope = effectScope()
|
||||||
|
let watcherCalls = 0
|
||||||
|
let cleanupCalls = 0
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
const stop1 = watch(count, () => {
|
||||||
|
watcherCalls++
|
||||||
|
})
|
||||||
|
watch(count, (val, old, onCleanup) => {
|
||||||
|
watcherCalls++
|
||||||
|
onCleanup(() => {
|
||||||
|
cleanupCalls++
|
||||||
|
stop1()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
watch(count, () => {
|
||||||
|
watcherCalls++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(watcherCalls).toBe(0)
|
||||||
|
expect(cleanupCalls).toBe(0)
|
||||||
|
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(watcherCalls).toBe(3)
|
||||||
|
expect(cleanupCalls).toBe(0)
|
||||||
|
|
||||||
|
scope.stop()
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(watcherCalls).toBe(3)
|
||||||
|
expect(cleanupCalls).toBe(1)
|
||||||
|
|
||||||
|
expect(scope.effects.length).toBe(0)
|
||||||
|
expect(scope.cleanups.length).toBe(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
@ -53,6 +53,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get(target: Target, key: string | symbol, receiver: object): any {
|
get(target: Target, key: string | symbol, receiver: object): any {
|
||||||
|
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
|
||||||
|
|
||||||
const isReadonly = this._isReadonly,
|
const isReadonly = this._isReadonly,
|
||||||
isShallow = this._isShallow
|
isShallow = this._isShallow
|
||||||
if (key === ReactiveFlags.IS_REACTIVE) {
|
if (key === ReactiveFlags.IS_REACTIVE) {
|
||||||
|
|
|
@ -119,18 +119,25 @@ export class EffectScope {
|
||||||
|
|
||||||
stop(fromParent?: boolean): void {
|
stop(fromParent?: boolean): void {
|
||||||
if (this._active) {
|
if (this._active) {
|
||||||
|
this._active = false
|
||||||
let i, l
|
let i, l
|
||||||
for (i = 0, l = this.effects.length; i < l; i++) {
|
for (i = 0, l = this.effects.length; i < l; i++) {
|
||||||
this.effects[i].stop()
|
this.effects[i].stop()
|
||||||
}
|
}
|
||||||
|
this.effects.length = 0
|
||||||
|
|
||||||
for (i = 0, l = this.cleanups.length; i < l; i++) {
|
for (i = 0, l = this.cleanups.length; i < l; i++) {
|
||||||
this.cleanups[i]()
|
this.cleanups[i]()
|
||||||
}
|
}
|
||||||
|
this.cleanups.length = 0
|
||||||
|
|
||||||
if (this.scopes) {
|
if (this.scopes) {
|
||||||
for (i = 0, l = this.scopes.length; i < l; i++) {
|
for (i = 0, l = this.scopes.length; i < l; i++) {
|
||||||
this.scopes[i].stop(true)
|
this.scopes[i].stop(true)
|
||||||
}
|
}
|
||||||
|
this.scopes.length = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// nested scope, dereference from parent to avoid memory leaks
|
// nested scope, dereference from parent to avoid memory leaks
|
||||||
if (!this.detached && this.parent && !fromParent) {
|
if (!this.detached && this.parent && !fromParent) {
|
||||||
// optimized O(1) removal
|
// optimized O(1) removal
|
||||||
|
@ -141,7 +148,6 @@ export class EffectScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.parent = undefined
|
this.parent = undefined
|
||||||
this._active = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ export function watch(
|
||||||
const scope = getCurrentScope()
|
const scope = getCurrentScope()
|
||||||
const watchHandle: WatchHandle = () => {
|
const watchHandle: WatchHandle = () => {
|
||||||
effect.stop()
|
effect.stop()
|
||||||
if (scope) {
|
if (scope && scope.active) {
|
||||||
remove(scope.effects, effect)
|
remove(scope.effects, effect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,6 @@ import {
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import {
|
import {
|
||||||
type DebuggerEvent,
|
type DebuggerEvent,
|
||||||
EffectFlags,
|
|
||||||
ITERATE_KEY,
|
ITERATE_KEY,
|
||||||
type Ref,
|
type Ref,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
|
@ -1341,7 +1340,7 @@ describe('api: watch', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
expect(instance!.scope.effects[0].flags & EffectFlags.ACTIVE).toBeFalsy()
|
expect(instance!.scope.effects.length).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
|
test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
|
||||||
|
|
|
@ -87,6 +87,49 @@ describe('renderer: teleport', () => {
|
||||||
`</div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('update before mounted with defer', async () => {
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const foo = ref('foo')
|
||||||
|
const Header = {
|
||||||
|
props: { foo: String },
|
||||||
|
setup(props: any) {
|
||||||
|
return () => h('div', props.foo)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Footer = {
|
||||||
|
setup() {
|
||||||
|
foo.value = 'bar'
|
||||||
|
return () => h('div', 'Footer')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createDOMApp({
|
||||||
|
render() {
|
||||||
|
return show.value
|
||||||
|
? [
|
||||||
|
h(
|
||||||
|
Teleport,
|
||||||
|
{ to: '#targetId', defer: true },
|
||||||
|
h(Header, { foo: foo.value }),
|
||||||
|
),
|
||||||
|
h(Footer),
|
||||||
|
h('div', { id: 'targetId' }),
|
||||||
|
]
|
||||||
|
: [h('div')]
|
||||||
|
},
|
||||||
|
}).mount(root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(`"<div></div>"`)
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--teleport start--><!--teleport end--><div>Footer</div><div id="targetId"><div>bar</div></div>"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function runSharedTests(deferMode: boolean) {
|
function runSharedTests(deferMode: boolean) {
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
onServerPrefetch,
|
||||||
openBlock,
|
openBlock,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
|
@ -518,6 +519,45 @@ describe('SSR hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with data-allow-mismatch component when using onServerPrefetch', async () => {
|
||||||
|
const Comp = {
|
||||||
|
template: `
|
||||||
|
<div>Comp2</div>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
let foo: any
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
const flag = ref(true)
|
||||||
|
foo = () => {
|
||||||
|
flag.value = false
|
||||||
|
}
|
||||||
|
onServerPrefetch(() => (flag.value = false))
|
||||||
|
return { flag }
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Comp,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<span data-allow-mismatch>
|
||||||
|
<Comp v-if="flag"></Comp>
|
||||||
|
</span>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
// hydrate
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.innerHTML = await renderToString(h(App))
|
||||||
|
createSSRApp(App).mount(container)
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
'<span data-allow-mismatch=""><div>Comp2</div></span>',
|
||||||
|
)
|
||||||
|
foo()
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
'<span data-allow-mismatch=""><!--v-if--></span>',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('Teleport unmount (full integration)', async () => {
|
test('Teleport unmount (full integration)', async () => {
|
||||||
const Comp1 = {
|
const Comp1 = {
|
||||||
template: `
|
template: `
|
||||||
|
@ -1284,6 +1324,84 @@ describe('SSR hydration', () => {
|
||||||
resolve({})
|
resolve({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//#12362
|
||||||
|
test('nested async wrapper', async () => {
|
||||||
|
const Toggle = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
r(
|
||||||
|
defineComponent({
|
||||||
|
setup(_, { slots }) {
|
||||||
|
const show = ref(false)
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
show.value = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return () =>
|
||||||
|
withDirectives(
|
||||||
|
h('div', null, [renderSlot(slots, 'default')]),
|
||||||
|
[[vShow, show.value]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const Wrapper = defineAsyncComponent(() => {
|
||||||
|
return new Promise(r => {
|
||||||
|
r(
|
||||||
|
defineComponent({
|
||||||
|
render(this: any) {
|
||||||
|
return renderSlot(this.$slots, 'default')
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
const fn = vi.fn()
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
fn()
|
||||||
|
count.value++
|
||||||
|
})
|
||||||
|
return () => h('div', count.value)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(Toggle, null, {
|
||||||
|
default: () =>
|
||||||
|
h(Wrapper, null, {
|
||||||
|
default: () =>
|
||||||
|
h(Wrapper, null, {
|
||||||
|
default: () => h(Child),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.innerHTML = await renderToString(h(App))
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<div style="display:none;"><!--[--><!--[--><!--[--><div>0</div><!--]--><!--]--><!--]--></div>"`,
|
||||||
|
)
|
||||||
|
|
||||||
|
createSSRApp(App).mount(root)
|
||||||
|
await nextTick()
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<div style=""><!--[--><!--[--><!--[--><div>1</div><!--]--><!--]--><!--]--></div>"`,
|
||||||
|
)
|
||||||
|
expect(fn).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('unmount async wrapper before load (fragment)', async () => {
|
test('unmount async wrapper before load (fragment)', async () => {
|
||||||
let resolve: any
|
let resolve: any
|
||||||
const AsyncComp = defineAsyncComponent(
|
const AsyncComp = defineAsyncComponent(
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
serializeInner as inner,
|
serializeInner as inner,
|
||||||
nextTick,
|
nextTick,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
|
onBeforeMount,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
openBlock,
|
openBlock,
|
||||||
|
@ -1199,7 +1200,7 @@ describe('renderer: optimized mode', () => {
|
||||||
createBlock('div', null, [
|
createBlock('div', null, [
|
||||||
createVNode('div', null, [
|
createVNode('div', null, [
|
||||||
cache[0] ||
|
cache[0] ||
|
||||||
(setBlockTracking(-1),
|
(setBlockTracking(-1, true),
|
||||||
((cache[0] = createVNode('div', null, [
|
((cache[0] = createVNode('div', null, [
|
||||||
createVNode(Child),
|
createVNode(Child),
|
||||||
])).cacheIndex = 0),
|
])).cacheIndex = 0),
|
||||||
|
@ -1233,4 +1234,64 @@ describe('renderer: optimized mode', () => {
|
||||||
expect(inner(root)).toBe('<!--v-if-->')
|
expect(inner(root)).toBe('<!--v-if-->')
|
||||||
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12371
|
||||||
|
test('unmount children when the user calls a compiled slot', async () => {
|
||||||
|
const beforeMountSpy = vi.fn()
|
||||||
|
const beforeUnmountSpy = vi.fn()
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onBeforeMount(beforeMountSpy)
|
||||||
|
onBeforeUnmount(beforeUnmountSpy)
|
||||||
|
return () => 'child'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = {
|
||||||
|
setup(_: any, { slots }: SetupContext) {
|
||||||
|
return () => (
|
||||||
|
openBlock(),
|
||||||
|
createElementBlock('section', null, [
|
||||||
|
(openBlock(),
|
||||||
|
createElementBlock('div', { key: 1 }, [
|
||||||
|
createTextVNode(slots.header!() ? 'foo' : 'bar', 1 /* TEXT */),
|
||||||
|
renderSlot(slots, 'content'),
|
||||||
|
])),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const app = createApp({
|
||||||
|
render() {
|
||||||
|
return show.value
|
||||||
|
? (openBlock(),
|
||||||
|
createBlock(Wrapper, null, {
|
||||||
|
header: withCtx(() => [createVNode({})]),
|
||||||
|
content: withCtx(() => [createVNode(Child)]),
|
||||||
|
_: 1,
|
||||||
|
}))
|
||||||
|
: createCommentVNode('v-if', true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.mount(root)
|
||||||
|
expect(inner(root)).toMatchInlineSnapshot(`"<!--v-if-->"`)
|
||||||
|
expect(beforeMountSpy).toHaveBeenCalledTimes(0)
|
||||||
|
expect(beforeUnmountSpy).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toMatchInlineSnapshot(
|
||||||
|
`"<section><div>foochild</div></section>"`,
|
||||||
|
)
|
||||||
|
expect(beforeMountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
show.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toBe('<!--v-if-->')
|
||||||
|
expect(beforeUnmountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import {
|
import {
|
||||||
|
KeepAlive,
|
||||||
|
defineAsyncComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
@ -538,4 +540,68 @@ describe('api: template refs', () => {
|
||||||
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
|
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with async component which nested in KeepAlive', async () => {
|
||||||
|
const AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(resolve =>
|
||||||
|
setTimeout(() =>
|
||||||
|
resolve(
|
||||||
|
defineComponent({
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
name: 'AsyncComp',
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
name: 'Comp',
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggle = ref(false)
|
||||||
|
const instanceRef = ref<any>(null)
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
render: () => {
|
||||||
|
return h(KeepAlive, () =>
|
||||||
|
toggle.value
|
||||||
|
? h(AsyncComp, { ref: instanceRef })
|
||||||
|
: h(Comp, { ref: instanceRef }),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(App), root)
|
||||||
|
expect(instanceRef.value.name).toBe('Comp')
|
||||||
|
|
||||||
|
// switch to async component
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value).toBe(null)
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
expect(instanceRef.value.name).toBe('AsyncComp')
|
||||||
|
|
||||||
|
// switch back to normal component
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value.name).toBe('Comp')
|
||||||
|
|
||||||
|
// switch to async component again
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value.name).toBe('AsyncComp')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -629,7 +629,7 @@ describe('vnode', () => {
|
||||||
const vnode =
|
const vnode =
|
||||||
(openBlock(),
|
(openBlock(),
|
||||||
createBlock('div', null, [
|
createBlock('div', null, [
|
||||||
setBlockTracking(-1),
|
setBlockTracking(-1, true),
|
||||||
(vnode1 = (openBlock(), createBlock('div'))),
|
(vnode1 = (openBlock(), createBlock('div'))),
|
||||||
setBlockTracking(1),
|
setBlockTracking(1),
|
||||||
vnode1,
|
vnode1,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-core",
|
"name": "@vue/runtime-core",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/runtime-core",
|
"description": "@vue/runtime-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-core.esm-bundler.js",
|
"module": "dist/runtime-core.esm-bundler.js",
|
||||||
|
|
|
@ -149,9 +149,7 @@ export function defineEmits() {
|
||||||
return null as any
|
return null as any
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentTypeEmits =
|
export type ComponentTypeEmits = ((...args: any[]) => any) | Record<string, any>
|
||||||
| ((...args: any[]) => any)
|
|
||||||
| Record<string, any[]>
|
|
||||||
|
|
||||||
type RecordToUnion<T extends Record<string, any>> = T[keyof T]
|
type RecordToUnion<T extends Record<string, any>> = T[keyof T]
|
||||||
|
|
||||||
|
|
|
@ -198,8 +198,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
setTransitionHooks(innerChild, enterHooks)
|
setTransitionHooks(innerChild, enterHooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldChild = instance.subTree
|
let oldInnerChild = instance.subTree && getInnerChild(instance.subTree)
|
||||||
const oldInnerChild = oldChild && getInnerChild(oldChild)
|
|
||||||
|
|
||||||
// handle mode
|
// handle mode
|
||||||
if (
|
if (
|
||||||
|
@ -208,7 +207,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
!isSameVNodeType(innerChild, oldInnerChild) &&
|
!isSameVNodeType(innerChild, oldInnerChild) &&
|
||||||
recursiveGetSubtree(instance).type !== Comment
|
recursiveGetSubtree(instance).type !== Comment
|
||||||
) {
|
) {
|
||||||
const leavingHooks = resolveTransitionHooks(
|
let leavingHooks = resolveTransitionHooks(
|
||||||
oldInnerChild,
|
oldInnerChild,
|
||||||
rawProps,
|
rawProps,
|
||||||
state,
|
state,
|
||||||
|
@ -228,6 +227,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
instance.update()
|
instance.update()
|
||||||
}
|
}
|
||||||
delete leavingHooks.afterLeave
|
delete leavingHooks.afterLeave
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
return emptyPlaceholder(child)
|
return emptyPlaceholder(child)
|
||||||
} else if (mode === 'in-out' && innerChild.type !== Comment) {
|
} else if (mode === 'in-out' && innerChild.type !== Comment) {
|
||||||
|
@ -238,18 +238,27 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
) => {
|
) => {
|
||||||
const leavingVNodesCache = getLeavingNodesForType(
|
const leavingVNodesCache = getLeavingNodesForType(
|
||||||
state,
|
state,
|
||||||
oldInnerChild,
|
oldInnerChild!,
|
||||||
)
|
)
|
||||||
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
|
leavingVNodesCache[String(oldInnerChild!.key)] = oldInnerChild!
|
||||||
// early removal callback
|
// early removal callback
|
||||||
el[leaveCbKey] = () => {
|
el[leaveCbKey] = () => {
|
||||||
earlyRemove()
|
earlyRemove()
|
||||||
el[leaveCbKey] = undefined
|
el[leaveCbKey] = undefined
|
||||||
delete enterHooks.delayedLeave
|
delete enterHooks.delayedLeave
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
enterHooks.delayedLeave = delayedLeave
|
enterHooks.delayedLeave = () => {
|
||||||
|
delayedLeave()
|
||||||
|
delete enterHooks.delayedLeave
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
oldInnerChild = undefined
|
||||||
|
}
|
||||||
|
} else if (oldInnerChild) {
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return child
|
return child
|
||||||
|
|
|
@ -164,11 +164,32 @@ export const TeleportImpl = {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTeleportDeferred(n2.props)) {
|
if (isTeleportDeferred(n2.props)) {
|
||||||
queuePostRenderEffect(mountToTarget, parentSuspense)
|
queuePostRenderEffect(() => {
|
||||||
|
mountToTarget()
|
||||||
|
n2.el!.__isMounted = true
|
||||||
|
}, parentSuspense)
|
||||||
} else {
|
} else {
|
||||||
mountToTarget()
|
mountToTarget()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (isTeleportDeferred(n2.props) && !n1.el!.__isMounted) {
|
||||||
|
queuePostRenderEffect(() => {
|
||||||
|
TeleportImpl.process(
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
container,
|
||||||
|
anchor,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense,
|
||||||
|
namespace,
|
||||||
|
slotScopeIds,
|
||||||
|
optimized,
|
||||||
|
internals,
|
||||||
|
)
|
||||||
|
delete n1.el!.__isMounted
|
||||||
|
}, parentSuspense)
|
||||||
|
return
|
||||||
|
}
|
||||||
// update content
|
// update content
|
||||||
n2.el = n1.el
|
n2.el = n1.el
|
||||||
n2.targetStart = n1.targetStart
|
n2.targetStart = n1.targetStart
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
normalizeVNode,
|
normalizeVNode,
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import { flushPostFlushCbs } from './scheduler'
|
import { flushPostFlushCbs } from './scheduler'
|
||||||
import type { ComponentInternalInstance } from './component'
|
import type { ComponentInternalInstance, ComponentOptions } from './component'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import {
|
import {
|
||||||
|
@ -41,6 +41,7 @@ import {
|
||||||
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
import { isReactive } from '@vue/reactivity'
|
import { isReactive } from '@vue/reactivity'
|
||||||
|
import { updateHOCHostEl } from './componentRenderUtils'
|
||||||
|
|
||||||
export type RootHydrateFunction = (
|
export type RootHydrateFunction = (
|
||||||
vnode: VNode<Node, Element>,
|
vnode: VNode<Node, Element>,
|
||||||
|
@ -307,7 +308,10 @@ export function createHydrationFunctions(
|
||||||
// if component is async, it may get moved / unmounted before its
|
// if component is async, it may get moved / unmounted before its
|
||||||
// inner component is loaded, so we need to give it a placeholder
|
// inner component is loaded, so we need to give it a placeholder
|
||||||
// vnode that matches its adopted DOM.
|
// vnode that matches its adopted DOM.
|
||||||
if (isAsyncWrapper(vnode)) {
|
if (
|
||||||
|
isAsyncWrapper(vnode) &&
|
||||||
|
!(vnode.type as ComponentOptions).__asyncResolved
|
||||||
|
) {
|
||||||
let subTree
|
let subTree
|
||||||
if (isFragmentStart) {
|
if (isFragmentStart) {
|
||||||
subTree = createVNode(Fragment)
|
subTree = createVNode(Fragment)
|
||||||
|
@ -716,6 +720,11 @@ export function createHydrationFunctions(
|
||||||
getContainerType(container),
|
getContainerType(container),
|
||||||
slotScopeIds,
|
slotScopeIds,
|
||||||
)
|
)
|
||||||
|
// the component vnode's el should be updated when a mismatch occurs.
|
||||||
|
if (parentComponent) {
|
||||||
|
parentComponent.vnode.el = vnode.el
|
||||||
|
updateHOCHostEl(parentComponent, vnode.el)
|
||||||
|
}
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { isRef, toRaw } from '@vue/reactivity'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import type { SchedulerJob } from './scheduler'
|
import type { SchedulerJob } from './scheduler'
|
||||||
import { queuePostRenderEffect } from './renderer'
|
import { queuePostRenderEffect } from './renderer'
|
||||||
import { getComponentPublicInstance } from './component'
|
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
||||||
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,8 +42,18 @@ export function setRef(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAsyncWrapper(vnode) && !isUnmount) {
|
if (isAsyncWrapper(vnode) && !isUnmount) {
|
||||||
// when mounting async components, nothing needs to be done,
|
// #4999 if an async component already resolved and cached by KeepAlive,
|
||||||
// because the template ref is forwarded to inner component
|
// we need to set the ref to inner component
|
||||||
|
if (
|
||||||
|
vnode.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE &&
|
||||||
|
(vnode.type as ComponentOptions).__asyncResolved &&
|
||||||
|
vnode.component!.subTree.component
|
||||||
|
) {
|
||||||
|
setRef(rawRef, oldRawRef, parentSuspense, vnode.component!.subTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, nothing needs to be done because the template ref
|
||||||
|
// is forwarded to inner component
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -301,7 +301,7 @@ export let isBlockTreeEnabled = 1
|
||||||
*
|
*
|
||||||
* ``` js
|
* ``` js
|
||||||
* _cache[1] || (
|
* _cache[1] || (
|
||||||
* setBlockTracking(-1),
|
* setBlockTracking(-1, true),
|
||||||
* _cache[1] = createVNode(...),
|
* _cache[1] = createVNode(...),
|
||||||
* setBlockTracking(1),
|
* setBlockTracking(1),
|
||||||
* _cache[1]
|
* _cache[1]
|
||||||
|
@ -310,11 +310,11 @@ export let isBlockTreeEnabled = 1
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function setBlockTracking(value: number): void {
|
export function setBlockTracking(value: number, inVOnce = false): void {
|
||||||
isBlockTreeEnabled += value
|
isBlockTreeEnabled += value
|
||||||
if (value < 0 && currentBlock) {
|
if (value < 0 && currentBlock && inVOnce) {
|
||||||
// mark current block so it doesn't take fast path and skip possible
|
// mark current block so it doesn't take fast path and skip possible
|
||||||
// nested components duriung unmount
|
// nested components during unmount
|
||||||
currentBlock.hasOnce = true
|
currentBlock.hasOnce = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -396,6 +396,38 @@ describe('defineCustomElement', () => {
|
||||||
expect(e.value).toBe('hi')
|
expect(e.value).toBe('hi')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12214
|
||||||
|
test('Boolean prop with default true', async () => {
|
||||||
|
const E = defineCustomElement({
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return String(this.foo)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
customElements.define('my-el-default-true', E)
|
||||||
|
container.innerHTML = `<my-el-default-true></my-el-default-true>`
|
||||||
|
const e = container.childNodes[0] as HTMLElement & { foo: any },
|
||||||
|
shadowRoot = e.shadowRoot as ShadowRoot
|
||||||
|
expect(shadowRoot.innerHTML).toBe('true')
|
||||||
|
e.foo = undefined
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('true')
|
||||||
|
e.foo = false
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('false')
|
||||||
|
e.foo = null
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('null')
|
||||||
|
e.foo = ''
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('true')
|
||||||
|
})
|
||||||
|
|
||||||
test('support direct setup function syntax with extra options', () => {
|
test('support direct setup function syntax with extra options', () => {
|
||||||
const E = defineCustomElement(
|
const E = defineCustomElement(
|
||||||
props => {
|
props => {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
defineCustomElement,
|
defineCustomElement,
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
onMounted,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
@ -384,6 +385,44 @@ describe('useCssVars', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with delay mount child', async () => {
|
||||||
|
const state = reactive({ color: 'red' })
|
||||||
|
const value = ref(false)
|
||||||
|
const root = document.createElement('div')
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
const childEl = root.children[0]
|
||||||
|
expect(getComputedStyle(childEl!).getPropertyValue(`--color`)).toBe(
|
||||||
|
`red`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return () => h('div', { id: 'childId' })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
useCssVars(() => state)
|
||||||
|
return () => (value.value ? h(Child) : [h('span')])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render(h(App), root)
|
||||||
|
await nextTick()
|
||||||
|
// css vars use with fallback tree
|
||||||
|
for (const c of [].slice.call(root.children as any)) {
|
||||||
|
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe(`red`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount child
|
||||||
|
value.value = true
|
||||||
|
await nextTick()
|
||||||
|
for (const c of [].slice.call(root.children as any)) {
|
||||||
|
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe(`red`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// #8826
|
// #8826
|
||||||
test('with custom element', async () => {
|
test('with custom element', async () => {
|
||||||
const state = reactive({ color: 'red' })
|
const state = reactive({ color: 'red' })
|
||||||
|
@ -405,4 +444,25 @@ describe('useCssVars', () => {
|
||||||
`<css-vars-ce style="--color: red;"></css-vars-ce>`,
|
`<css-vars-ce style="--color: red;"></css-vars-ce>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should set vars before child component onMount hook', () => {
|
||||||
|
const state = reactive({ color: 'red' })
|
||||||
|
const root = document.createElement('div')
|
||||||
|
let colorInOnMount
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
useCssVars(() => state)
|
||||||
|
onMounted(() => {
|
||||||
|
colorInOnMount = (
|
||||||
|
root.children[0] as HTMLElement
|
||||||
|
).style.getPropertyValue(`--color`)
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render(h(App), root)
|
||||||
|
expect(colorInOnMount).toBe(`red`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-dom",
|
"name": "@vue/runtime-dom",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/runtime-dom",
|
"description": "@vue/runtime-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-dom.esm-bundler.js",
|
"module": "dist/runtime-dom.esm-bundler.js",
|
||||||
|
|
|
@ -505,6 +505,8 @@ export class VueElement
|
||||||
}
|
}
|
||||||
// reflect
|
// reflect
|
||||||
if (shouldReflect) {
|
if (shouldReflect) {
|
||||||
|
const ob = this._ob
|
||||||
|
ob && ob.disconnect()
|
||||||
if (val === true) {
|
if (val === true) {
|
||||||
this.setAttribute(hyphenate(key), '')
|
this.setAttribute(hyphenate(key), '')
|
||||||
} else if (typeof val === 'string' || typeof val === 'number') {
|
} else if (typeof val === 'string' || typeof val === 'number') {
|
||||||
|
@ -512,6 +514,7 @@ export class VueElement
|
||||||
} else if (!val) {
|
} else if (!val) {
|
||||||
this.removeAttribute(hyphenate(key))
|
this.removeAttribute(hyphenate(key))
|
||||||
}
|
}
|
||||||
|
ob && ob.observe(this, { attributes: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,13 @@ export function resolveTransitionProps(
|
||||||
onAppearCancelled = onEnterCancelled,
|
onAppearCancelled = onEnterCancelled,
|
||||||
} = baseProps
|
} = baseProps
|
||||||
|
|
||||||
const finishEnter = (el: Element, isAppear: boolean, done?: () => void) => {
|
const finishEnter = (
|
||||||
|
el: Element & { _enterCancelled?: boolean },
|
||||||
|
isAppear: boolean,
|
||||||
|
done?: () => void,
|
||||||
|
isCancelled?: boolean,
|
||||||
|
) => {
|
||||||
|
el._enterCancelled = isCancelled
|
||||||
removeTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
removeTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
||||||
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
|
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
|
||||||
done && done()
|
done && done()
|
||||||
|
@ -240,7 +246,10 @@ export function resolveTransitionProps(
|
||||||
},
|
},
|
||||||
onEnter: makeEnterHook(false),
|
onEnter: makeEnterHook(false),
|
||||||
onAppear: makeEnterHook(true),
|
onAppear: makeEnterHook(true),
|
||||||
onLeave(el: Element & { _isLeaving?: boolean }, done) {
|
onLeave(
|
||||||
|
el: Element & { _isLeaving?: boolean; _enterCancelled?: boolean },
|
||||||
|
done,
|
||||||
|
) {
|
||||||
el._isLeaving = true
|
el._isLeaving = true
|
||||||
const resolve = () => finishLeave(el, done)
|
const resolve = () => finishLeave(el, done)
|
||||||
addTransitionClass(el, leaveFromClass)
|
addTransitionClass(el, leaveFromClass)
|
||||||
|
@ -249,9 +258,14 @@ export function resolveTransitionProps(
|
||||||
}
|
}
|
||||||
// add *-leave-active class before reflow so in the case of a cancelled enter transition
|
// add *-leave-active class before reflow so in the case of a cancelled enter transition
|
||||||
// the css will not get the final state (#10677)
|
// the css will not get the final state (#10677)
|
||||||
addTransitionClass(el, leaveActiveClass)
|
if (!el._enterCancelled) {
|
||||||
// force reflow so *-leave-from classes immediately take effect (#2593)
|
// force reflow so *-leave-from classes immediately take effect (#2593)
|
||||||
forceReflow()
|
forceReflow()
|
||||||
|
addTransitionClass(el, leaveActiveClass)
|
||||||
|
} else {
|
||||||
|
addTransitionClass(el, leaveActiveClass)
|
||||||
|
forceReflow()
|
||||||
|
}
|
||||||
nextFrame(() => {
|
nextFrame(() => {
|
||||||
if (!el._isLeaving) {
|
if (!el._isLeaving) {
|
||||||
// cancelled
|
// cancelled
|
||||||
|
@ -269,11 +283,11 @@ export function resolveTransitionProps(
|
||||||
callHook(onLeave, [el, resolve])
|
callHook(onLeave, [el, resolve])
|
||||||
},
|
},
|
||||||
onEnterCancelled(el) {
|
onEnterCancelled(el) {
|
||||||
finishEnter(el, false)
|
finishEnter(el, false, undefined, true)
|
||||||
callHook(onEnterCancelled, [el])
|
callHook(onEnterCancelled, [el])
|
||||||
},
|
},
|
||||||
onAppearCancelled(el) {
|
onAppearCancelled(el) {
|
||||||
finishEnter(el, true)
|
finishEnter(el, true, undefined, true)
|
||||||
callHook(onAppearCancelled, [el])
|
callHook(onAppearCancelled, [el])
|
||||||
},
|
},
|
||||||
onLeaveCancelled(el) {
|
onLeaveCancelled(el) {
|
||||||
|
|
|
@ -3,13 +3,14 @@ import {
|
||||||
Static,
|
Static,
|
||||||
type VNode,
|
type VNode,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
onBeforeMount,
|
onBeforeUpdate,
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
|
queuePostFlushCb,
|
||||||
warn,
|
warn,
|
||||||
watchPostEffect,
|
watch,
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { ShapeFlags } from '@vue/shared'
|
import { NOOP, ShapeFlags } from '@vue/shared'
|
||||||
|
|
||||||
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
|
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
|
||||||
/**
|
/**
|
||||||
|
@ -48,11 +49,15 @@ export function useCssVars(getter: (ctx: any) => Record<string, string>): void {
|
||||||
updateTeleports(vars)
|
updateTeleports(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
// handle cases where child component root is affected
|
||||||
watchPostEffect(setVars)
|
// and triggers reflow in onMounted
|
||||||
|
onBeforeUpdate(() => {
|
||||||
|
queuePostFlushCb(setVars)
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// run setVars synchronously here, but run as post-effect on changes
|
||||||
|
watch(setVars, NOOP, { flush: 'post' })
|
||||||
const ob = new MutationObserver(setVars)
|
const ob = new MutationObserver(setVars)
|
||||||
ob.observe(instance.subTree.el!.parentNode, { childList: true })
|
ob.observe(instance.subTree.el!.parentNode, { childList: true })
|
||||||
onUnmounted(() => ob.disconnect())
|
onUnmounted(() => ob.disconnect())
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
|
|
||||||
import { createBuffer } from '../src/render'
|
import { createBuffer as _createBuffer } from '../src/render'
|
||||||
|
|
||||||
|
// move to local const to avoid import access overhead
|
||||||
|
// https://github.com/vitest-dev/vitest/issues/6903
|
||||||
|
const createBuffer = _createBuffer
|
||||||
|
|
||||||
describe('createBuffer', () => {
|
describe('createBuffer', () => {
|
||||||
let stringBuffer = createBuffer()
|
let stringBuffer = createBuffer()
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
|
|
||||||
import { type SSRBuffer, createBuffer } from '../src/render'
|
import { type SSRBuffer, createBuffer } from '../src/render'
|
||||||
import { unrollBuffer } from '../src/renderToString'
|
import { unrollBuffer as _unrollBuffer } from '../src/renderToString'
|
||||||
|
|
||||||
|
// move to local const to avoid import access overhead
|
||||||
|
// https://github.com/vitest-dev/vitest/issues/6903
|
||||||
|
const unrollBuffer = _unrollBuffer
|
||||||
|
|
||||||
function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
||||||
const buffer = createBuffer()
|
const buffer = createBuffer()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/server-renderer",
|
"name": "@vue/server-renderer",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "@vue/server-renderer",
|
"description": "@vue/server-renderer",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/server-renderer.esm-bundler.js",
|
"module": "dist/server-renderer.esm-bundler.js",
|
||||||
|
|
|
@ -153,10 +153,10 @@ describe('normalizeStyle', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('stringifyStyle', () => {
|
describe('stringifyStyle', () => {
|
||||||
test('should return empty string for undefined or string styles', () => {
|
test('should return empty string for undefined', () => {
|
||||||
expect(stringifyStyle(undefined)).toBe('')
|
expect(stringifyStyle(undefined)).toBe('')
|
||||||
expect(stringifyStyle('')).toBe('')
|
expect(stringifyStyle('')).toBe('')
|
||||||
expect(stringifyStyle('color: blue;')).toBe('')
|
expect(stringifyStyle('color: blue;')).toBe('color: blue;')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should return valid CSS string for normalized style object', () => {
|
test('should return valid CSS string for normalized style object', () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/shared",
|
"name": "@vue/shared",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "internal utils shared across @vue packages",
|
"description": "internal utils shared across @vue packages",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/shared.esm-bundler.js",
|
"module": "dist/shared.esm-bundler.js",
|
||||||
|
|
|
@ -45,10 +45,10 @@ export function parseStringStyle(cssText: string): NormalizedStyle {
|
||||||
export function stringifyStyle(
|
export function stringifyStyle(
|
||||||
styles: NormalizedStyle | string | undefined,
|
styles: NormalizedStyle | string | undefined,
|
||||||
): string {
|
): string {
|
||||||
|
if (!styles) return ''
|
||||||
|
if (isString(styles)) return styles
|
||||||
|
|
||||||
let ret = ''
|
let ret = ''
|
||||||
if (!styles || isString(styles)) {
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
for (const key in styles) {
|
for (const key in styles) {
|
||||||
const value = styles[key]
|
const value = styles[key]
|
||||||
if (isString(value) || typeof value === 'number') {
|
if (isString(value) || typeof value === 'number') {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compat",
|
"name": "@vue/compat",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "Vue 3 compatibility build for Vue 2",
|
"description": "Vue 3 compatibility build for Vue 2",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/vue.runtime.esm-bundler.js",
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
|
|
|
@ -3,7 +3,7 @@ import path from 'node:path'
|
||||||
import { Transition, createApp, h, nextTick, ref } from 'vue'
|
import { Transition, createApp, h, nextTick, ref } from 'vue'
|
||||||
|
|
||||||
describe('e2e: Transition', () => {
|
describe('e2e: Transition', () => {
|
||||||
const { page, html, classList, isVisible, timeout, nextFrame, click } =
|
const { page, html, classList, style, isVisible, timeout, nextFrame, click } =
|
||||||
setupPuppeteer()
|
setupPuppeteer()
|
||||||
const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
|
const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
|
||||||
|
|
||||||
|
@ -2986,6 +2986,55 @@ describe('e2e: Transition', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('reflow after *-leave-from before *-leave-active', async () => {
|
||||||
|
await page().evaluate(() => {
|
||||||
|
const { createApp, ref } = (window as any).Vue
|
||||||
|
createApp({
|
||||||
|
template: `
|
||||||
|
<div id="container">
|
||||||
|
<transition name="test-reflow">
|
||||||
|
<div v-if="toggle" class="test-reflow">content</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
<button id="toggleBtn" @click="click">button</button>
|
||||||
|
`,
|
||||||
|
setup: () => {
|
||||||
|
const toggle = ref(false)
|
||||||
|
const click = () => (toggle.value = !toggle.value)
|
||||||
|
return {
|
||||||
|
toggle,
|
||||||
|
click,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
})
|
||||||
|
|
||||||
|
// if transition starts while there's v-leave-active added along with v-leave-from, its bad, it has to start when it doesnt have the v-leave-from
|
||||||
|
|
||||||
|
// enter
|
||||||
|
await classWhenTransitionStart()
|
||||||
|
await transitionFinish()
|
||||||
|
|
||||||
|
// leave
|
||||||
|
expect(await classWhenTransitionStart()).toStrictEqual([
|
||||||
|
'test-reflow',
|
||||||
|
'test-reflow-leave-from',
|
||||||
|
'test-reflow-leave-active',
|
||||||
|
])
|
||||||
|
|
||||||
|
expect(await style('.test-reflow', 'opacity')).toStrictEqual('0.9')
|
||||||
|
|
||||||
|
await nextFrame()
|
||||||
|
expect(await classList('.test-reflow')).toStrictEqual([
|
||||||
|
'test-reflow',
|
||||||
|
'test-reflow-leave-active',
|
||||||
|
'test-reflow-leave-to',
|
||||||
|
])
|
||||||
|
|
||||||
|
await transitionFinish()
|
||||||
|
expect(await html('#container')).toBe('<!--v-if-->')
|
||||||
|
})
|
||||||
|
|
||||||
test('warn when used on multiple elements', async () => {
|
test('warn when used on multiple elements', async () => {
|
||||||
createApp({
|
createApp({
|
||||||
render() {
|
render() {
|
||||||
|
@ -3121,4 +3170,124 @@ describe('e2e: Transition', () => {
|
||||||
},
|
},
|
||||||
E2E_TIMEOUT,
|
E2E_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// https://github.com/vuejs/core/issues/12181#issuecomment-2414380955
|
||||||
|
describe('not leaking', async () => {
|
||||||
|
test('switching VNodes', async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref, nextTick } = (window as any).Vue
|
||||||
|
const empty = ref(true)
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Child: {
|
||||||
|
setup: () => {
|
||||||
|
// Big arrays kick GC earlier
|
||||||
|
const test = ref([...Array(30_000_000)].map((_, i) => ({ i })))
|
||||||
|
// TODO: Use a diferent TypeScript env for testing
|
||||||
|
// @ts-expect-error - Custom property and same lib as runtime is used
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<p>{{ test.length }}</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
Empty: {
|
||||||
|
template: '<div></div>',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<transition>
|
||||||
|
<component :is="empty ? 'Empty' : 'Child'" />
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
return { empty }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
empty.value = false
|
||||||
|
await nextTick()
|
||||||
|
empty.value = true
|
||||||
|
await nextTick()
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error - Custom property
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
// https://github.com/vuejs/core/issues/12181#issue-2588232334
|
||||||
|
test('switching deep vnodes edge case', async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref, nextTick } = (window as any).Vue
|
||||||
|
const shown = ref(false)
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Child: {
|
||||||
|
setup: () => {
|
||||||
|
// Big arrays kick GC earlier
|
||||||
|
const test = ref([...Array(30_000_000)].map((_, i) => ({ i })))
|
||||||
|
// TODO: Use a diferent TypeScript env for testing
|
||||||
|
// @ts-expect-error - Custom property and same lib as runtime is used
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<p>{{ test.length }}</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
Wrapper: {
|
||||||
|
template: `
|
||||||
|
<transition>
|
||||||
|
<div v-if="true">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<button id="toggleBtn" @click="shown = !shown">{{ shown ? 'Hide' : 'Show' }}</button>
|
||||||
|
<Wrapper>
|
||||||
|
<Child v-if="shown" />
|
||||||
|
<div v-else></div>
|
||||||
|
</Wrapper>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
return { shown }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
shown.value = true
|
||||||
|
await nextTick()
|
||||||
|
shown.value = false
|
||||||
|
await nextTick()
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error - Custom property
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -39,6 +39,7 @@ interface PuppeteerUtils {
|
||||||
value(selector: string): Promise<string>
|
value(selector: string): Promise<string>
|
||||||
html(selector: string): Promise<string>
|
html(selector: string): Promise<string>
|
||||||
classList(selector: string): Promise<string[]>
|
classList(selector: string): Promise<string[]>
|
||||||
|
style(selector: string, property: keyof CSSStyleDeclaration): Promise<any>
|
||||||
children(selector: string): Promise<any[]>
|
children(selector: string): Promise<any[]>
|
||||||
isVisible(selector: string): Promise<boolean>
|
isVisible(selector: string): Promise<boolean>
|
||||||
isChecked(selector: string): Promise<boolean>
|
isChecked(selector: string): Promise<boolean>
|
||||||
|
@ -120,6 +121,19 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
|
||||||
return page.$eval(selector, (node: any) => [...node.children])
|
return page.$eval(selector, (node: any) => [...node.children])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function style(
|
||||||
|
selector: string,
|
||||||
|
property: keyof CSSStyleDeclaration,
|
||||||
|
): Promise<any> {
|
||||||
|
return await page.$eval(
|
||||||
|
selector,
|
||||||
|
(node, property) => {
|
||||||
|
return window.getComputedStyle(node)[property]
|
||||||
|
},
|
||||||
|
property,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function isVisible(selector: string): Promise<boolean> {
|
async function isVisible(selector: string): Promise<boolean> {
|
||||||
const display = await page.$eval(selector, node => {
|
const display = await page.$eval(selector, node => {
|
||||||
return window.getComputedStyle(node).display
|
return window.getComputedStyle(node).display
|
||||||
|
@ -195,6 +209,7 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
|
||||||
value,
|
value,
|
||||||
html,
|
html,
|
||||||
classList,
|
classList,
|
||||||
|
style,
|
||||||
children,
|
children,
|
||||||
isVisible,
|
isVisible,
|
||||||
isChecked,
|
isChecked,
|
||||||
|
|
|
@ -16,11 +16,21 @@
|
||||||
.test-appear,
|
.test-appear,
|
||||||
.test-enter,
|
.test-enter,
|
||||||
.test-leave-active,
|
.test-leave-active,
|
||||||
|
.test-reflow-enter,
|
||||||
|
.test-reflow-leave-to,
|
||||||
.hello,
|
.hello,
|
||||||
.bye.active,
|
.bye.active,
|
||||||
.changed-enter {
|
.changed-enter {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
.test-reflow-leave-active,
|
||||||
|
.test-reflow-enter-active {
|
||||||
|
-webkit-transition: opacity 50ms ease;
|
||||||
|
transition: opacity 50ms ease;
|
||||||
|
}
|
||||||
|
.test-reflow-leave-from {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
.test-anim-enter-active {
|
.test-anim-enter-active {
|
||||||
animation: test-enter 50ms;
|
animation: test-enter 50ms;
|
||||||
-webkit-animation: test-enter 50ms;
|
-webkit-animation: test-enter 50ms;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "vue",
|
"name": "vue",
|
||||||
"version": "3.5.12",
|
"version": "3.5.13",
|
||||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/vue.runtime.esm-bundler.js",
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
|
|
|
@ -358,8 +358,8 @@ importers:
|
||||||
specifier: ^6.0.0
|
specifier: ^6.0.0
|
||||||
version: 6.0.0(postcss@8.4.48)
|
version: 6.0.0(postcss@8.4.48)
|
||||||
postcss-selector-parser:
|
postcss-selector-parser:
|
||||||
specifier: ^6.1.2
|
specifier: ^7.0.0
|
||||||
version: 6.1.2
|
version: 7.0.0
|
||||||
pug:
|
pug:
|
||||||
specifier: ^3.0.3
|
specifier: ^3.0.3
|
||||||
version: 3.0.3
|
version: 3.0.3
|
||||||
|
@ -3027,6 +3027,10 @@ packages:
|
||||||
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
postcss-selector-parser@7.0.0:
|
||||||
|
resolution: {integrity: sha512-9RbEr1Y7FFfptd/1eEdntyjMwLeghW1bHX9GWjXo19vx4ytPQhANltvVxDggzJl7mnWM+dX28kb6cyS/4iQjlQ==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
postcss-value-parser@4.2.0:
|
postcss-value-parser@4.2.0:
|
||||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||||
|
|
||||||
|
@ -6214,6 +6218,11 @@ snapshots:
|
||||||
cssesc: 3.0.0
|
cssesc: 3.0.0
|
||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
|
postcss-selector-parser@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
cssesc: 3.0.0
|
||||||
|
util-deprecate: 1.0.2
|
||||||
|
|
||||||
postcss-value-parser@4.2.0: {}
|
postcss-value-parser@4.2.0: {}
|
||||||
|
|
||||||
postcss@8.4.48:
|
postcss@8.4.48:
|
||||||
|
|
|
@ -46,6 +46,12 @@ const pkg = require(resolve(`package.json`))
|
||||||
const packageOptions = pkg.buildOptions || {}
|
const packageOptions = pkg.buildOptions || {}
|
||||||
const name = packageOptions.filename || path.basename(packageDir)
|
const name = packageOptions.filename || path.basename(packageDir)
|
||||||
|
|
||||||
|
const banner = `/**
|
||||||
|
* ${pkg.name} v${masterVersion}
|
||||||
|
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
||||||
|
* @license MIT
|
||||||
|
**/`
|
||||||
|
|
||||||
const [enumPlugin, enumDefines] = inlineEnums()
|
const [enumPlugin, enumDefines] = inlineEnums()
|
||||||
|
|
||||||
/** @type {Record<PackageFormat, OutputOptions>} */
|
/** @type {Record<PackageFormat, OutputOptions>} */
|
||||||
|
@ -138,11 +144,7 @@ function createConfig(format, output, plugins = []) {
|
||||||
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
|
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
|
||||||
!packageOptions.enableNonBrowserBranches
|
!packageOptions.enableNonBrowserBranches
|
||||||
|
|
||||||
output.banner = `/**
|
output.banner = banner
|
||||||
* ${pkg.name} v${masterVersion}
|
|
||||||
* (c) 2018-present Yuxi (Evan) You and Vue contributors
|
|
||||||
* @license MIT
|
|
||||||
**/`
|
|
||||||
|
|
||||||
output.exports = isCompatPackage ? 'auto' : 'named'
|
output.exports = isCompatPackage ? 'auto' : 'named'
|
||||||
if (isCJSBuild) {
|
if (isCJSBuild) {
|
||||||
|
@ -375,24 +377,21 @@ function createMinifiedConfig(/** @type {PackageFormat} */ format) {
|
||||||
{
|
{
|
||||||
name: 'swc-minify',
|
name: 'swc-minify',
|
||||||
|
|
||||||
async renderChunk(
|
async renderChunk(contents, _, { format }) {
|
||||||
contents,
|
const { code } = await minifySwc(contents, {
|
||||||
_,
|
|
||||||
{ format, sourcemap, sourcemapExcludeSources },
|
|
||||||
) {
|
|
||||||
const { code, map } = await minifySwc(contents, {
|
|
||||||
module: format === 'es',
|
module: format === 'es',
|
||||||
|
format: {
|
||||||
|
comments: false,
|
||||||
|
},
|
||||||
compress: {
|
compress: {
|
||||||
ecma: 2016,
|
ecma: 2016,
|
||||||
pure_getters: true,
|
pure_getters: true,
|
||||||
},
|
},
|
||||||
safari10: true,
|
safari10: true,
|
||||||
mangle: true,
|
mangle: true,
|
||||||
sourceMap: !!sourcemap,
|
|
||||||
inlineSourcesContent: !sourcemapExcludeSources,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return { code, map: map || null }
|
return { code: banner + code, map: null }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
Loading…
Reference in New Issue