Merge remote-tracking branch 'upstream/main'

This commit is contained in:
三咲智子 Kevin Deng 2024-01-14 23:33:08 +08:00
commit af9f892afa
No known key found for this signature in database
GPG Key ID: 69992F2250DFD93E
68 changed files with 1770 additions and 873 deletions

View File

@ -1,3 +1,94 @@
## [3.4.13](https://github.com/vuejs/core/compare/v3.4.12...v3.4.13) (2024-01-13)
### Bug Fixes
* **reactivity:** fix dirtyLevel checks for recursive effects ([#10101](https://github.com/vuejs/core/issues/10101)) ([e45a8d2](https://github.com/vuejs/core/commit/e45a8d24b46c174deb46ed952bdaf54c81ad5a85)), closes [#10082](https://github.com/vuejs/core/issues/10082)
## [3.4.12](https://github.com/vuejs/core/compare/v3.4.11...v3.4.12) (2024-01-13)
### Reverts
* fix(reactivity): correct dirty assign in render function ([#10091](https://github.com/vuejs/core/issues/10091)) ([8b18481](https://github.com/vuejs/core/commit/8b1848173b0bc8fd84ce1da1af8d373c044bf073)), closes [#10098](https://github.com/vuejs/core/issues/10098) [#10100](https://github.com/vuejs/core/issues/10100)
## [3.4.11](https://github.com/vuejs/core/compare/v3.4.10...v3.4.11) (2024-01-12)
### Bug Fixes
* **hydration:** improve mismatch when client value is null or undefined ([#10086](https://github.com/vuejs/core/issues/10086)) ([08b60f5](https://github.com/vuejs/core/commit/08b60f5d0d5b57fcf3347ef66cbeab472c475a88))
* **reactivity:** correct dirty assign in render function ([#10091](https://github.com/vuejs/core/issues/10091)) ([8d04205](https://github.com/vuejs/core/commit/8d042050411fdf04d9d1d6c153287164b12e0255)), closes [#10082](https://github.com/vuejs/core/issues/10082)
* **runtime-core:** filter single root for nested DEV_ROOT_FRAGMENT ([#8593](https://github.com/vuejs/core/issues/8593)) ([d35b877](https://github.com/vuejs/core/commit/d35b87725ab3e2bdc86fb5781ab34939f7ec1029)), closes [#5203](https://github.com/vuejs/core/issues/5203) [#8581](https://github.com/vuejs/core/issues/8581) [#10087](https://github.com/vuejs/core/issues/10087)
## [3.4.10](https://github.com/vuejs/core/compare/v3.4.9...v3.4.10) (2024-01-11)
### Bug Fixes
* **hydration:** should not warn on falsy bindings of non-property keys ([3907c87](https://github.com/vuejs/core/commit/3907c87ce23cc6ef4a739b5a66ddb553e8723114))
## [3.4.9](https://github.com/vuejs/core/compare/v3.4.8...v3.4.9) (2024-01-11)
### Bug Fixes
* **build:** avoid accessing __FEATURE_PROD_DEVTOOLS__ flag in root scope ([dfd9654](https://github.com/vuejs/core/commit/dfd9654665890d1bc7129f6e3c2faaa5b1f28f72))
* **hydration:** do not warn against bindings w/ object values ([dcc68ef](https://github.com/vuejs/core/commit/dcc68ef7d48973abd8dd3178b46e50e3b0785ea4))
* **runtime-dom:** unify behavior for v-show + style display binding ([#10075](https://github.com/vuejs/core/issues/10075)) ([cd419ae](https://github.com/vuejs/core/commit/cd419aec3cb615eaea8b2324356f38f4c0ff1fcc)), closes [#10074](https://github.com/vuejs/core/issues/10074)
* **suspense:** avoid double-patching nested suspense when parent suspense is not resolved ([#10055](https://github.com/vuejs/core/issues/10055)) ([bcda96b](https://github.com/vuejs/core/commit/bcda96b525801eb7a1d397300fb3f2f9b827ddfb)), closes [#8678](https://github.com/vuejs/core/issues/8678)
## [3.4.8](https://github.com/vuejs/core/compare/v3.4.7...v3.4.8) (2024-01-10)
### Bug Fixes
* **hydration:** fix class and style hydration mismatch message ([5af3987](https://github.com/vuejs/core/commit/5af398729168481c3bee741b4f36fa4f375e0f4a)), closes [#10067](https://github.com/vuejs/core/issues/10067)
* **hydration:** improve attr hydration mismatch check for boolean attrs ([972face](https://github.com/vuejs/core/commit/972facee0d892a1b6d9d4ad1da5da9306ed45c3f)), closes [#10057](https://github.com/vuejs/core/issues/10057) [#10060](https://github.com/vuejs/core/issues/10060)
* **suspense:** fix more suspense patch before resolve edge cases ([70ad4ca](https://github.com/vuejs/core/commit/70ad4caad7d19938f8ccf1ede3228a81254dd4bf)), closes [#10017](https://github.com/vuejs/core/issues/10017)
## [3.4.7](https://github.com/vuejs/core/compare/v3.4.6...v3.4.7) (2024-01-09)
### Bug Fixes
* **parser:** skip compat mode check for SFC root `<template>` tags ([#10034](https://github.com/vuejs/core/issues/10034)) ([923d560](https://github.com/vuejs/core/commit/923d560d0b6713144671809b6dfeb1e2da503b1f))
* **types:** fix functional component for `h` ([#9991](https://github.com/vuejs/core/issues/9991)) ([438a74a](https://github.com/vuejs/core/commit/438a74aad840183286fbdb488178510f37218a73))
### Reverts
* "dx(computed): warn incorrect use of getCurrentInstance inside computed" ([2fd3905](https://github.com/vuejs/core/commit/2fd39057386644c8bfee426c60a51f2b07a79b09))
## [3.4.6](https://github.com/vuejs/core/compare/v3.4.5...v3.4.6) (2024-01-08)
### Bug Fixes
* **build:** revert "build: add production/development export conditions ([#9977](https://github.com/vuejs/core/issues/9977))" ([7bd4e90](https://github.com/vuejs/core/commit/7bd4e90506547c42234165776b01793abd37b148)), closes [#10012](https://github.com/vuejs/core/issues/10012) [#10020](https://github.com/vuejs/core/issues/10020)
* fix post watcher fire timing on nested app mounts ([3c3561e](https://github.com/vuejs/core/commit/3c3561e7203091f49d57f1da6d822c91e462bc46)), closes [#10005](https://github.com/vuejs/core/issues/10005)
* **hydration:** avoid hydration mismatch warning for styles with different order ([#10011](https://github.com/vuejs/core/issues/10011)) ([2701355](https://github.com/vuejs/core/commit/2701355e8eb07ab664e398d9fc05d6c4e2e9b20e)), closes [#10000](https://github.com/vuejs/core/issues/10000) [#10006](https://github.com/vuejs/core/issues/10006)
* **runtime-core:** handle fragment with null children ([#10010](https://github.com/vuejs/core/issues/10010)) ([3bf34b7](https://github.com/vuejs/core/commit/3bf34b767e4dd3cf6a974301ecf0363ae4dda4ec)), closes [#10007](https://github.com/vuejs/core/issues/10007)
* **scheduler:** sort nested postFlushCbs ([d9162df](https://github.com/vuejs/core/commit/d9162dfc2ee0c3a369fb9bf32ff413e74761bee6)), closes [#10003](https://github.com/vuejs/core/issues/10003)
* **suspense:** fix anchor for suspense with transition out-in ([#9999](https://github.com/vuejs/core/issues/9999)) ([a3fbf21](https://github.com/vuejs/core/commit/a3fbf2132b0cd3655e969e290548c8fabc08fd33)), closes [#9996](https://github.com/vuejs/core/issues/9996)
* **types:** allow `null` type for textarea value ([#9997](https://github.com/vuejs/core/issues/9997)) ([c379bc2](https://github.com/vuejs/core/commit/c379bc29efc70d6ac5840de10c357ee3dad998c0)), closes [#9904](https://github.com/vuejs/core/issues/9904)
## [3.4.5](https://github.com/vuejs/core/compare/v3.4.4...v3.4.5) (2024-01-04) ## [3.4.5](https://github.com/vuejs/core/compare/v3.4.4...v3.4.5) (2024-01-04)

View File

@ -1,6 +1,6 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2018-present, Yuxi (Evan) You Copyright (c) 2018-present, Yuxi (Evan) You and Vue contributors
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -70,11 +70,11 @@
"@rollup/plugin-terser": "^0.4.4", "@rollup/plugin-terser": "^0.4.4",
"@types/hash-sum": "^1.0.2", "@types/hash-sum": "^1.0.2",
"@types/minimist": "^1.2.5", "@types/minimist": "^1.2.5",
"@types/node": "^20.10.6", "@types/node": "^20.10.7",
"@types/semver": "^7.5.6", "@types/semver": "^7.5.6",
"@typescript-eslint/eslint-plugin": "^6.17.0", "@typescript-eslint/eslint-plugin": "^6.17.0",
"@typescript-eslint/parser": "^6.17.0", "@typescript-eslint/parser": "^6.17.0",
"@vitest/coverage-istanbul": "^1.1.1", "@vitest/coverage-istanbul": "^1.1.3",
"@vue/consolidate": "0.17.3", "@vue/consolidate": "0.17.3",
"conventional-changelog-cli": "^4.1.0", "conventional-changelog-cli": "^4.1.0",
"enquirer": "^2.4.1", "enquirer": "^2.4.1",
@ -86,7 +86,7 @@
"eslint-plugin-jest": "^27.6.1", "eslint-plugin-jest": "^27.6.1",
"estree-walker": "^2.0.2", "estree-walker": "^2.0.2",
"execa": "^8.0.1", "execa": "^8.0.1",
"jsdom": "^23.0.1", "jsdom": "^23.2.0",
"lint-staged": "^15.2.0", "lint-staged": "^15.2.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"magic-string": "^0.30.5", "magic-string": "^0.30.5",
@ -98,7 +98,7 @@
"prettier": "^3.1.1", "prettier": "^3.1.1",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"pug": "^3.0.2", "pug": "^3.0.2",
"puppeteer": "~21.6.1", "puppeteer": "~21.7.0",
"rimraf": "^5.0.5", "rimraf": "^5.0.5",
"rollup": "^4.1.4", "rollup": "^4.1.4",
"rollup-plugin-dts": "^6.1.0", "rollup-plugin-dts": "^6.1.0",
@ -113,6 +113,6 @@
"tsx": "^4.7.0", "tsx": "^4.7.0",
"typescript": "^5.2.2", "typescript": "^5.2.2",
"vite": "^5.0.5", "vite": "^5.0.5",
"vitest": "^1.1.1" "vitest": "^1.1.3"
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-core", "name": "@vue/compiler-core",
"version": "3.4.5", "version": "3.4.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",
@ -17,6 +17,7 @@
"development": "./dist/compiler-core.cjs.js", "development": "./dist/compiler-core.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/compiler-core.esm-bundler.js",
"import": "./dist/compiler-core.esm-bundler.js", "import": "./dist/compiler-core.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -50,7 +50,7 @@ export function walkIdentifiers(
} }
} else if ( } else if (
node.type === 'ObjectProperty' && node.type === 'ObjectProperty' &&
parent!.type === 'ObjectPattern' parent?.type === 'ObjectPattern'
) { ) {
// mark property in destructure pattern // mark property in destructure pattern
;(node as any).inPattern = true ;(node as any).inPattern = true

View File

@ -692,6 +692,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
} }
if ( if (
!tokenizer.inSFCRoot &&
isCompatEnabled( isCompatEnabled(
CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE, CompilerDeprecationTypes.COMPILER_NATIVE_TEMPLATE,
currentOptions, currentOptions,

View File

@ -256,7 +256,7 @@ export function createTransformContext(
} }
context.parent!.children.splice(removalIndex, 1) context.parent!.children.splice(removalIndex, 1)
}, },
onNodeRemoved: () => {}, onNodeRemoved: NOOP,
addIdentifiers(exp) { addIdentifiers(exp) {
// identifier tracking only happens in non-browser builds. // identifier tracking only happens in non-browser builds.
if (!__BROWSER__) { if (!__BROWSER__) {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-dom", "name": "@vue/compiler-dom",
"version": "3.4.5", "version": "3.4.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",
@ -19,6 +19,7 @@
"development": "./dist/compiler-dom.cjs.js", "development": "./dist/compiler-dom.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/compiler-dom.esm-bundler.js",
"import": "./dist/compiler-dom.esm-bundler.js", "import": "./dist/compiler-dom.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -953,6 +953,38 @@ describe('SFC compile <script setup>', () => {
</script>`).content, </script>`).content,
) )
}) })
test('defineModel() referencing local var', () => {
expect(() =>
compile(`<script setup>
let bar = 1
defineModel({
default: () => bar
})
</script>`),
).toThrow(`cannot reference locally declared variables`)
// allow const
expect(() =>
compile(`<script setup>
const bar = 1
defineModel({
default: () => bar
})
</script>`),
).not.toThrow(`cannot reference locally declared variables`)
// allow in get/set
expect(() =>
compile(`<script setup>
let bar = 1
defineModel({
get: () => bar,
set: () => bar
})
</script>`),
).not.toThrow(`cannot reference locally declared variables`)
})
}) })
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-sfc", "name": "@vue/compiler-sfc",
"version": "3.4.5", "version": "3.4.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",
@ -12,6 +12,7 @@
".": { ".": {
"types": "./dist/compiler-sfc.d.ts", "types": "./dist/compiler-sfc.d.ts",
"node": "./dist/compiler-sfc.cjs.js", "node": "./dist/compiler-sfc.cjs.js",
"module": "./dist/compiler-sfc.esm-browser.js",
"import": "./dist/compiler-sfc.esm-browser.js", "import": "./dist/compiler-sfc.esm-browser.js",
"require": "./dist/compiler-sfc.cjs.js" "require": "./dist/compiler-sfc.cjs.js"
}, },

View File

@ -672,6 +672,11 @@ export function compileScript(
checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS) checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS) checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS) checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
for (const { runtimeOptionNodes } of Object.values(ctx.modelDecls)) {
for (const node of runtimeOptionNodes) {
checkInvalidScopeReference(node, DEFINE_MODEL)
}
}
// 5. remove non-script content // 5. remove non-script content
if (script) { if (script) {

View File

@ -219,6 +219,7 @@ function doCompileTemplate({
// We need to parse a fresh one. Can't just use `source` here since we need // We need to parse a fresh one. Can't just use `source` here since we need
// the AST location info to be relative to the entire SFC. // the AST location info to be relative to the entire SFC.
const newAST = (ssr ? CompilerDOM : compiler).parse(inAST.source, { const newAST = (ssr ? CompilerDOM : compiler).parse(inAST.source, {
prefixIdentifiers: true,
...compilerOptions, ...compilerOptions,
parseMode: 'sfc', parseMode: 'sfc',
onError: e => errors.push(e), onError: e => errors.push(e),

View File

@ -15,6 +15,7 @@ export interface ModelDecl {
type: TSType | undefined type: TSType | undefined
options: string | undefined options: string | undefined
identifier: string | undefined identifier: string | undefined
runtimeOptionNodes: Node[]
} }
export function processDefineModel( export function processDefineModel(
@ -48,6 +49,7 @@ export function processDefineModel(
let optionsString = options && ctx.getString(options) let optionsString = options && ctx.getString(options)
let optionsRemoved = !options let optionsRemoved = !options
const runtimeOptionNodes: Node[] = []
if ( if (
options && options &&
@ -75,6 +77,8 @@ export function processDefineModel(
// remove prop options from runtime options // remove prop options from runtime options
removed++ removed++
ctx.s.remove(ctx.startOffset! + start, ctx.startOffset! + end) ctx.s.remove(ctx.startOffset! + start, ctx.startOffset! + end)
// record prop options for invalid scope var reference check
runtimeOptionNodes.push(p)
} }
} }
if (removed === options.properties.length) { if (removed === options.properties.length) {
@ -89,6 +93,7 @@ export function processDefineModel(
ctx.modelDecls[modelName] = { ctx.modelDecls[modelName] = {
type, type,
options: optionsString, options: optionsString,
runtimeOptionNodes,
identifier: identifier:
declId && declId.type === 'Identifier' ? declId.name : undefined, declId && declId.type === 'Identifier' ? declId.name : undefined,
} }

View File

@ -908,7 +908,7 @@ function importSourceToScope(
resolved = resolveExt(filename, fs) resolved = resolveExt(filename, fs)
} else { } else {
// module or aliased import - use full TS resolution, only supported in Node // module or aliased import - use full TS resolution, only supported in Node
if (!__NODE_JS__) { if (!__CJS__) {
return ctx.error( return ctx.error(
`Type import from non-relative sources is not supported in the browser build.`, `Type import from non-relative sources is not supported in the browser build.`,
node, node,
@ -975,7 +975,7 @@ function resolveWithTS(
ts: typeof TS, ts: typeof TS,
fs: FS, fs: FS,
): string | undefined { ): string | undefined {
if (!__NODE_JS__) return if (!__CJS__) return
// 1. resolve tsconfig.json // 1. resolve tsconfig.json
const configPath = ts.findConfigFile(containingFile, fs.fileExists) const configPath = ts.findConfigFile(containingFile, fs.fileExists)

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-ssr", "name": "@vue/compiler-ssr",
"version": "3.4.5", "version": "3.4.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",

View File

@ -2,8 +2,10 @@ import {
type Component, type Component,
type DefineComponent, type DefineComponent,
Fragment, Fragment,
type FunctionalComponent,
Suspense, Suspense,
Teleport, Teleport,
type VNode,
defineComponent, defineComponent,
h, h,
ref, ref,
@ -77,6 +79,19 @@ describe('h inference w/ Suspense', () => {
h(Suspense, { onResolve: 1 }) h(Suspense, { onResolve: 1 })
}) })
declare const fc: FunctionalComponent<
{
foo: string
bar?: number
onClick: (evt: MouseEvent) => void
},
['click'],
{
default: () => VNode
title: (scope: { id: number }) => VNode
}
>
declare const vnode: VNode
describe('h inference w/ functional component', () => { describe('h inference w/ functional component', () => {
const Func = (_props: { foo: string; bar?: number }) => '' const Func = (_props: { foo: string; bar?: number }) => ''
h(Func, { foo: 'hello' }) h(Func, { foo: 'hello' })
@ -87,6 +102,15 @@ describe('h inference w/ functional component', () => {
h(Func, {}) h(Func, {})
// @ts-expect-error // @ts-expect-error
h(Func, { bar: 123 }) h(Func, { bar: 123 })
h(
fc,
{ foo: 'hello', onClick: () => {} },
{
default: () => vnode,
title: ({ id }: { id: number }) => vnode,
},
)
}) })
describe('h support w/ plain object component', () => { describe('h support w/ plain object component', () => {

View File

@ -2,18 +2,18 @@ import {
type Ref, type Ref,
type Slots, type Slots,
type VNode, type VNode,
defineComponent,
defineEmits, defineEmits,
defineModel, defineModel,
defineProps, defineProps,
defineSlots, defineSlots,
toRefs, toRefs,
useAttrs, useAttrs,
useModel,
useSlots, useSlots,
withDefaults, withDefaults,
} from 'vue' } from 'vue'
import { describe, expectType } from './utils' import { describe, expectType } from './utils'
import { defineComponent } from 'vue'
import { useModel } from 'vue'
describe('defineProps w/ type declaration', () => { describe('defineProps w/ type declaration', () => {
// type declaration // type declaration

View File

@ -7,6 +7,7 @@ expectType<JSX.Element>(<div />)
expectType<JSX.Element>(<div id="foo" />) expectType<JSX.Element>(<div id="foo" />)
expectType<JSX.Element>(<div>hello</div>) expectType<JSX.Element>(<div>hello</div>)
expectType<JSX.Element>(<input value="foo" />) expectType<JSX.Element>(<input value="foo" />)
expectType<JSX.Element>(<textarea value={null} />)
// @ts-expect-error style css property validation // @ts-expect-error style css property validation
;<div style={{ unknown: 123 }} /> ;<div style={{ unknown: 123 }} />

View File

@ -7,7 +7,7 @@ declare var __BROWSER__: boolean
declare var __GLOBAL__: boolean declare var __GLOBAL__: boolean
declare var __ESM_BUNDLER__: boolean declare var __ESM_BUNDLER__: boolean
declare var __ESM_BROWSER__: boolean declare var __ESM_BROWSER__: boolean
declare var __NODE_JS__: boolean declare var __CJS__: boolean
declare var __SSR__: boolean declare var __SSR__: boolean
declare var __COMMIT__: string declare var __COMMIT__: string
declare var __VERSION__: string declare var __VERSION__: string

View File

@ -11,6 +11,7 @@ import {
ref, ref,
toRaw, toRaw,
} from '../src' } from '../src'
import { DirtyLevels } from '../src/constants'
describe('reactivity/computed', () => { describe('reactivity/computed', () => {
it('should return updated value', () => { it('should return updated value', () => {
@ -451,4 +452,33 @@ describe('reactivity/computed', () => {
v.value = 2 v.value = 2
expect(fnSpy).toBeCalledTimes(2) expect(fnSpy).toBeCalledTimes(2)
}) })
it('...', () => {
const fnSpy = vi.fn()
const v = ref(1)
const c1 = computed(() => v.value)
const c2 = computed(() => {
fnSpy()
return c1.value
})
c1.effect.allowRecurse = true
c2.effect.allowRecurse = true
c2.value
expect(c1.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
expect(c2.effect._dirtyLevel).toBe(DirtyLevels.NotDirty)
})
it('should work when chained(ref+computed)', () => {
const value = ref(0)
const consumer = computed(() => {
value.value++
return 'foo'
})
const provider = computed(() => value.value + consumer.value)
expect(provider.value).toBe('0foo')
expect(provider.effect._dirtyLevel).toBe(DirtyLevels.Dirty)
expect(provider.value).toBe('1foo')
})
}) })

View File

@ -13,6 +13,15 @@ import {
} from '../src/index' } from '../src/index'
import { pauseScheduling, resetScheduling } from '../src/effect' import { pauseScheduling, resetScheduling } from '../src/effect'
import { ITERATE_KEY, getDepFromReactive } from '../src/reactiveEffect' import { ITERATE_KEY, getDepFromReactive } from '../src/reactiveEffect'
import {
computed,
h,
nextTick,
nodeOps,
ref,
render,
serializeInner,
} from '@vue/runtime-test'
describe('reactivity/effect', () => { describe('reactivity/effect', () => {
it('should run the passed function once (wrapped by a effect)', () => { it('should run the passed function once (wrapped by a effect)', () => {
@ -1011,6 +1020,35 @@ describe('reactivity/effect', () => {
expect(counterSpy).toHaveBeenCalledTimes(1) expect(counterSpy).toHaveBeenCalledTimes(1)
}) })
// #10082
it('should set dirtyLevel when effect is allowRecurse and is running', async () => {
const s = ref(0)
const n = computed(() => s.value + 1)
const Child = {
setup() {
s.value++
return () => n.value
},
}
const renderSpy = vi.fn()
const Parent = {
setup() {
return () => {
renderSpy()
return [n.value, h(Child)]
}
},
}
const root = nodeOps.createElement('div')
render(h(Parent), root)
await nextTick()
expect(serializeInner(root)).toBe('22')
expect(renderSpy).toHaveBeenCalledTimes(2)
})
describe('empty dep cleanup', () => { describe('empty dep cleanup', () => {
it('should remove the dep when the effect is stopped', () => { it('should remove the dep when the effect is stopped', () => {
const obj = reactive({ prop: 1 }) const obj = reactive({ prop: 1 })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/reactivity", "name": "@vue/reactivity",
"version": "3.4.5", "version": "3.4.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",
@ -19,6 +19,7 @@
"development": "./dist/reactivity.cjs.js", "development": "./dist/reactivity.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/reactivity.esm-bundler.js",
"import": "./dist/reactivity.esm-bundler.js", "import": "./dist/reactivity.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -43,7 +43,7 @@ export class ComputedRefImpl<T> {
) { ) {
this.effect = new ReactiveEffect( this.effect = new ReactiveEffect(
() => getter(this._value), () => getter(this._value),
() => triggerRefValue(this, DirtyLevels.ComputedValueMaybeDirty), () => triggerRefValue(this, DirtyLevels.MaybeDirty),
) )
this.effect.computed = this this.effect.computed = this
this.effect.active = this._cacheable = !isSSR this.effect.active = this._cacheable = !isSSR
@ -53,12 +53,12 @@ export class ComputedRefImpl<T> {
get value() { get value() {
// the computed ref may get wrapped by other proxies e.g. readonly() #3376 // the computed ref may get wrapped by other proxies e.g. readonly() #3376
const self = toRaw(this) const self = toRaw(this)
trackRefValue(self)
if (!self._cacheable || self.effect.dirty) { if (!self._cacheable || self.effect.dirty) {
if (hasChanged(self._value, (self._value = self.effect.run()!))) { if (hasChanged(self._value, (self._value = self.effect.run()!))) {
triggerRefValue(self, DirtyLevels.ComputedValueDirty) triggerRefValue(self, DirtyLevels.Dirty)
} }
} }
trackRefValue(self)
return self._value return self._value
} }

View File

@ -24,7 +24,6 @@ export enum ReactiveFlags {
export enum DirtyLevels { export enum DirtyLevels {
NotDirty = 0, NotDirty = 0,
ComputedValueMaybeDirty = 1, MaybeDirty = 1,
ComputedValueDirty = 2, Dirty = 2,
Dirty = 3,
} }

View File

@ -60,7 +60,7 @@ export class ReactiveEffect<T = any> {
/** /**
* @internal * @internal
*/ */
_queryings = 0 _shouldSchedule = false
/** /**
* @internal * @internal
*/ */
@ -76,22 +76,23 @@ export class ReactiveEffect<T = any> {
} }
public get dirty() { public get dirty() {
if (this._dirtyLevel === DirtyLevels.ComputedValueMaybeDirty) { if (this._dirtyLevel === DirtyLevels.MaybeDirty) {
this._dirtyLevel = DirtyLevels.NotDirty
this._queryings++
pauseTracking() pauseTracking()
for (const dep of this.deps) { for (let i = 0; i < this._depsLength; i++) {
const dep = this.deps[i]
if (dep.computed) { if (dep.computed) {
triggerComputed(dep.computed) triggerComputed(dep.computed)
if (this._dirtyLevel >= DirtyLevels.ComputedValueDirty) { if (this._dirtyLevel >= DirtyLevels.Dirty) {
break break
} }
} }
} }
resetTracking() if (this._dirtyLevel < DirtyLevels.Dirty) {
this._queryings-- this._dirtyLevel = DirtyLevels.NotDirty
} }
return this._dirtyLevel >= DirtyLevels.ComputedValueDirty resetTracking()
}
return this._dirtyLevel >= DirtyLevels.Dirty
} }
public set dirty(v) { public set dirty(v) {
@ -281,7 +282,7 @@ export function trackEffect(
} }
} }
const queueEffectSchedulers: (() => void)[] = [] const queueEffectSchedulers: EffectScheduler[] = []
export function triggerEffects( export function triggerEffects(
dep: Dep, dep: Dep,
@ -290,28 +291,29 @@ export function triggerEffects(
) { ) {
pauseScheduling() pauseScheduling()
for (const effect of dep.keys()) { for (const effect of dep.keys()) {
if (!effect.allowRecurse && effect._runnings) { if (dep.get(effect) !== effect._trackId) {
// when recurse effect is running, dep map could have outdated items
continue continue
} }
if ( if (effect._dirtyLevel < dirtyLevel) {
effect._dirtyLevel < dirtyLevel &&
(!effect._runnings || dirtyLevel !== DirtyLevels.ComputedValueDirty)
) {
const lastDirtyLevel = effect._dirtyLevel const lastDirtyLevel = effect._dirtyLevel
effect._dirtyLevel = dirtyLevel effect._dirtyLevel = dirtyLevel
if ( if (lastDirtyLevel === DirtyLevels.NotDirty) {
lastDirtyLevel === DirtyLevels.NotDirty && effect._shouldSchedule = true
(!effect._queryings || dirtyLevel !== DirtyLevels.ComputedValueDirty)
) {
if (__DEV__) { if (__DEV__) {
effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo)) effect.onTrigger?.(extend({ effect }, debuggerEventExtraInfo))
} }
effect.trigger() effect.trigger()
if (effect.scheduler) { }
}
if (
effect.scheduler &&
effect._shouldSchedule &&
(!effect._runnings || effect.allowRecurse)
) {
effect._shouldSchedule = false
queueEffectSchedulers.push(effect.scheduler) queueEffectSchedulers.push(effect.scheduler)
} }
} }
}
}
resetScheduling() resetScheduling()
} }

View File

@ -13,7 +13,7 @@ import {
// The main WeakMap that stores {target -> key -> dep} connections. // The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class // Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as // which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead. // raw Maps to reduce memory overhead.
type KeyToDepMap = Map<any, Dep> type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<object, KeyToDepMap>() const targetMap = new WeakMap<object, KeyToDepMap>()

View File

@ -5,12 +5,15 @@ import {
getCurrentInstance, getCurrentInstance,
h, h,
inject, inject,
nextTick,
nodeOps, nodeOps,
onMounted,
provide, provide,
ref, ref,
resolveComponent, resolveComponent,
resolveDirective, resolveDirective,
serializeInner, serializeInner,
watch,
withDirectives, withDirectives,
} from '@vue/runtime-test' } from '@vue/runtime-test'
@ -551,6 +554,35 @@ describe('api: createApp', () => {
).not.toHaveBeenWarned() ).not.toHaveBeenWarned()
}) })
// #10005
test('flush order edge case on nested createApp', async () => {
const order: string[] = []
const App = defineComponent({
setup(props) {
const message = ref('m1')
watch(
message,
() => {
order.push('post watcher')
},
{ flush: 'post' },
)
onMounted(() => {
message.value = 'm2'
createApp(() => '').mount(nodeOps.createElement('div'))
})
return () => {
order.push('render')
return h('div', [message.value])
}
},
})
createApp(App).mount(nodeOps.createElement('div'))
await nextTick()
expect(order).toMatchObject(['render', 'render', 'post watcher'])
})
// config.compilerOptions is tested in packages/vue since it is only // config.compilerOptions is tested in packages/vue since it is only
// supported in the full build. // supported in the full build.
}) })

View File

@ -1,28 +1,18 @@
import { import {
type ComponentInternalInstance, type ComponentInternalInstance,
type ComputedRef, type ComputedRef,
Fragment,
type Ref,
type SetupContext, type SetupContext,
Suspense, Suspense,
computed, computed,
createApp, createApp,
createBlock,
createElementBlock,
createElementVNode,
createVNode,
defineComponent, defineComponent,
getCurrentInstance, getCurrentInstance,
h, h,
nextTick,
nodeOps, nodeOps,
onMounted, onMounted,
openBlock,
ref,
render, render,
serializeInner, serializeInner,
shallowReactive, shallowReactive,
watch,
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { import {
createPropsRestProxy, createPropsRestProxy,
@ -32,7 +22,6 @@ import {
mergeDefaults, mergeDefaults,
mergeModels, mergeModels,
useAttrs, useAttrs,
useModel,
useSlots, useSlots,
withAsyncContext, withAsyncContext,
withDefaults, withDefaults,
@ -185,516 +174,6 @@ describe('SFC <script setup> helpers', () => {
}) })
}) })
describe('useModel', () => {
test('basic', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const compRender = vi.fn()
const Comp = defineComponent({
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props) {
foo = useModel(props, 'modelValue')
return () => {
compRender()
return foo.value
}
},
})
const msg = ref('')
const setValue = vi.fn(v => (msg.value = v))
const root = nodeOps.createElement('div')
createApp(() =>
h(Comp, {
modelValue: msg.value,
'onUpdate:modelValue': setValue,
}),
).mount(root)
expect(foo.value).toBe('')
expect(msg.value).toBe('')
expect(setValue).not.toBeCalled()
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('')
// update from child
update()
await nextTick()
expect(msg.value).toBe('bar')
expect(foo.value).toBe('bar')
expect(setValue).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('bar')
// update from parent
msg.value = 'qux'
expect(msg.value).toBe('qux')
await nextTick()
expect(msg.value).toBe('qux')
expect(foo.value).toBe('qux')
expect(setValue).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(3)
expect(serializeInner(root)).toBe('qux')
})
test('without parent value (local mutation)', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const compRender = vi.fn()
const Comp = defineComponent({
props: ['foo'],
emits: ['update:foo'],
setup(props) {
foo = useModel(props, 'foo')
return () => {
compRender()
return foo.value
}
},
})
const root = nodeOps.createElement('div')
const updateFoo = vi.fn()
render(h(Comp, { 'onUpdate:foo': updateFoo }), root)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('<!---->')
expect(foo.value).toBeUndefined()
update()
// when parent didn't provide value, local mutation is enabled
expect(foo.value).toBe('bar')
await nextTick()
expect(updateFoo).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('bar')
})
test('without parent listener (local mutation)', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const compRender = vi.fn()
const Comp = defineComponent({
props: ['foo'],
emits: ['update:foo'],
setup(props) {
foo = useModel(props, 'foo')
return () => {
compRender()
return foo.value
}
},
})
const root = nodeOps.createElement('div')
// provide initial value
render(h(Comp, { foo: 'initial' }), root)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')
expect(foo.value).toBe('initial')
update()
// when parent didn't provide value, local mutation is enabled
expect(foo.value).toBe('bar')
await nextTick()
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('bar')
})
test('kebab-case v-model (should not be local)', async () => {
let foo: any
const compRender = vi.fn()
const Comp = defineComponent({
props: ['fooBar'],
emits: ['update:fooBar'],
setup(props) {
foo = useModel(props, 'fooBar')
return () => {
compRender()
return foo.value
}
},
})
const updateFooBar = vi.fn()
const root = nodeOps.createElement('div')
// v-model:foo-bar compiles to foo-bar and onUpdate:fooBar
render(
h(Comp, { 'foo-bar': 'initial', 'onUpdate:fooBar': updateFooBar }),
root,
)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')
expect(foo.value).toBe('initial')
foo.value = 'bar'
// should not be using local mode, so nothing should actually change
expect(foo.value).toBe('initial')
await nextTick()
expect(compRender).toBeCalledTimes(1)
expect(updateFooBar).toBeCalledTimes(1)
expect(updateFooBar).toHaveBeenCalledWith('bar')
expect(foo.value).toBe('initial')
expect(serializeInner(root)).toBe('initial')
})
test('kebab-case update listener (should not be local)', async () => {
let foo: any
const compRender = vi.fn()
const Comp = defineComponent({
props: ['fooBar'],
emits: ['update:fooBar'],
setup(props) {
foo = useModel(props, 'fooBar')
return () => {
compRender()
return foo.value
}
},
})
const updateFooBar = vi.fn()
const root = nodeOps.createElement('div')
// The template compiler won't create hyphenated listeners, but it could have been passed manually
render(
h(Comp, { 'foo-bar': 'initial', 'onUpdate:foo-bar': updateFooBar }),
root,
)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')
expect(foo.value).toBe('initial')
foo.value = 'bar'
// should not be using local mode, so nothing should actually change
expect(foo.value).toBe('initial')
await nextTick()
expect(compRender).toBeCalledTimes(1)
expect(updateFooBar).toBeCalledTimes(1)
expect(updateFooBar).toHaveBeenCalledWith('bar')
expect(foo.value).toBe('initial')
expect(serializeInner(root)).toBe('initial')
})
test('default value', async () => {
let count: any
const inc = () => {
count.value++
}
const compRender = vi.fn()
const Comp = defineComponent({
props: { count: { default: 0 } },
emits: ['update:count'],
setup(props) {
count = useModel(props, 'count')
return () => {
compRender()
return count.value
}
},
})
const root = nodeOps.createElement('div')
const updateCount = vi.fn()
render(h(Comp, { 'onUpdate:count': updateCount }), root)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('0')
expect(count.value).toBe(0)
inc()
// when parent didn't provide value, local mutation is enabled
expect(count.value).toBe(1)
await nextTick()
expect(updateCount).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('1')
})
test('parent limiting child value', async () => {
let childCount: Ref<number>
const compRender = vi.fn()
const Comp = defineComponent({
props: ['count'],
emits: ['update:count'],
setup(props) {
childCount = useModel(props, 'count')
return () => {
compRender()
return childCount.value
}
},
})
const Parent = defineComponent({
setup() {
const count = ref(0)
watch(count, () => {
if (count.value < 0) {
count.value = 0
}
})
return () =>
h(Comp, {
count: count.value,
'onUpdate:count': val => {
count.value = val
},
})
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(serializeInner(root)).toBe('0')
// child update
childCount!.value = 1
// not yet updated
expect(childCount!.value).toBe(0)
await nextTick()
expect(childCount!.value).toBe(1)
expect(serializeInner(root)).toBe('1')
// child update to invalid value
childCount!.value = -1
// not yet updated
expect(childCount!.value).toBe(1)
await nextTick()
// limited to 0 by parent
expect(childCount!.value).toBe(0)
expect(serializeInner(root)).toBe('0')
})
test('has parent value -> no parent value', async () => {
let childCount: Ref<number>
const compRender = vi.fn()
const Comp = defineComponent({
props: ['count'],
emits: ['update:count'],
setup(props) {
childCount = useModel(props, 'count')
return () => {
compRender()
return childCount.value
}
},
})
const toggle = ref(true)
const Parent = defineComponent({
setup() {
const count = ref(0)
return () =>
toggle.value
? h(Comp, {
count: count.value,
'onUpdate:count': val => {
count.value = val
},
})
: h(Comp)
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(serializeInner(root)).toBe('0')
// child update
childCount!.value = 1
// not yet updated
expect(childCount!.value).toBe(0)
await nextTick()
expect(childCount!.value).toBe(1)
expect(serializeInner(root)).toBe('1')
// parent change
toggle.value = false
await nextTick()
// localValue should be reset
expect(childCount!.value).toBeUndefined()
expect(serializeInner(root)).toBe('<!---->')
// child local mutation should continue to work
childCount!.value = 2
expect(childCount!.value).toBe(2)
await nextTick()
expect(serializeInner(root)).toBe('2')
})
// #9838
test('pass modelValue to slot (optimized mode) ', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const Comp = {
render(this: any) {
return this.$slots.default()
},
}
const childRender = vi.fn()
const slotRender = vi.fn()
const Child = defineComponent({
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props) {
foo = useModel(props, 'modelValue')
return () => {
childRender()
return (
openBlock(),
createElementBlock(Fragment, null, [
createVNode(Comp, null, {
default: () => {
slotRender()
return createElementVNode('div', null, foo.value)
},
_: 1 /* STABLE */,
}),
])
)
}
},
})
const msg = ref('')
const setValue = vi.fn(v => (msg.value = v))
const root = nodeOps.createElement('div')
createApp({
render() {
return (
openBlock(),
createBlock(
Child,
{
modelValue: msg.value,
'onUpdate:modelValue': setValue,
},
null,
8 /* PROPS */,
['modelValue'],
)
)
},
}).mount(root)
expect(foo.value).toBe('')
expect(msg.value).toBe('')
expect(setValue).not.toBeCalled()
expect(childRender).toBeCalledTimes(1)
expect(slotRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('<div></div>')
// update from child
update()
await nextTick()
expect(msg.value).toBe('bar')
expect(foo.value).toBe('bar')
expect(setValue).toBeCalledTimes(1)
expect(childRender).toBeCalledTimes(2)
expect(slotRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('<div>bar</div>')
})
test('with modifiers & transformers', async () => {
let childMsg: Ref<string>
let childModifiers: Record<string, true | undefined>
const compRender = vi.fn()
const Comp = defineComponent({
props: ['msg', 'msgModifiers'],
emits: ['update:msg'],
setup(props) {
;[childMsg, childModifiers] = useModel(props, 'msg', {
get(val) {
return val.toLowerCase()
},
set(val) {
if (childModifiers.upper) {
return val.toUpperCase()
}
},
})
return () => {
compRender()
return childMsg.value
}
},
})
const msg = ref('HI')
const Parent = defineComponent({
setup() {
return () =>
h(Comp, {
msg: msg.value,
msgModifiers: { upper: true },
'onUpdate:msg': val => {
msg.value = val
},
})
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
// should be lowered
expect(serializeInner(root)).toBe('hi')
// child update
childMsg!.value = 'Hmm'
await nextTick()
expect(childMsg!.value).toBe('hmm')
expect(serializeInner(root)).toBe('hmm')
// parent should get uppercase value
expect(msg.value).toBe('HMM')
// parent update
msg.value = 'Ughh'
await nextTick()
expect(serializeInner(root)).toBe('ughh')
expect(msg.value).toBe('Ughh')
// child update again
childMsg!.value = 'ughh'
await nextTick()
expect(msg.value).toBe('UGHH')
})
})
test('createPropsRestProxy', () => { test('createPropsRestProxy', () => {
const original = shallowReactive({ const original = shallowReactive({
foo: 1, foo: 1,

View File

@ -1641,6 +1641,141 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(expected) expect(serializeInner(root)).toBe(expected)
}) })
//#8678
test('nested suspense (child suspense update before parent suspense resolve)', async () => {
const calls: string[] = []
const InnerA = defineAsyncComponent(
{
setup: () => {
calls.push('innerA created')
onMounted(() => {
calls.push('innerA mounted')
})
return () => h('div', 'innerA')
},
},
10,
)
const InnerB = defineAsyncComponent(
{
setup: () => {
calls.push('innerB created')
onMounted(() => {
calls.push('innerB mounted')
})
return () => h('div', 'innerB')
},
},
10,
)
const OuterA = defineAsyncComponent(
{
setup: (_, { slots }: any) => {
calls.push('outerA created')
onMounted(() => {
calls.push('outerA mounted')
})
return () =>
h(Fragment, null, [h('div', 'outerA'), slots.default?.()])
},
},
5,
)
const OuterB = defineAsyncComponent(
{
setup: (_, { slots }: any) => {
calls.push('outerB created')
onMounted(() => {
calls.push('outerB mounted')
})
return () =>
h(Fragment, null, [h('div', 'outerB'), slots.default?.()])
},
},
5,
)
const outerToggle = ref(false)
const innerToggle = ref(false)
/**
* <Suspense>
* <component :is="outerToggle ? outerB : outerA">
* <Suspense>
* <component :is="innerToggle ? innerB : innerA" />
* </Suspense>
* </component>
* </Suspense>
*/
const Comp = {
setup() {
return () =>
h(Suspense, null, {
default: [
h(outerToggle.value ? OuterB : OuterA, null, {
default: () =>
h(Suspense, null, {
default: h(innerToggle.value ? InnerB : InnerA),
}),
}),
],
fallback: h('div', 'fallback outer'),
})
},
}
const root = nodeOps.createElement('div')
render(h(Comp), root)
expect(serializeInner(root)).toBe(`<div>fallback outer</div>`)
// mount outer component
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>outerA</div><!---->`)
expect(calls).toEqual([`outerA created`, `outerA mounted`])
// mount inner component
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>outerA</div><div>innerA</div>`)
expect(calls).toEqual([
'outerA created',
'outerA mounted',
'innerA created',
'innerA mounted',
])
calls.length = 0
deps.length = 0
// toggle both outer and inner components
outerToggle.value = true
innerToggle.value = true
await nextTick()
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>outerB</div><!---->`)
await Promise.all(deps)
await nextTick()
expect(serializeInner(root)).toBe(`<div>outerB</div><div>innerB</div>`)
// innerB only mount once
expect(calls).toEqual([
'outerB created',
'outerB mounted',
'innerB created',
'innerB mounted',
])
})
// #6416 // #6416
test('KeepAlive with Suspense', async () => { test('KeepAlive with Suspense', async () => {
const Async = defineAsyncComponent({ const Async = defineAsyncComponent({
@ -1692,7 +1827,7 @@ describe('Suspense', () => {
expect(serializeInner(root)).toBe(`<div>sync</div>`) expect(serializeInner(root)).toBe(`<div>sync</div>`)
}) })
// #6416 follow up // #6416 follow up / #10017
test('Suspense patched during HOC async component re-mount', async () => { test('Suspense patched during HOC async component re-mount', async () => {
const key = ref('k') const key = ref('k')
const data = ref('data') const data = ref('data')
@ -1713,7 +1848,7 @@ describe('Suspense', () => {
const App = { const App = {
render() { render() {
return h(Suspense, null, { return h(Suspense, null, {
default: h(Comp, { data: data.value }), default: h(Comp, { k: key.value, data: data.value }),
}) })
}, },
} }

View File

@ -0,0 +1,529 @@
import {
Fragment,
type Ref,
createApp,
createBlock,
createElementBlock,
createElementVNode,
createVNode,
defineComponent,
h,
nextTick,
nodeOps,
openBlock,
ref,
render,
serializeInner,
watch,
} from '@vue/runtime-test'
import { useModel } from '../../src/helpers/useModel'
describe('useModel', () => {
test('basic', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const compRender = vi.fn()
const Comp = defineComponent({
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props) {
foo = useModel(props, 'modelValue')
return () => {
compRender()
return foo.value
}
},
})
const msg = ref('')
const setValue = vi.fn(v => (msg.value = v))
const root = nodeOps.createElement('div')
createApp(() =>
h(Comp, {
modelValue: msg.value,
'onUpdate:modelValue': setValue,
}),
).mount(root)
expect(foo.value).toBe('')
expect(msg.value).toBe('')
expect(setValue).not.toBeCalled()
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('')
// update from child
update()
await nextTick()
expect(msg.value).toBe('bar')
expect(foo.value).toBe('bar')
expect(setValue).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('bar')
// update from parent
msg.value = 'qux'
expect(msg.value).toBe('qux')
await nextTick()
expect(msg.value).toBe('qux')
expect(foo.value).toBe('qux')
expect(setValue).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(3)
expect(serializeInner(root)).toBe('qux')
})
test('without parent value (local mutation)', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const compRender = vi.fn()
const Comp = defineComponent({
props: ['foo'],
emits: ['update:foo'],
setup(props) {
foo = useModel(props, 'foo')
return () => {
compRender()
return foo.value
}
},
})
const root = nodeOps.createElement('div')
const updateFoo = vi.fn()
render(h(Comp, { 'onUpdate:foo': updateFoo }), root)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('<!---->')
expect(foo.value).toBeUndefined()
update()
// when parent didn't provide value, local mutation is enabled
expect(foo.value).toBe('bar')
await nextTick()
expect(updateFoo).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('bar')
})
test('without parent listener (local mutation)', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const compRender = vi.fn()
const Comp = defineComponent({
props: ['foo'],
emits: ['update:foo'],
setup(props) {
foo = useModel(props, 'foo')
return () => {
compRender()
return foo.value
}
},
})
const root = nodeOps.createElement('div')
// provide initial value
render(h(Comp, { foo: 'initial' }), root)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')
expect(foo.value).toBe('initial')
update()
// when parent didn't provide value, local mutation is enabled
expect(foo.value).toBe('bar')
await nextTick()
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('bar')
})
test('kebab-case v-model (should not be local)', async () => {
let foo: any
const compRender = vi.fn()
const Comp = defineComponent({
props: ['fooBar'],
emits: ['update:fooBar'],
setup(props) {
foo = useModel(props, 'fooBar')
return () => {
compRender()
return foo.value
}
},
})
const updateFooBar = vi.fn()
const root = nodeOps.createElement('div')
// v-model:foo-bar compiles to foo-bar and onUpdate:fooBar
render(
h(Comp, { 'foo-bar': 'initial', 'onUpdate:fooBar': updateFooBar }),
root,
)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')
expect(foo.value).toBe('initial')
foo.value = 'bar'
// should not be using local mode, so nothing should actually change
expect(foo.value).toBe('initial')
await nextTick()
expect(compRender).toBeCalledTimes(1)
expect(updateFooBar).toBeCalledTimes(1)
expect(updateFooBar).toHaveBeenCalledWith('bar')
expect(foo.value).toBe('initial')
expect(serializeInner(root)).toBe('initial')
})
test('kebab-case update listener (should not be local)', async () => {
let foo: any
const compRender = vi.fn()
const Comp = defineComponent({
props: ['fooBar'],
emits: ['update:fooBar'],
setup(props) {
foo = useModel(props, 'fooBar')
return () => {
compRender()
return foo.value
}
},
})
const updateFooBar = vi.fn()
const root = nodeOps.createElement('div')
// The template compiler won't create hyphenated listeners, but it could have been passed manually
render(
h(Comp, { 'foo-bar': 'initial', 'onUpdate:foo-bar': updateFooBar }),
root,
)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('initial')
expect(foo.value).toBe('initial')
foo.value = 'bar'
// should not be using local mode, so nothing should actually change
expect(foo.value).toBe('initial')
await nextTick()
expect(compRender).toBeCalledTimes(1)
expect(updateFooBar).toBeCalledTimes(1)
expect(updateFooBar).toHaveBeenCalledWith('bar')
expect(foo.value).toBe('initial')
expect(serializeInner(root)).toBe('initial')
})
test('default value', async () => {
let count: any
const inc = () => {
count.value++
}
const compRender = vi.fn()
const Comp = defineComponent({
props: { count: { default: 0 } },
emits: ['update:count'],
setup(props) {
count = useModel(props, 'count')
return () => {
compRender()
return count.value
}
},
})
const root = nodeOps.createElement('div')
const updateCount = vi.fn()
render(h(Comp, { 'onUpdate:count': updateCount }), root)
expect(compRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('0')
expect(count.value).toBe(0)
inc()
// when parent didn't provide value, local mutation is enabled
expect(count.value).toBe(1)
await nextTick()
expect(updateCount).toBeCalledTimes(1)
expect(compRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('1')
})
test('parent limiting child value', async () => {
let childCount: Ref<number>
const compRender = vi.fn()
const Comp = defineComponent({
props: ['count'],
emits: ['update:count'],
setup(props) {
childCount = useModel(props, 'count')
return () => {
compRender()
return childCount.value
}
},
})
const Parent = defineComponent({
setup() {
const count = ref(0)
watch(count, () => {
if (count.value < 0) {
count.value = 0
}
})
return () =>
h(Comp, {
count: count.value,
'onUpdate:count': val => {
count.value = val
},
})
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(serializeInner(root)).toBe('0')
// child update
childCount!.value = 1
// not yet updated
expect(childCount!.value).toBe(0)
await nextTick()
expect(childCount!.value).toBe(1)
expect(serializeInner(root)).toBe('1')
// child update to invalid value
childCount!.value = -1
// not yet updated
expect(childCount!.value).toBe(1)
await nextTick()
// limited to 0 by parent
expect(childCount!.value).toBe(0)
expect(serializeInner(root)).toBe('0')
})
test('has parent value -> no parent value', async () => {
let childCount: Ref<number>
const compRender = vi.fn()
const Comp = defineComponent({
props: ['count'],
emits: ['update:count'],
setup(props) {
childCount = useModel(props, 'count')
return () => {
compRender()
return childCount.value
}
},
})
const toggle = ref(true)
const Parent = defineComponent({
setup() {
const count = ref(0)
return () =>
toggle.value
? h(Comp, {
count: count.value,
'onUpdate:count': val => {
count.value = val
},
})
: h(Comp)
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(serializeInner(root)).toBe('0')
// child update
childCount!.value = 1
// not yet updated
expect(childCount!.value).toBe(0)
await nextTick()
expect(childCount!.value).toBe(1)
expect(serializeInner(root)).toBe('1')
// parent change
toggle.value = false
await nextTick()
// localValue should be reset
expect(childCount!.value).toBeUndefined()
expect(serializeInner(root)).toBe('<!---->')
// child local mutation should continue to work
childCount!.value = 2
expect(childCount!.value).toBe(2)
await nextTick()
expect(serializeInner(root)).toBe('2')
})
// #9838
test('pass modelValue to slot (optimized mode) ', async () => {
let foo: any
const update = () => {
foo.value = 'bar'
}
const Comp = {
render(this: any) {
return this.$slots.default()
},
}
const childRender = vi.fn()
const slotRender = vi.fn()
const Child = defineComponent({
props: ['modelValue'],
emits: ['update:modelValue'],
setup(props) {
foo = useModel(props, 'modelValue')
return () => {
childRender()
return (
openBlock(),
createElementBlock(Fragment, null, [
createVNode(Comp, null, {
default: () => {
slotRender()
return createElementVNode('div', null, foo.value)
},
_: 1 /* STABLE */,
}),
])
)
}
},
})
const msg = ref('')
const setValue = vi.fn(v => (msg.value = v))
const root = nodeOps.createElement('div')
createApp({
render() {
return (
openBlock(),
createBlock(
Child,
{
modelValue: msg.value,
'onUpdate:modelValue': setValue,
},
null,
8 /* PROPS */,
['modelValue'],
)
)
},
}).mount(root)
expect(foo.value).toBe('')
expect(msg.value).toBe('')
expect(setValue).not.toBeCalled()
expect(childRender).toBeCalledTimes(1)
expect(slotRender).toBeCalledTimes(1)
expect(serializeInner(root)).toBe('<div></div>')
// update from child
update()
await nextTick()
expect(msg.value).toBe('bar')
expect(foo.value).toBe('bar')
expect(setValue).toBeCalledTimes(1)
expect(childRender).toBeCalledTimes(2)
expect(slotRender).toBeCalledTimes(2)
expect(serializeInner(root)).toBe('<div>bar</div>')
})
test('with modifiers & transformers', async () => {
let childMsg: Ref<string>
let childModifiers: Record<string, true | undefined>
const compRender = vi.fn()
const Comp = defineComponent({
props: ['msg', 'msgModifiers'],
emits: ['update:msg'],
setup(props) {
;[childMsg, childModifiers] = useModel(props, 'msg', {
get(val) {
return val.toLowerCase()
},
set(val) {
if (childModifiers.upper) {
return val.toUpperCase()
}
},
})
return () => {
compRender()
return childMsg.value
}
},
})
const msg = ref('HI')
const Parent = defineComponent({
setup() {
return () =>
h(Comp, {
msg: msg.value,
msgModifiers: { upper: true },
'onUpdate:msg': val => {
msg.value = val
},
})
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
// should be lowered
expect(serializeInner(root)).toBe('hi')
// child update
childMsg!.value = 'Hmm'
await nextTick()
expect(childMsg!.value).toBe('hmm')
expect(serializeInner(root)).toBe('hmm')
// parent should get uppercase value
expect(msg.value).toBe('HMM')
// parent update
msg.value = 'Ughh'
await nextTick()
expect(serializeInner(root)).toBe('ughh')
expect(msg.value).toBe('Ughh')
// child update again
childMsg!.value = 'ughh'
await nextTick()
expect(msg.value).toBe('UGHH')
})
})

View File

@ -1080,9 +1080,7 @@ describe('SSR hydration', () => {
}) })
test('force hydrate prop with `.prop` modifier', () => { test('force hydrate prop with `.prop` modifier', () => {
const { container } = mountWithHydration( const { container } = mountWithHydration('<input type="checkbox">', () =>
'<input type="checkbox" :indeterminate.prop="true">',
() =>
h('input', { h('input', {
type: 'checkbox', type: 'checkbox',
'.indeterminate': true, '.indeterminate': true,
@ -1431,11 +1429,35 @@ describe('SSR hydration', () => {
mountWithHydration(`<div style="color:red;"></div>`, () => mountWithHydration(`<div style="color:red;"></div>`, () =>
h('div', { style: `color:red;` }), h('div', { style: `color:red;` }),
) )
mountWithHydration(
`<div style="color:red; font-size: 12px;"></div>`,
() => h('div', { style: `font-size: 12px; color:red;` }),
)
mountWithHydration(`<div style="color:red;display:none;"></div>`, () =>
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
[vShow, false],
]),
)
expect(`Hydration style mismatch`).not.toHaveBeenWarned() expect(`Hydration style mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<div style="color:red;"></div>`, () => mountWithHydration(`<div style="color:red;"></div>`, () =>
h('div', { style: { color: 'green' } }), h('div', { style: { color: 'green' } }),
) )
expect(`Hydration style mismatch`).toHaveBeenWarned() expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
})
test('style mismatch w/ v-show', () => {
mountWithHydration(`<div style="color:red;display:none"></div>`, () =>
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
[vShow, false],
]),
)
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<div style="color:red;"></div>`, () =>
withDirectives(createVNode('div', { style: 'color: red' }, ''), [
[vShow, false],
]),
)
expect(`Hydration style mismatch`).toHaveBeenWarnedTimes(1)
}) })
test('attr mismatch', () => { test('attr mismatch', () => {
@ -1454,10 +1476,60 @@ describe('SSR hydration', () => {
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned() expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' })) mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
expect(`Hydration attribute mismatch`).toHaveBeenWarned() expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(1)
mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' })) mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2) expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
}) })
test('attr special case: textarea value', () => {
mountWithHydration(`<textarea>foo</textarea>`, () =>
h('textarea', { value: 'foo' }),
)
mountWithHydration(`<textarea></textarea>`, () =>
h('textarea', { value: '' }),
)
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<textarea>foo</textarea>`, () =>
h('textarea', { value: 'bar' }),
)
expect(`Hydration attribute mismatch`).toHaveBeenWarned()
})
test('boolean attr handling', () => {
mountWithHydration(`<input />`, () => h('input', { readonly: false }))
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<input readonly />`, () =>
h('input', { readonly: true }),
)
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<input readonly="readonly" />`, () =>
h('input', { readonly: true }),
)
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
})
test('client value is null or undefined', () => {
mountWithHydration(`<div></div>`, () =>
h('div', { draggable: undefined }),
)
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
mountWithHydration(`<input />`, () => h('input', { type: null }))
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
})
test('should not warn against object values', () => {
mountWithHydration(`<input />`, () => h('input', { from: {} }))
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
})
test('should not warn on falsy bindings of non-property keys', () => {
mountWithHydration(`<button />`, () => h('button', { href: undefined }))
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
})
}) })
}) })

View File

@ -8,6 +8,8 @@ import {
type FunctionalComponent, type FunctionalComponent,
createBlock, createBlock,
createCommentVNode, createCommentVNode,
createElementBlock,
createElementVNode,
defineComponent, defineComponent,
h, h,
mergeProps, mergeProps,
@ -673,6 +675,58 @@ describe('attribute fallthrough', () => {
expect(click).toHaveBeenCalled() expect(click).toHaveBeenCalled()
}) })
it('should support fallthrough for nested dev root fragments', async () => {
const toggle = ref(false)
const Child = {
setup() {
return () => (
openBlock(),
createElementBlock(
Fragment,
null,
[
createCommentVNode(' comment A '),
toggle.value
? (openBlock(), createElementBlock('span', { key: 0 }, 'Foo'))
: (openBlock(),
createElementBlock(
Fragment,
{ key: 1 },
[
createCommentVNode(' comment B '),
createElementVNode('div', null, 'Bar'),
],
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
)),
],
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
)
)
},
}
const Root = {
setup() {
return () => (openBlock(), createBlock(Child, { class: 'red' }))
},
}
const root = document.createElement('div')
document.body.appendChild(root)
render(h(Root), root)
expect(root.innerHTML).toBe(
`<!-- comment A --><!-- comment B --><div class="red">Bar</div>`,
)
toggle.value = true
await nextTick()
expect(root.innerHTML).toBe(
`<!-- comment A --><span class=\"red\">Foo</span>`,
)
})
// #1989 // #1989
it('should not fallthrough v-model listeners with corresponding declared prop', () => { it('should not fallthrough v-model listeners with corresponding declared prop', () => {
let textFoo = '' let textFoo = ''

View File

@ -351,4 +351,16 @@ describe('renderer: fragment', () => {
render(renderFn(['two', 'one']), root) render(renderFn(['two', 'one']), root)
expect(serializeInner(root)).toBe(`text<div>two</div>text<div>one</div>`) expect(serializeInner(root)).toBe(`text<div>two</div>text<div>one</div>`)
}) })
// #10007
test('empty fragment', () => {
const root = nodeOps.createElement('div')
const renderFn = () => {
return openBlock(true), createBlock(Fragment, null)
}
render(renderFn(), root)
expect(serializeInner(root)).toBe('')
})
}) })

View File

@ -610,4 +610,25 @@ describe('scheduler', () => {
expect(await p).toBe(1) expect(await p).toBe(1)
expect(fn).toHaveBeenCalledTimes(1) expect(fn).toHaveBeenCalledTimes(1)
}) })
// #10003
test('nested flushPostFlushCbs', async () => {
const calls: string[] = []
const cb1 = () => calls.push('cb1')
// cb1 has no id
const cb2 = () => calls.push('cb2')
cb2.id = -1
const queueAndFlush = (hook: Function) => {
queuePostFlushCb(hook)
flushPostFlushCbs()
}
queueAndFlush(() => {
queuePostFlushCb([cb1, cb2])
flushPostFlushCbs()
})
await nextTick()
expect(calls).toEqual(['cb2', 'cb1'])
})
}) })

View File

@ -1,14 +1,23 @@
import { import {
Fragment,
createBlock,
createCommentVNode,
createVNode,
defineComponent,
h, h,
nextTick,
nodeOps, nodeOps,
openBlock,
popScopeId, popScopeId,
pushScopeId, pushScopeId,
ref,
render, render,
renderSlot, renderSlot,
serializeInner, serializeInner,
withScopeId, withScopeId,
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { withCtx } from '../src/componentRenderContext' import { withCtx } from '../src/componentRenderContext'
import { PatchFlags } from '@vue/shared'
describe('scopeId runtime support', () => { describe('scopeId runtime support', () => {
test('should attach scopeId', () => { test('should attach scopeId', () => {
@ -184,6 +193,55 @@ describe('scopeId runtime support', () => {
expect(serializeInner(root)).toBe(`<div parent></div>`) expect(serializeInner(root)).toBe(`<div parent></div>`)
}) })
test('should inherit scopeId through nested DEV_ROOT_FRAGMENT with inheritAttrs: false', async () => {
const Parent = {
__scopeId: 'parent',
render() {
return h(Child, { class: 'foo' })
},
}
const ok = ref(true)
const Child = defineComponent({
inheritAttrs: false,
render() {
return (
openBlock(),
createBlock(
Fragment,
null,
[
createCommentVNode('comment1'),
ok.value
? (openBlock(), createBlock('div', { key: 0 }, 'div1'))
: (openBlock(),
createBlock(
Fragment,
{ key: 1 },
[
createCommentVNode('comment2'),
createVNode('div', null, 'div2'),
],
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
)),
],
PatchFlags.STABLE_FRAGMENT | PatchFlags.DEV_ROOT_FRAGMENT,
)
)
},
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(serializeInner(root)).toBe(`<!--comment1--><div parent>div1</div>`)
ok.value = false
await nextTick()
expect(serializeInner(root)).toBe(
`<!--comment1--><!--comment2--><div parent>div2</div>`,
)
})
}) })
describe('backwards compat with <=3.0.7', () => { describe('backwards compat with <=3.0.7', () => {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/runtime-core", "name": "@vue/runtime-core",
"version": "3.4.5", "version": "3.4.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",
@ -17,6 +17,7 @@
"development": "./dist/runtime-core.cjs.js", "development": "./dist/runtime-core.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/runtime-core.esm-bundler.js",
"import": "./dist/runtime-core.esm-bundler.js", "import": "./dist/runtime-core.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -3,7 +3,6 @@ import {
currentInstance, currentInstance,
isInSSRComponentSetup, isInSSRComponentSetup,
setCurrentInstance, setCurrentInstance,
unsetCurrentInstance,
} from './component' } from './component'
import type { ComponentPublicInstance } from './componentPublicInstance' import type { ComponentPublicInstance } from './componentPublicInstance'
import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling' import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling'
@ -41,9 +40,9 @@ export function injectHook(
// Set currentInstance during hook invocation. // Set currentInstance during hook invocation.
// This assumes the hook does not synchronously trigger other hooks, which // This assumes the hook does not synchronously trigger other hooks, which
// can only be false when the user does something really funky. // can only be false when the user does something really funky.
setCurrentInstance(target) const reset = setCurrentInstance(target)
const res = callWithAsyncErrorHandling(hook, target, type, args) const res = callWithAsyncErrorHandling(hook, target, type, args)
unsetCurrentInstance() reset()
resetTracking() resetTracking()
return res return res
}) })

View File

@ -1,12 +1,8 @@
import { import {
EMPTY_OBJ,
type LooseRequired, type LooseRequired,
type Prettify, type Prettify,
type UnionToIntersection, type UnionToIntersection,
camelize,
extend, extend,
hasChanged,
hyphenate,
isArray, isArray,
isFunction, isFunction,
isPromise, isPromise,
@ -29,13 +25,11 @@ import type {
ComponentObjectPropsOptions, ComponentObjectPropsOptions,
ComponentPropsOptions, ComponentPropsOptions,
ExtractPropTypes, ExtractPropTypes,
NormalizedProps,
PropOptions, PropOptions,
} from './componentProps' } from './componentProps'
import { warn } from './warning' import { warn } from './warning'
import type { SlotsType, StrictUnwrapSlotsType } from './componentSlots' import type { SlotsType, StrictUnwrapSlotsType } from './componentSlots'
import { type Ref, customRef, ref } from '@vue/reactivity' import type { Ref } from '@vue/reactivity'
import { watchSyncEffect } from '.'
// dev only // dev only
const warnRuntimeUsage = (method: string) => const warnRuntimeUsage = (method: string) =>
@ -224,6 +218,11 @@ export function defineSlots<
export type ModelRef<T, M extends string | number | symbol = string> = Ref<T> & export type ModelRef<T, M extends string | number | symbol = string> = Ref<T> &
[ModelRef<T, M>, Record<M, true | undefined>] [ModelRef<T, M>, Record<M, true | undefined>]
export type DefineModelOptions<T = any> = {
get?: (v: T) => any
set?: (v: T) => any
}
/** /**
* Vue `<script setup>` compiler macro for declaring a * Vue `<script setup>` compiler macro for declaring a
* two-way binding prop that can be consumed via `v-model` from the parent * two-way binding prop that can be consumed via `v-model` from the parent
@ -258,25 +257,25 @@ export type ModelRef<T, M extends string | number | symbol = string> = Ref<T> &
* ``` * ```
*/ */
export function defineModel<T, M extends string | number | symbol = string>( export function defineModel<T, M extends string | number | symbol = string>(
options: { required: true } & PropOptions<T> & UseModelOptions<T>, options: { required: true } & PropOptions<T> & DefineModelOptions<T>,
): ModelRef<T, M> ): ModelRef<T, M>
export function defineModel<T, M extends string | number | symbol = string>( export function defineModel<T, M extends string | number | symbol = string>(
options: { default: any } & PropOptions<T> & UseModelOptions<T>, options: { default: any } & PropOptions<T> & DefineModelOptions<T>,
): ModelRef<T, M> ): ModelRef<T, M>
export function defineModel<T, M extends string | number | symbol = string>( export function defineModel<T, M extends string | number | symbol = string>(
options?: PropOptions<T> & UseModelOptions<T>, options?: PropOptions<T> & DefineModelOptions<T>,
): ModelRef<T | undefined, M> ): ModelRef<T | undefined, M>
export function defineModel<T, M extends string | number | symbol = string>( export function defineModel<T, M extends string | number | symbol = string>(
name: string, name: string,
options: { required: true } & PropOptions<T> & UseModelOptions<T>, options: { required: true } & PropOptions<T> & DefineModelOptions<T>,
): ModelRef<T, M> ): ModelRef<T, M>
export function defineModel<T, M extends string | number | symbol = string>( export function defineModel<T, M extends string | number | symbol = string>(
name: string, name: string,
options: { default: any } & PropOptions<T> & UseModelOptions<T>, options: { default: any } & PropOptions<T> & DefineModelOptions<T>,
): ModelRef<T, M> ): ModelRef<T, M>
export function defineModel<T, M extends string | number | symbol = string>( export function defineModel<T, M extends string | number | symbol = string>(
name: string, name: string,
options?: PropOptions<T> & UseModelOptions<T>, options?: PropOptions<T> & DefineModelOptions<T>,
): ModelRef<T | undefined, M> ): ModelRef<T | undefined, M>
export function defineModel(): any { export function defineModel(): any {
if (__DEV__) { if (__DEV__) {
@ -356,92 +355,6 @@ export function useAttrs(): SetupContext['attrs'] {
return getContext().attrs return getContext().attrs
} }
type UseModelOptions<T = any> = {
get?: (v: T) => any
set?: (v: T) => any
}
export function useModel<
M extends string | number | symbol,
T extends Record<string, any>,
K extends keyof T,
>(props: T, name: K, options?: UseModelOptions<T[K]>): ModelRef<T[K], M>
export function useModel(
props: Record<string, any>,
name: string,
options: UseModelOptions = EMPTY_OBJ,
): Ref {
const i = getCurrentInstance()!
if (__DEV__ && !i) {
warn(`useModel() called without active instance.`)
return ref() as any
}
if (__DEV__ && !(i.propsOptions[0] as NormalizedProps)[name]) {
warn(`useModel() called with prop "${name}" which is not declared.`)
return ref() as any
}
const camelizedName = camelize(name)
const hyphenatedName = hyphenate(name)
const res = customRef((track, trigger) => {
let localValue: any
watchSyncEffect(() => {
const propValue = props[name]
if (hasChanged(localValue, propValue)) {
localValue = propValue
trigger()
}
})
return {
get() {
track()
return options.get ? options.get(localValue) : localValue
},
set(value) {
const rawProps = i.vnode!.props
if (
!(
rawProps &&
// check if parent has passed v-model
(name in rawProps ||
camelizedName in rawProps ||
hyphenatedName in rawProps) &&
(`onUpdate:${name}` in rawProps ||
`onUpdate:${camelizedName}` in rawProps ||
`onUpdate:${hyphenatedName}` in rawProps)
) &&
hasChanged(value, localValue)
) {
localValue = value
trigger()
}
i.emit(`update:${name}`, options.set ? options.set(value) : value)
},
}
})
const modifierKey =
name === 'modelValue' ? 'modelModifiers' : `${name}Modifiers`
// @ts-expect-error
res[Symbol.iterator] = () => {
let i = 0
return {
next() {
if (i < 2) {
return { value: i++ ? props[modifierKey] || {} : res, done: false }
} else {
return { done: true }
}
},
}
}
return res
}
function getContext(): SetupContext { function getContext(): SetupContext {
const i = getCurrentInstance()! const i = getCurrentInstance()!
if (__DEV__ && !i) { if (__DEV__ && !i) {

View File

@ -25,7 +25,6 @@ import {
currentInstance, currentInstance,
isInSSRComponentSetup, isInSSRComponentSetup,
setCurrentInstance, setCurrentInstance,
unsetCurrentInstance,
} from './component' } from './component'
import { handleError as handleErrorWithInstance } from './errorHandling' import { handleError as handleErrorWithInstance } from './errorHandling'
import { createPostRenderScheduler } from './renderer' import { createPostRenderScheduler } from './renderer'
@ -262,14 +261,9 @@ export function instanceWatch(
cb = value.handler as Function cb = value.handler as Function
options = value options = value
} }
const cur = currentInstance const reset = setCurrentInstance(this)
setCurrentInstance(this)
const res = doWatch(getter, cb.bind(publicThis), options) const res = doWatch(getter, cb.bind(publicThis), options)
if (cur) { reset()
setCurrentInstance(cur)
} else {
unsetCurrentInstance()
}
return res return res
} }

View File

@ -684,8 +684,13 @@ if (__SSR__) {
} }
export const setCurrentInstance = (instance: ComponentInternalInstance) => { export const setCurrentInstance = (instance: ComponentInternalInstance) => {
const prev = currentInstance
internalSetCurrentInstance(instance) internalSetCurrentInstance(instance)
instance.scope.on() instance.scope.on()
return () => {
instance.scope.off()
internalSetCurrentInstance(prev)
}
} }
export const unsetCurrentInstance = () => { export const unsetCurrentInstance = () => {
@ -773,7 +778,7 @@ function setupStatefulComponent(
const setupContext = (instance.setupContext = const setupContext = (instance.setupContext =
setup.length > 1 ? createSetupContext(instance) : null) setup.length > 1 ? createSetupContext(instance) : null)
setCurrentInstance(instance) const reset = setCurrentInstance(instance)
pauseTracking() pauseTracking()
const setupResult = callWithErrorHandling( const setupResult = callWithErrorHandling(
setup, setup,
@ -785,7 +790,7 @@ function setupStatefulComponent(
], ],
) )
resetTracking() resetTracking()
unsetCurrentInstance() reset()
if (isPromise(setupResult)) { if (isPromise(setupResult)) {
setupResult.then(unsetCurrentInstance, unsetCurrentInstance) setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
@ -960,13 +965,13 @@ export function finishComponentSetup(
// support for 2.x options // support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) { if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
setCurrentInstance(instance) const reset = setCurrentInstance(instance)
pauseTracking() pauseTracking()
try { try {
applyOptions(instance) applyOptions(instance)
} finally { } finally {
resetTracking() resetTracking()
unsetCurrentInstance() reset()
} }
} }

View File

@ -32,7 +32,6 @@ import {
type ComponentOptions, type ComponentOptions,
type ConcreteComponent, type ConcreteComponent,
setCurrentInstance, setCurrentInstance,
unsetCurrentInstance,
} from './component' } from './component'
import { isEmitListener } from './componentEmits' import { isEmitListener } from './componentEmits'
import { InternalObjectKey } from './vnode' import { InternalObjectKey } from './vnode'
@ -470,7 +469,7 @@ function resolvePropValue(
if (key in propsDefaults) { if (key in propsDefaults) {
value = propsDefaults[key] value = propsDefaults[key]
} else { } else {
setCurrentInstance(instance) const reset = setCurrentInstance(instance)
value = propsDefaults[key] = defaultValue.call( value = propsDefaults[key] = defaultValue.call(
__COMPAT__ && __COMPAT__ &&
isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance) isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
@ -478,7 +477,7 @@ function resolvePropValue(
: null, : null,
props, props,
) )
unsetCurrentInstance() reset()
} }
} else { } else {
value = defaultValue value = defaultValue

View File

@ -271,10 +271,17 @@ export function renderComponentRoot(
const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => { const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
const rawChildren = vnode.children as VNodeArrayChildren const rawChildren = vnode.children as VNodeArrayChildren
const dynamicChildren = vnode.dynamicChildren const dynamicChildren = vnode.dynamicChildren
const childRoot = filterSingleRoot(rawChildren) const childRoot = filterSingleRoot(rawChildren, false)
if (!childRoot) { if (!childRoot) {
return [vnode, undefined] return [vnode, undefined]
} else if (
__DEV__ &&
childRoot.patchFlag > 0 &&
childRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
) {
return getChildRoot(childRoot)
} }
const index = rawChildren.indexOf(childRoot) const index = rawChildren.indexOf(childRoot)
const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1 const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
const setRoot: SetRootFn = (updatedRoot: VNode) => { const setRoot: SetRootFn = (updatedRoot: VNode) => {
@ -292,6 +299,7 @@ const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
export function filterSingleRoot( export function filterSingleRoot(
children: VNodeArrayChildren, children: VNodeArrayChildren,
recurse = true,
): VNode | undefined { ): VNode | undefined {
let singleRoot let singleRoot
for (let i = 0; i < children.length; i++) { for (let i = 0; i < children.length; i++) {
@ -304,6 +312,14 @@ export function filterSingleRoot(
return return
} else { } else {
singleRoot = child singleRoot = child
if (
__DEV__ &&
recurse &&
singleRoot.patchFlag > 0 &&
singleRoot.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
) {
return filterSingleRoot(singleRoot.children as VNodeArrayChildren)
}
} }
} }
} else { } else {
@ -433,7 +449,6 @@ export function updateHOCHostEl(
{ vnode, parent }: ComponentInternalInstance, { vnode, parent }: ComponentInternalInstance,
el: typeof vnode.el, // HostNode el: typeof vnode.el, // HostNode
) { ) {
if (!el) return
while (parent) { while (parent) {
const root = parent.subTree const root = parent.subTree
if (root.suspense && root.suspense.activeBranch === vnode) { if (root.suspense && root.suspense.activeBranch === vnode) {

View File

@ -91,6 +91,18 @@ export const SuspenseImpl = {
rendererInternals, rendererInternals,
) )
} else { } else {
// #8678 if the current suspense needs to be patched and parentSuspense has
// not been resolved. this means that both the current suspense and parentSuspense
// need to be patched. because parentSuspense's pendingBranch includes the
// current suspense, it will be processed twice:
// 1. current patch
// 2. mounting along with the pendingBranch of parentSuspense
// it is necessary to skip the current patch to avoid multiple mounts
// of inner components.
if (parentSuspense && parentSuspense.deps > 0) {
n2.suspense = n1.suspense
return
}
patchSuspense( patchSuspense(
n1, n1,
n2, n2,
@ -400,7 +412,6 @@ export interface SuspenseBoundary {
namespace: ElementNamespace namespace: ElementNamespace
container: RendererElement container: RendererElement
hiddenContainer: RendererElement hiddenContainer: RendererElement
anchor: RendererNode | null
activeBranch: VNode | null activeBranch: VNode | null
pendingBranch: VNode | null pendingBranch: VNode | null
deps: number deps: number
@ -473,6 +484,7 @@ function createSuspenseBoundary(
assertNumber(timeout, `Suspense timeout`) assertNumber(timeout, `Suspense timeout`)
} }
const initialAnchor = anchor
const suspense: SuspenseBoundary = { const suspense: SuspenseBoundary = {
vnode, vnode,
parent: parentSuspense, parent: parentSuspense,
@ -480,7 +492,6 @@ function createSuspenseBoundary(
namespace, namespace,
container, container,
hiddenContainer, hiddenContainer,
anchor,
deps: 0, deps: 0,
pendingId: suspenseId++, pendingId: suspenseId++,
timeout: typeof timeout === 'number' ? timeout : -1, timeout: typeof timeout === 'number' ? timeout : -1,
@ -529,20 +540,28 @@ function createSuspenseBoundary(
move( move(
pendingBranch!, pendingBranch!,
container, container,
next(activeBranch!), anchor === initialAnchor ? next(activeBranch!) : anchor,
MoveType.ENTER, MoveType.ENTER,
) )
queuePostFlushCb(effects) queuePostFlushCb(effects)
} }
} }
} }
// this is initial anchor on mount
let { anchor } = suspense
// unmount current active tree // unmount current active tree
if (activeBranch) { if (activeBranch) {
// if the fallback tree was mounted, it may have been moved // if the fallback tree was mounted, it may have been moved
// as part of a parent suspense. get the latest anchor for insertion // as part of a parent suspense. get the latest anchor for insertion
// #8105 if `delayEnter` is true, it means that the mounting of
// `activeBranch` will be delayed. if the branch switches before
// transition completes, both `activeBranch` and `pendingBranch` may
// coexist in the `hiddenContainer`. This could result in
// `next(activeBranch!)` obtaining an incorrect anchor
// (got `pendingBranch.el`).
// Therefore, after the mounting of activeBranch is completed,
// it is necessary to get the latest anchor.
if (parentNode(activeBranch.el!) !== suspense.hiddenContainer) {
anchor = next(activeBranch) anchor = next(activeBranch)
}
unmount(activeBranch, parentComponent, suspense, true) unmount(activeBranch, parentComponent, suspense, true)
} }
if (!delayEnter) { if (!delayEnter) {
@ -857,7 +876,14 @@ export function queueEffectWithSuspense(
function setActiveBranch(suspense: SuspenseBoundary, branch: VNode) { function setActiveBranch(suspense: SuspenseBoundary, branch: VNode) {
suspense.activeBranch = branch suspense.activeBranch = branch
const { vnode, parentComponent } = suspense const { vnode, parentComponent } = suspense
const el = (vnode.el = branch.el) let el = branch.el
// if branch has no el after patch, it's a HOC wrapping async components
// drill and locate the placeholder comment node
while (!el && branch.component) {
branch = branch.component.subTree
el = branch.el
}
vnode.el = el
// in case suspense is the root node of a component, // in case suspense is the root node of a component,
// recursively update the HOC el // recursively update the HOC el
if (parentComponent && parentComponent.subTree === vnode) { if (parentComponent && parentComponent.subTree === vnode) {

View File

@ -88,14 +88,13 @@ export function withDirectives<T extends VNode>(
vnode: T, vnode: T,
directives: DirectiveArguments, directives: DirectiveArguments,
): T { ): T {
const internalInstance = currentRenderingInstance if (currentRenderingInstance === null) {
if (internalInstance === null) {
__DEV__ && warn(`withDirectives can only be used inside render functions.`) __DEV__ && warn(`withDirectives can only be used inside render functions.`)
return vnode return vnode
} }
const instance = const instance =
(getExposeProxy(internalInstance) as ComponentPublicInstance) || (getExposeProxy(currentRenderingInstance) as ComponentPublicInstance) ||
internalInstance.proxy currentRenderingInstance.proxy
const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = []) const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
for (let i = 0; i < directives.length; i++) { for (let i = 0; i < directives.length; i++) {
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i] let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]

View File

@ -10,7 +10,7 @@ import {
} from './vnode' } from './vnode'
import type { Teleport, TeleportProps } from './components/Teleport' import type { Teleport, TeleportProps } from './components/Teleport'
import type { Suspense, SuspenseProps } from './components/Suspense' import type { Suspense, SuspenseProps } from './components/Suspense'
import { isArray, isObject } from '@vue/shared' import { type IfAny, isArray, isObject } from '@vue/shared'
import type { RawSlots } from './componentSlots' import type { RawSlots } from './componentSlots'
import type { import type {
Component, Component,
@ -140,11 +140,11 @@ export function h(
export function h< export function h<
P, P,
E extends EmitsOptions = {}, E extends EmitsOptions = {},
S extends Record<string, any> = {}, S extends Record<string, any> = any,
>( >(
type: FunctionalComponent<P, E, S>, type: FunctionalComponent<P, any, S, any>,
props?: (RawProps & P) | ({} extends P ? null : never), props?: (RawProps & P) | ({} extends P ? null : never),
children?: RawChildren | RawSlots, children?: RawChildren | IfAny<S, RawSlots, S>,
): VNode ): VNode
// catch-all for generic component types // catch-all for generic component types

View File

@ -0,0 +1,88 @@
import { type Ref, customRef, ref } from '@vue/reactivity'
import { EMPTY_OBJ, camelize, hasChanged, hyphenate } from '@vue/shared'
import type { DefineModelOptions, ModelRef } from '../apiSetupHelpers'
import { getCurrentInstance } from '../component'
import { warn } from '../warning'
import type { NormalizedProps } from '../componentProps'
import { watchSyncEffect } from '../apiWatch'
export function useModel<
M extends string | number | symbol,
T extends Record<string, any>,
K extends keyof T,
>(props: T, name: K, options?: DefineModelOptions<T[K]>): ModelRef<T[K], M>
export function useModel(
props: Record<string, any>,
name: string,
options: DefineModelOptions = EMPTY_OBJ,
): Ref {
const i = getCurrentInstance()!
if (__DEV__ && !i) {
warn(`useModel() called without active instance.`)
return ref() as any
}
if (__DEV__ && !(i.propsOptions[0] as NormalizedProps)[name]) {
warn(`useModel() called with prop "${name}" which is not declared.`)
return ref() as any
}
const camelizedName = camelize(name)
const hyphenatedName = hyphenate(name)
const res = customRef((track, trigger) => {
let localValue: any
watchSyncEffect(() => {
const propValue = props[name]
if (hasChanged(localValue, propValue)) {
localValue = propValue
trigger()
}
})
return {
get() {
track()
return options.get ? options.get(localValue) : localValue
},
set(value) {
const rawProps = i.vnode!.props
if (
!(
rawProps &&
// check if parent has passed v-model
(name in rawProps ||
camelizedName in rawProps ||
hyphenatedName in rawProps) &&
(`onUpdate:${name}` in rawProps ||
`onUpdate:${camelizedName}` in rawProps ||
`onUpdate:${hyphenatedName}` in rawProps)
) &&
hasChanged(value, localValue)
) {
localValue = value
trigger()
}
i.emit(`update:${name}`, options.set ? options.set(value) : value)
},
}
})
const modifierKey =
name === 'modelValue' ? 'modelModifiers' : `${name}Modifiers`
// @ts-expect-error
res[Symbol.iterator] = () => {
let i = 0
return {
next() {
if (i < 2) {
return { value: i++ ? props[modifierKey] || {} : res, done: false }
} else {
return { done: true }
}
},
}
}
return res
}

View File

@ -21,6 +21,7 @@ import {
isBooleanAttr, isBooleanAttr,
isKnownHtmlAttr, isKnownHtmlAttr,
isKnownSvgAttr, isKnownSvgAttr,
isObject,
isOn, isOn,
isReservedProp, isReservedProp,
isString, isString,
@ -448,7 +449,7 @@ export function createHydrationFunctions(
) { ) {
for (const key in props) { for (const key in props) {
// check hydration mismatch // check hydration mismatch
if (__DEV__ && propHasMismatch(el, key, props[key])) { if (__DEV__ && propHasMismatch(el, key, props[key], vnode)) {
hasMismatch = true hasMismatch = true
} }
if ( if (
@ -712,7 +713,12 @@ export function createHydrationFunctions(
/** /**
* Dev only * Dev only
*/ */
function propHasMismatch(el: Element, key: string, clientValue: any): boolean { function propHasMismatch(
el: Element,
key: string,
clientValue: any,
vnode: VNode,
): boolean {
let mismatchType: string | undefined let mismatchType: string | undefined
let mismatchKey: string | undefined let mismatchKey: string | undefined
let actual: any let actual: any
@ -720,31 +726,55 @@ function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
if (key === 'class') { if (key === 'class') {
// classes might be in different order, but that doesn't affect cascade // classes might be in different order, but that doesn't affect cascade
// so we just need to check if the class lists contain the same classes. // so we just need to check if the class lists contain the same classes.
actual = toClassSet(el.getAttribute('class') || '') actual = el.getAttribute('class')
expected = toClassSet(normalizeClass(clientValue)) expected = normalizeClass(clientValue)
if (!isSetEqual(actual, expected)) { if (!isSetEqual(toClassSet(actual || ''), toClassSet(expected))) {
mismatchType = mismatchKey = `class` mismatchType = mismatchKey = `class`
} }
} else if (key === 'style') { } else if (key === 'style') {
// style might be in different order, but that doesn't affect cascade
actual = el.getAttribute('style') actual = el.getAttribute('style')
expected = isString(clientValue) expected = isString(clientValue)
? clientValue ? clientValue
: stringifyStyle(normalizeStyle(clientValue)) : stringifyStyle(normalizeStyle(clientValue))
if (actual !== expected) { const actualMap = toStyleMap(actual)
const expectedMap = toStyleMap(expected)
// If `v-show=false`, `display: 'none'` should be added to expected
if (vnode.dirs) {
for (const { dir, value } of vnode.dirs) {
// @ts-expect-error only vShow has this internal name
if (dir.name === 'show' && !value) {
expectedMap.set('display', 'none')
}
}
}
if (!isMapEqual(actualMap, expectedMap)) {
mismatchType = mismatchKey = 'style' mismatchType = mismatchKey = 'style'
} }
} else if ( } else if (
(el instanceof SVGElement && isKnownSvgAttr(key)) || (el instanceof SVGElement && isKnownSvgAttr(key)) ||
(el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key))) (el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key)))
) { ) {
actual = el.hasAttribute(key) && el.getAttribute(key) if (isBooleanAttr(key)) {
expected = isBooleanAttr(key) actual = el.hasAttribute(key)
? includeBooleanAttr(clientValue) expected = includeBooleanAttr(clientValue)
} else if (clientValue == null) {
actual = el.hasAttribute(key)
expected = false
} else {
if (el.hasAttribute(key)) {
actual = el.getAttribute(key)
} else {
// #10000 some attrs such as textarea.value can't be retrieved by `hasAttribute`
const serverValue = el[key as keyof typeof el]
actual =
isObject(serverValue) || serverValue == null
? '' ? ''
: false : String(serverValue)
: clientValue == null }
? false expected =
: String(clientValue) isObject(clientValue) || clientValue == null ? '' : String(clientValue)
}
if (actual !== expected) { if (actual !== expected) {
mismatchType = `attribute` mismatchType = `attribute`
mismatchKey = key mismatchKey = key
@ -754,15 +784,20 @@ function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
if (mismatchType) { if (mismatchType) {
const format = (v: any) => const format = (v: any) =>
v === false ? `(not rendered)` : `${mismatchKey}="${v}"` v === false ? `(not rendered)` : `${mismatchKey}="${v}"`
warn( const preSegment = `Hydration ${mismatchType} mismatch on`
`Hydration ${mismatchType} mismatch on`, const postSegment =
el,
`\n - rendered on server: ${format(actual)}` + `\n - rendered on server: ${format(actual)}` +
`\n - expected on client: ${format(expected)}` + `\n - expected on client: ${format(expected)}` +
`\n Note: this mismatch is check-only. The DOM will not be rectified ` + `\n Note: this mismatch is check-only. The DOM will not be rectified ` +
`in production due to performance overhead.` + `in production due to performance overhead.` +
`\n You should fix the source of the mismatch.`, `\n You should fix the source of the mismatch.`
) if (__TEST__) {
// during tests, log the full message in one single string for easier
// debugging.
warn(`${preSegment} ${el.tagName}${postSegment}`)
} else {
warn(preSegment, el, postSegment)
}
return true return true
} }
return false return false
@ -783,3 +818,28 @@ function isSetEqual(a: Set<string>, b: Set<string>): boolean {
} }
return true return true
} }
function toStyleMap(str: string): Map<string, string> {
const styleMap: Map<string, string> = new Map()
for (const item of str.split(';')) {
let [key, value] = item.split(':')
key = key?.trim()
value = value?.trim()
if (key && value) {
styleMap.set(key, value)
}
}
return styleMap
}
function isMapEqual(a: Map<string, string>, b: Map<string, string>): boolean {
if (a.size !== b.size) {
return false
}
for (const [key, value] of a) {
if (value !== b.get(key)) {
return false
}
}
return true
}

View File

@ -62,6 +62,7 @@ export { nextTick } from './scheduler'
export { defineComponent } from './apiDefineComponent' export { defineComponent } from './apiDefineComponent'
export { defineAsyncComponent } from './apiAsyncComponent' export { defineAsyncComponent } from './apiAsyncComponent'
export { useAttrs, useSlots } from './apiSetupHelpers' export { useAttrs, useSlots } from './apiSetupHelpers'
export { useModel } from './helpers/useModel'
// <script setup> API ---------------------------------------------------------- // <script setup> API ----------------------------------------------------------
@ -74,7 +75,6 @@ export {
defineSlots, defineSlots,
defineModel, defineModel,
withDefaults, withDefaults,
useModel,
type DefineProps, type DefineProps,
type ModelRef, type ModelRef,
} from './apiSetupHelpers' } from './apiSetupHelpers'
@ -151,7 +151,7 @@ import { ErrorTypeStrings as _ErrorTypeStrings } from './errorHandling'
* @internal * @internal
*/ */
export const ErrorTypeStrings = ( export const ErrorTypeStrings = (
__ESM_BUNDLER__ || __NODE_JS__ || __DEV__ ? _ErrorTypeStrings : null __ESM_BUNDLER__ || __CJS__ || __DEV__ ? _ErrorTypeStrings : null
) as typeof _ErrorTypeStrings ) as typeof _ErrorTypeStrings
// For devtools // For devtools
@ -162,13 +162,13 @@ import {
} from './devtools' } from './devtools'
export const devtools = ( export const devtools = (
__DEV__ || __FEATURE_PROD_DEVTOOLS__ ? _devtools : undefined __DEV__ || __ESM_BUNDLER__ ? _devtools : undefined
) as DevtoolsHook ) as DevtoolsHook
export const setDevtoolsHook = ( export const setDevtoolsHook = (
__DEV__ || __FEATURE_PROD_DEVTOOLS__ ? _setDevtoolsHook : NOOP __DEV__ || __ESM_BUNDLER__ ? _setDevtoolsHook : NOOP
) as typeof _setDevtoolsHook ) as typeof _setDevtoolsHook
// Types ------------------------------------------------------------------------- // Types -----------------------------------------------------------------------
import type { VNode } from './vnode' import type { VNode } from './vnode'
import type { ComponentInternalInstance } from './component' import type { ComponentInternalInstance } from './component'

View File

@ -1099,7 +1099,11 @@ function baseCreateRenderer(
// since they are either generated by the compiler, or implicitly created // since they are either generated by the compiler, or implicitly created
// from arrays. // from arrays.
mountChildren( mountChildren(
n2.children as VNodeArrayChildren, // #10007
// such fragment like `<></>` will be compiled into
// a fragment which doesn't have a children.
// In this case fallback to an empty array
(n2.children || []) as VNodeArrayChildren,
container, container,
fragmentEndAnchor, fragmentEndAnchor,
parentComponent, parentComponent,
@ -2361,6 +2365,7 @@ function baseCreateRenderer(
return hostNextSibling((vnode.anchor || vnode.el)!) return hostNextSibling((vnode.anchor || vnode.el)!)
} }
let isFlushing = false
const render: RootRenderFunction = (vnode, container, namespace) => { const render: RootRenderFunction = (vnode, container, namespace) => {
if (vnode == null) { if (vnode == null) {
if (container._vnode) { if (container._vnode) {
@ -2377,8 +2382,12 @@ function baseCreateRenderer(
namespace, namespace,
) )
} }
if (!isFlushing) {
isFlushing = true
flushPreFlushCbs() flushPreFlushCbs()
flushPostFlushCbs() flushPostFlushCbs()
isFlushing = false
}
container._vnode = vnode container._vnode = vnode
} }

View File

@ -165,7 +165,9 @@ export function flushPreFlushCbs(
export function flushPostFlushCbs(seen?: CountMap) { export function flushPostFlushCbs(seen?: CountMap) {
if (pendingPostFlushCbs.length) { if (pendingPostFlushCbs.length) {
const deduped = [...new Set(pendingPostFlushCbs)] const deduped = [...new Set(pendingPostFlushCbs)].sort(
(a, b) => getId(a) - getId(b),
)
pendingPostFlushCbs.length = 0 pendingPostFlushCbs.length = 0
// #1947 already has active queue, nested flushPostFlushCbs call // #1947 already has active queue, nested flushPostFlushCbs call
@ -179,8 +181,6 @@ export function flushPostFlushCbs(seen?: CountMap) {
seen = seen || new Map() seen = seen || new Map()
} }
activePostFlushCbs.sort((a, b) => getId(a) - getId(b))
for ( for (
postFlushIndex = 0; postFlushIndex = 0;
postFlushIndex < activePostFlushCbs.length; postFlushIndex < activePostFlushCbs.length;
@ -243,7 +243,6 @@ function flushJobs(seen?: CountMap) {
if (__DEV__ && check(job)) { if (__DEV__ && check(job)) {
continue continue
} }
// console.log(`running:`, job.id)
callWithErrorHandling(job, null, ErrorCodes.SCHEDULER) callWithErrorHandling(job, null, ErrorCodes.SCHEDULER)
} }
} }

View File

@ -30,7 +30,7 @@ describe('runtime-dom: v-show directive', () => {
}) })
render(h(component), root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.children[0]
expect($div.style.display).toEqual('') expect($div.style.display).toEqual('')
}) })
@ -46,7 +46,7 @@ describe('runtime-dom: v-show directive', () => {
}) })
render(h(component), root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.children[0]
expect($div.style.display).toEqual('none') expect($div.style.display).toEqual('none')
}) })
@ -62,7 +62,7 @@ describe('runtime-dom: v-show directive', () => {
}) })
render(h(component), root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.children[0]
const data = root._vnode.component.data const data = root._vnode.component.data
expect($div.style.display).toEqual('') expect($div.style.display).toEqual('')
@ -113,7 +113,7 @@ describe('runtime-dom: v-show directive', () => {
}) })
render(h(component), root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.children[0]
const data = root._vnode.component.data const data = root._vnode.component.data
expect($div.style.display).toEqual('block') expect($div.style.display).toEqual('block')
@ -138,7 +138,7 @@ describe('runtime-dom: v-show directive', () => {
}) })
render(h(component), root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.children[0]
expect($div.style.display).toEqual('none') expect($div.style.display).toEqual('none')
@ -151,6 +151,32 @@ describe('runtime-dom: v-show directive', () => {
expect($div.style.display).toEqual('') expect($div.style.display).toEqual('')
}) })
test('the value of `display` set by v-show should not be overwritten by the style attribute when updated (object value)', async () => {
const style = ref({
display: 'block',
width: '100px',
})
const display = ref(false)
const component = defineComponent({
render() {
return withVShow(h('div', { style: style.value }), display.value)
},
})
render(h(component), root)
const $div = root.children[0]
expect($div.style.display).toEqual('none')
style.value.width = '50px'
await nextTick()
expect($div.style.display).toEqual('none')
display.value = true
await nextTick()
expect($div.style.display).toEqual('block')
})
// #2583, #2757 // #2583, #2757
test('the value of `display` set by v-show should not be overwritten by the style attribute when updated (with Transition)', async () => { test('the value of `display` set by v-show should not be overwritten by the style attribute when updated (with Transition)', async () => {
const style = ref('width: 100px') const style = ref('width: 100px')
@ -173,7 +199,7 @@ describe('runtime-dom: v-show directive', () => {
}) })
render(h(component), root) render(h(component), root)
const $div = root.querySelector('div') const $div = root.children[0]
expect($div.style.display).toEqual('none') expect($div.style.display).toEqual('none')

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/runtime-dom", "name": "@vue/runtime-dom",
"version": "3.4.5", "version": "3.4.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",
@ -18,6 +18,7 @@
"development": "./dist/runtime-dom.cjs.js", "development": "./dist/runtime-dom.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/runtime-dom.esm-bundler.js",
"import": "./dist/runtime-dom.esm-bundler.js", "import": "./dist/runtime-dom.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -7,7 +7,7 @@ interface VShowElement extends HTMLElement {
[vShowOldKey]: string [vShowOldKey]: string
} }
export const vShow: ObjectDirective<VShowElement> = { export const vShow: ObjectDirective<VShowElement> & { name?: 'show' } = {
beforeMount(el, { value }, { transition }) { beforeMount(el, { value }, { transition }) {
el[vShowOldKey] = el.style.display === 'none' ? '' : el.style.display el[vShowOldKey] = el.style.display === 'none' ? '' : el.style.display
if (transition && value) { if (transition && value) {
@ -42,6 +42,10 @@ export const vShow: ObjectDirective<VShowElement> = {
}, },
} }
if (__DEV__) {
vShow.name = 'show'
}
function setDisplay(el: VShowElement, value: unknown): void { function setDisplay(el: VShowElement, value: unknown): void {
el.style.display = value ? el[vShowOldKey] : 'none' el.style.display = value ? el[vShowOldKey] : 'none'
} }

View File

@ -739,7 +739,7 @@ export interface TextareaHTMLAttributes extends HTMLAttributes {
readonly?: Booleanish readonly?: Booleanish
required?: Booleanish required?: Booleanish
rows?: Numberish rows?: Numberish
value?: string | ReadonlyArray<string> | number value?: string | ReadonlyArray<string> | number | null
wrap?: string wrap?: string
} }

View File

@ -7,6 +7,7 @@ type Style = string | Record<string, string | string[]> | null
export function patchStyle(el: Element, prev: Style, next: Style) { export function patchStyle(el: Element, prev: Style, next: Style) {
const style = (el as HTMLElement).style const style = (el as HTMLElement).style
const currentDisplay = style.display
const isCssString = isString(next) const isCssString = isString(next)
if (next && !isCssString) { if (next && !isCssString) {
if (prev && !isString(prev)) { if (prev && !isString(prev)) {
@ -20,7 +21,6 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
setStyle(style, key, next[key]) setStyle(style, key, next[key])
} }
} else { } else {
const currentDisplay = style.display
if (isCssString) { if (isCssString) {
if (prev !== next) { if (prev !== next) {
// #9821 // #9821
@ -33,13 +33,13 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
} else if (prev) { } else if (prev) {
el.removeAttribute('style') el.removeAttribute('style')
} }
}
// indicates that the `display` of the element is controlled by `v-show`, // indicates that the `display` of the element is controlled by `v-show`,
// so we always keep the current `display` value regardless of the `style` // so we always keep the current `display` value regardless of the `style`
// value, thus handing over control to `v-show`. // value, thus handing over control to `v-show`.
if (vShowOldKey in el) { if (vShowOldKey in el) {
style.display = currentDisplay style.display = currentDisplay
} }
}
} }
const semicolonRE = /[^\\];\s*$/ const semicolonRE = /[^\\];\s*$/

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/server-renderer", "name": "@vue/server-renderer",
"version": "3.4.5", "version": "3.4.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",
@ -17,6 +17,7 @@
"development": "./dist/server-renderer.cjs.js", "development": "./dist/server-renderer.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/server-renderer.esm-bundler.js",
"import": "./dist/server-renderer.esm-bundler.js", "import": "./dist/server-renderer.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -24,7 +24,7 @@ export function ssrCompile(
instance: ComponentInternalInstance, instance: ComponentInternalInstance,
): SSRRenderFunction { ): SSRRenderFunction {
// TODO: this branch should now work in ESM builds, enable it in a minor // TODO: this branch should now work in ESM builds, enable it in a minor
if (!__NODE_JS__) { if (!__CJS__) {
throw new Error( throw new Error(
`On-the-fly template compilation is not supported in the ESM build of ` + `On-the-fly template compilation is not supported in the ESM build of ` +
`@vue/server-renderer. All templates must be pre-compiled into ` + `@vue/server-renderer. All templates must be pre-compiled into ` +

View File

@ -107,7 +107,7 @@ export function renderComponentVNode(
), ),
) )
// Note: error display is already done by the wrapped lifecycle hook function. // Note: error display is already done by the wrapped lifecycle hook function.
.catch(() => {}) .catch(NOOP)
} }
return p.then(() => renderComponentSubTree(instance, slotScopeId)) return p.then(() => renderComponentSubTree(instance, slotScopeId))
} else { } else {

View File

@ -108,7 +108,7 @@ export function renderToNodeStream(
input: App | VNode, input: App | VNode,
context: SSRContext = {}, context: SSRContext = {},
): Readable { ): Readable {
const stream: Readable = __NODE_JS__ const stream: Readable = __CJS__
? new (require('node:stream').Readable)({ read() {} }) ? new (require('node:stream').Readable)({ read() {} })
: null : null

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/shared", "name": "@vue/shared",
"version": "3.4.5", "version": "3.4.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",
@ -17,6 +17,7 @@
"development": "./dist/shared.cjs.js", "development": "./dist/shared.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/shared.esm-bundler.js",
"import": "./dist/shared.esm-bundler.js", "import": "./dist/shared.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compat", "name": "@vue/compat",
"version": "3.4.5", "version": "3.4.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",
@ -18,6 +18,7 @@
"development": "./dist/vue.cjs.js", "development": "./dist/vue.cjs.js",
"default": "./index.js" "default": "./index.js"
}, },
"module": "./dist/vue.esm-bundler.js",
"import": "./dist/vue.esm-bundler.js", "import": "./dist/vue.esm-bundler.js",
"require": "./index.js" "require": "./index.js"
}, },

View File

@ -1652,6 +1652,77 @@ describe('e2e: Transition', () => {
}, },
E2E_TIMEOUT, E2E_TIMEOUT,
) )
// #9996
test(
'trigger again when transition is not finished & correctly anchor',
async () => {
await page().evaluate(duration => {
const { createApp, shallowRef, h } = (window as any).Vue
const One = {
async setup() {
return () => h('div', { class: 'test' }, 'one')
},
}
const Two = {
async setup() {
return () => h('div', { class: 'test' }, 'two')
},
}
createApp({
template: `
<div id="container">
<div>Top</div>
<transition name="test" mode="out-in" :duration="${duration}">
<Suspense>
<component :is="view"/>
</Suspense>
</transition>
<div>Bottom</div>
</div>
<button id="toggleBtn" @click="click">button</button>
`,
setup: () => {
const view = shallowRef(One)
const click = () => {
view.value = view.value === One ? Two : One
}
return { view, click }
},
}).mount('#app')
}, duration)
await nextFrame()
expect(await html('#container')).toBe(
'<div>Top</div><div class="test test-enter-active test-enter-to">one</div><div>Bottom</div>',
)
await transitionFinish()
expect(await html('#container')).toBe(
'<div>Top</div><div class="test">one</div><div>Bottom</div>',
)
// trigger twice
classWhenTransitionStart()
await nextFrame()
expect(await html('#container')).toBe(
'<div>Top</div><div class="test test-leave-active test-leave-to">one</div><div>Bottom</div>',
)
await transitionFinish()
await nextFrame()
expect(await html('#container')).toBe(
'<div>Top</div><div class="test test-enter-active test-enter-to">two</div><div>Bottom</div>',
)
await transitionFinish()
await nextFrame()
expect(await html('#container')).toBe(
'<div>Top</div><div class="test">two</div><div>Bottom</div>',
)
},
E2E_TIMEOUT,
)
}) })
describe('transition with v-show', () => { describe('transition with v-show', () => {

View File

@ -1,6 +1,6 @@
{ {
"name": "vue", "name": "vue",
"version": "3.4.5", "version": "3.4.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",
@ -20,15 +20,16 @@
".": { ".": {
"import": { "import": {
"types": "./dist/vue.d.mts", "types": "./dist/vue.d.mts",
"node": { "node": "./index.mjs",
"production": "./dist/vue.cjs.prod.js",
"development": "./dist/vue.cjs.js",
"default": "./index.mjs"
},
"default": "./dist/vue.runtime.esm-bundler.js" "default": "./dist/vue.runtime.esm-bundler.js"
}, },
"require": { "require": {
"types": "./dist/vue.d.ts", "types": "./dist/vue.d.ts",
"node": {
"production": "./dist/vue.cjs.prod.js",
"development": "./dist/vue.cjs.js",
"default": "./index.js"
},
"default": "./index.js" "default": "./index.js"
} }
}, },

View File

@ -16,7 +16,7 @@ importers:
version: 7.23.6 version: 7.23.6
'@codspeed/vitest-plugin': '@codspeed/vitest-plugin':
specifier: ^2.3.1 specifier: ^2.3.1
version: 2.3.1(vite@5.0.10)(vitest@1.1.1) version: 2.3.1(vite@5.0.10)(vitest@1.2.0)
'@rollup/plugin-alias': '@rollup/plugin-alias':
specifier: ^5.0.1 specifier: ^5.0.1
version: 5.0.1(rollup@4.4.1) version: 5.0.1(rollup@4.4.1)
@ -42,8 +42,8 @@ importers:
specifier: ^1.2.5 specifier: ^1.2.5
version: 1.2.5 version: 1.2.5
'@types/node': '@types/node':
specifier: ^20.10.6 specifier: ^20.10.7
version: 20.10.6 version: 20.11.0
'@types/semver': '@types/semver':
specifier: ^7.5.6 specifier: ^7.5.6
version: 7.5.6 version: 7.5.6
@ -54,8 +54,8 @@ importers:
specifier: ^6.17.0 specifier: ^6.17.0
version: 6.17.0(eslint@8.56.0)(typescript@5.2.2) version: 6.17.0(eslint@8.56.0)(typescript@5.2.2)
'@vitest/coverage-istanbul': '@vitest/coverage-istanbul':
specifier: ^1.1.1 specifier: ^1.1.3
version: 1.1.1(vitest@1.1.1) version: 1.2.0(vitest@1.2.0)
'@vue/consolidate': '@vue/consolidate':
specifier: 0.17.3 specifier: 0.17.3
version: 0.17.3 version: 0.17.3
@ -90,8 +90,8 @@ importers:
specifier: ^8.0.1 specifier: ^8.0.1
version: 8.0.1 version: 8.0.1
jsdom: jsdom:
specifier: ^23.0.1 specifier: ^23.2.0
version: 23.0.1 version: 23.2.0
lint-staged: lint-staged:
specifier: ^15.2.0 specifier: ^15.2.0
version: 15.2.0 version: 15.2.0
@ -126,8 +126,8 @@ importers:
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2 version: 3.0.2
puppeteer: puppeteer:
specifier: ~21.6.1 specifier: ~21.7.0
version: 21.6.1(typescript@5.2.2) version: 21.7.0(typescript@5.2.2)
rimraf: rimraf:
specifier: ^5.0.5 specifier: ^5.0.5
version: 5.0.5 version: 5.0.5
@ -169,10 +169,10 @@ importers:
version: 5.2.2 version: 5.2.2
vite: vite:
specifier: ^5.0.5 specifier: ^5.0.5
version: 5.0.10(@types/node@20.10.6)(terser@5.22.0) version: 5.0.10(@types/node@20.11.0)(terser@5.22.0)
vitest: vitest:
specifier: ^1.1.1 specifier: ^1.1.3
version: 1.1.1(@types/node@20.10.6)(jsdom@23.0.1)(terser@5.22.0) version: 1.2.0(@types/node@20.11.0)(jsdom@23.2.0)(terser@5.22.0)
packages/compiler-core: packages/compiler-core:
dependencies: dependencies:
@ -369,7 +369,7 @@ importers:
dependencies: dependencies:
'@vue/repl': '@vue/repl':
specifier: ^3.1.1 specifier: ^3.1.1
version: 3.2.0 version: 3.3.0
file-saver: file-saver:
specifier: ^2.0.5 specifier: ^2.0.5
version: 2.0.5 version: 2.0.5
@ -385,7 +385,7 @@ importers:
version: 4.4.0(vite@5.0.10)(vue@packages+vue) version: 4.4.0(vite@5.0.10)(vue@packages+vue)
vite: vite:
specifier: ^5.0.5 specifier: ^5.0.5
version: 5.0.10(@types/node@20.10.6)(terser@5.22.0) version: 5.0.10(@types/node@20.11.0)(terser@5.22.0)
packages/shared: {} packages/shared: {}
@ -460,10 +460,10 @@ importers:
version: 4.5.0(vite@5.0.2)(vue@packages+vue) version: 4.5.0(vite@5.0.2)(vue@packages+vue)
vite: vite:
specifier: ^5.0.2 specifier: ^5.0.2
version: 5.0.2(@types/node@20.10.6)(terser@5.22.0) version: 5.0.2(@types/node@20.11.0)(terser@5.22.0)
vite-hyper-config: vite-hyper-config:
specifier: ^0.2.1 specifier: ^0.2.1
version: 0.2.1(@types/node@20.10.6)(terser@5.22.0)(vite@5.0.2) version: 0.2.1(@types/node@20.11.0)(terser@5.22.0)(vite@5.0.2)
vite-plugin-inspect: vite-plugin-inspect:
specifier: ^0.7.42 specifier: ^0.7.42
version: 0.7.42(rollup@4.4.1)(vite@5.0.2) version: 0.7.42(rollup@4.4.1)(vite@5.0.2)
@ -487,6 +487,14 @@ packages:
resolution: {integrity: sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==} resolution: {integrity: sha512-pvFiLP2BeOKA/ZOS6jxx4XhKzdVLHDhGlFEaZ2flWWYf2xOqVniqpk38I04DFRyz+L0ASggl7SkItTc+ZLju4w==}
dev: true dev: true
/@asamuzakjp/dom-selector@2.0.2:
resolution: {integrity: sha512-x1KXOatwofR6ZAYzXRBL5wrdV0vwNxlTCK9NCuLqAzQYARqGcvFwiJA6A1ERuh+dgeA4Dxm3JBYictIes+SqUQ==}
dependencies:
bidi-js: 1.0.3
css-tree: 2.3.1
is-potential-custom-element-name: 1.0.1
dev: true
/@babel/code-frame@7.23.5: /@babel/code-frame@7.23.5:
resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -684,15 +692,15 @@ packages:
node-gyp-build: 4.7.1 node-gyp-build: 4.7.1
dev: true dev: true
/@codspeed/vitest-plugin@2.3.1(vite@5.0.10)(vitest@1.1.1): /@codspeed/vitest-plugin@2.3.1(vite@5.0.10)(vitest@1.2.0):
resolution: {integrity: sha512-/e4G2B/onX/hG/EjUU/NpDxnIryeTDamVRTBeWfgQDoex3g7GDzTwoQktaU5l/Asw3ZjEErQg+oQVToQ6jYZlA==} resolution: {integrity: sha512-/e4G2B/onX/hG/EjUU/NpDxnIryeTDamVRTBeWfgQDoex3g7GDzTwoQktaU5l/Asw3ZjEErQg+oQVToQ6jYZlA==}
peerDependencies: peerDependencies:
vite: ^4.2.0 || ^5.0.0 vite: ^4.2.0 || ^5.0.0
vitest: '>=1.0.0-beta.4 || >=1' vitest: '>=1.0.0-beta.4 || >=1'
dependencies: dependencies:
'@codspeed/core': 2.3.1 '@codspeed/core': 2.3.1
vite: 5.0.10(@types/node@20.10.6)(terser@5.22.0) vite: 5.0.10(@types/node@20.11.0)(terser@5.22.0)
vitest: 1.1.1(@types/node@20.10.6)(jsdom@23.0.1)(terser@5.22.0) vitest: 1.2.0(@types/node@20.11.0)(jsdom@23.2.0)(terser@5.22.0)
dev: true dev: true
/@esbuild/aix-ppc64@0.19.10: /@esbuild/aix-ppc64@0.19.10:
@ -1060,8 +1068,8 @@ packages:
resolution: {integrity: sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==} resolution: {integrity: sha512-C16M+IYz0rgRhWZdCmK+h58JMv8vijAA61gmz2rspCSwKwzBebpdcsiUmwrtJRdphuY30i6BSLEOP8ppbNLyLg==}
dev: true dev: true
/@puppeteer/browsers@1.9.0: /@puppeteer/browsers@1.9.1:
resolution: {integrity: sha512-QwguOLy44YBGC8vuPP2nmpX4MUN2FzWbsnvZJtiCzecU3lHmVZkaC1tq6rToi9a200m8RzlVtDyxCS0UIDrxUg==} resolution: {integrity: sha512-PuvK6xZzGhKPvlx3fpfdM2kYY3P/hB1URtK8wA7XUJ6prn6pp22zvJHu48th0SGcHL9SutbPHrFuQgfXTFobWA==}
engines: {node: '>=16.3.0'} engines: {node: '>=16.3.0'}
hasBin: true hasBin: true
dependencies: dependencies:
@ -1317,8 +1325,8 @@ packages:
resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==}
dev: true dev: true
/@types/node@20.10.6: /@types/node@20.11.0:
resolution: {integrity: sha512-Vac8H+NlRNNlAmDfGUP7b5h/KA+AtWIzuXy0E6OyP8f1tCLYAtPvKRRDJjAPqhpCb0t6U2j7/xqAuLEebW2kiw==} resolution: {integrity: sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==}
dependencies: dependencies:
undici-types: 5.26.5 undici-types: 5.26.5
dev: true dev: true
@ -1339,7 +1347,7 @@ packages:
resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==} resolution: {integrity: sha512-Km7XAtUIduROw7QPgvcft0lIupeG8a8rdKL8RiSyKvlE7dYY31fEn41HVuQsRFDuROA8tA4K2UVL+WdfFmErBA==}
requiresBuild: true requiresBuild: true
dependencies: dependencies:
'@types/node': 20.10.6 '@types/node': 20.11.0
dev: true dev: true
optional: true optional: true
@ -1548,7 +1556,7 @@ packages:
vite: ^4.0.0 vite: ^4.0.0
vue: ^3.2.25 vue: ^3.2.25
dependencies: dependencies:
vite: 5.0.10(@types/node@20.10.6)(terser@5.22.0) vite: 5.0.10(@types/node@20.11.0)(terser@5.22.0)
vue: link:packages/vue vue: link:packages/vue
dev: true dev: true
@ -1559,12 +1567,12 @@ packages:
vite: ^4.0.0 || ^5.0.0 vite: ^4.0.0 || ^5.0.0
vue: ^3.2.25 vue: ^3.2.25
dependencies: dependencies:
vite: 5.0.2(@types/node@20.10.6)(terser@5.22.0) vite: 5.0.2(@types/node@20.11.0)(terser@5.22.0)
vue: link:packages/vue vue: link:packages/vue
dev: true dev: true
/@vitest/coverage-istanbul@1.1.1(vitest@1.1.1): /@vitest/coverage-istanbul@1.2.0(vitest@1.2.0):
resolution: {integrity: sha512-Ikq6k2/KJ3MqEnGJCZBctcgxW1JRPzyVetVz1AYqLxrHNiG/epGFPZ74kIc3AK0HGaf0RsqZkc8riCTmxfH/dQ==} resolution: {integrity: sha512-hNN/pUR5la6P/L78+YcRl05Lpf6APXlH9ujkmCxxjVWtVG6WuKuqUMhHgYQBYfiOORBwDZ1MBgSUGCMPh4kpmQ==}
peerDependencies: peerDependencies:
vitest: ^1.0.0 vitest: ^1.0.0
dependencies: dependencies:
@ -1577,45 +1585,46 @@ packages:
magicast: 0.3.2 magicast: 0.3.2
picocolors: 1.0.0 picocolors: 1.0.0
test-exclude: 6.0.0 test-exclude: 6.0.0
vitest: 1.1.1(@types/node@20.10.6)(jsdom@23.0.1)(terser@5.22.0) vitest: 1.2.0(@types/node@20.11.0)(jsdom@23.2.0)(terser@5.22.0)
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
dev: true dev: true
/@vitest/expect@1.1.1: /@vitest/expect@1.2.0:
resolution: {integrity: sha512-Qpw01C2Hyb3085jBkOJLQ7HRX0Ncnh2qV4p+xWmmhcIUlMykUF69zsnZ1vPmAjZpomw9+5tWEGOQ0GTfR8U+kA==} resolution: {integrity: sha512-H+2bHzhyvgp32o7Pgj2h9RTHN0pgYaoi26Oo3mE+dCi1PAqV31kIIVfTbqMO3Bvshd5mIrJLc73EwSRrbol9Lw==}
dependencies: dependencies:
'@vitest/spy': 1.1.1 '@vitest/spy': 1.2.0
'@vitest/utils': 1.1.1 '@vitest/utils': 1.2.0
chai: 4.3.10 chai: 4.3.10
dev: true dev: true
/@vitest/runner@1.1.1: /@vitest/runner@1.2.0:
resolution: {integrity: sha512-8HokyJo1SnSi3uPFKfWm/Oq1qDwLC4QDcVsqpXIXwsRPAg3gIDh8EbZ1ri8cmQkBxdOu62aOF9B4xcqJhvt4xQ==} resolution: {integrity: sha512-vaJkDoQaNUTroT70OhM0NPznP7H3WyRwt4LvGwCVYs/llLaqhoSLnlIhUClZpbF5RgAee29KRcNz0FEhYcgxqA==}
dependencies: dependencies:
'@vitest/utils': 1.1.1 '@vitest/utils': 1.2.0
p-limit: 5.0.0 p-limit: 5.0.0
pathe: 1.1.1 pathe: 1.1.1
dev: true dev: true
/@vitest/snapshot@1.1.1: /@vitest/snapshot@1.2.0:
resolution: {integrity: sha512-WnMHjv4VdHLbFGgCdVVvyRkRPnOKN75JJg+LLTdr6ah7YnL75W+7CTIMdzPEPzaDxA8r5yvSVlc1d8lH3yE28w==} resolution: {integrity: sha512-P33EE7TrVgB3HDLllrjK/GG6WSnmUtWohbwcQqmm7TAk9AVHpdgf7M3F3qRHKm6vhr7x3eGIln7VH052Smo6Kw==}
dependencies: dependencies:
magic-string: 0.30.5 magic-string: 0.30.5
pathe: 1.1.1 pathe: 1.1.1
pretty-format: 29.7.0 pretty-format: 29.7.0
dev: true dev: true
/@vitest/spy@1.1.1: /@vitest/spy@1.2.0:
resolution: {integrity: sha512-hDU2KkOTfFp4WFFPWwHFauddwcKuGQ7gF6Un/ZZkCogoAiTMN7/7YKvUDbywPZZ754iCQGjdUmXN3t4k0jm1IQ==} resolution: {integrity: sha512-MNxSAfxUaCeowqyyGwC293yZgk7cECZU9wGb8N1pYQ0yOn/SIr8t0l9XnGRdQZvNV/ZHBYu6GO/W3tj5K3VN1Q==}
dependencies: dependencies:
tinyspy: 2.2.0 tinyspy: 2.2.0
dev: true dev: true
/@vitest/utils@1.1.1: /@vitest/utils@1.2.0:
resolution: {integrity: sha512-E9LedH093vST/JuBSyHLFMpxJKW3dLhe/flUSPFedoyj4wKiFX7Jm8gYLtOIiin59dgrssfmFv0BJ1u8P/LC/A==} resolution: {integrity: sha512-FyD5bpugsXlwVpTcGLDf3wSPYy8g541fQt14qtzo8mJ4LdEpDKZ9mQy2+qdJm2TZRpjY5JLXihXCgIxiRJgi5g==}
dependencies: dependencies:
diff-sequences: 29.6.3 diff-sequences: 29.6.3
estree-walker: 3.0.3
loupe: 2.3.7 loupe: 2.3.7
pretty-format: 29.7.0 pretty-format: 29.7.0
dev: true dev: true
@ -1625,8 +1634,8 @@ packages:
engines: {node: '>= 0.12.0'} engines: {node: '>= 0.12.0'}
dev: true dev: true
/@vue/repl@3.2.0: /@vue/repl@3.3.0:
resolution: {integrity: sha512-heKXXwAm4p3wYqVsYqW5i9bAkiGqwbZygfBx8stZ48QqTvaTGiGY3qASJwvhc5FrZDPGoGcFRjab6XDImKxPvA==} resolution: {integrity: sha512-A9tdO7obt/kpFUHdgGoRnan6bZjfz/WAJ5+DpPkvgNEc960W+bJraURv8MUVtH2Id/byWotKbUve2jTakiccSw==}
dev: false dev: false
/@zeit/schemas@2.29.0: /@zeit/schemas@2.29.0:
@ -1858,6 +1867,12 @@ packages:
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
dev: true dev: true
/bidi-js@1.0.3:
resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==}
dependencies:
require-from-string: 2.0.2
dev: true
/big-integer@1.6.51: /big-integer@1.6.51:
resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==} resolution: {integrity: sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==}
engines: {node: '>=0.6'} engines: {node: '>=0.6'}
@ -2053,8 +2068,8 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/chromium-bidi@0.5.1(devtools-protocol@0.0.1203626): /chromium-bidi@0.5.2(devtools-protocol@0.0.1203626):
resolution: {integrity: sha512-dcCqOgq9fHKExc2R4JZs/oKbOghWpUNFAJODS8WKRtLhp3avtIH5UDCBrutdqZdh3pARogH8y1ObXm87emwb3g==} resolution: {integrity: sha512-PbVOSddxgKyj+JByqavWMNqWPCoCaT6XK5Z1EFe168sxnB/BM51LnZEPXSbFcFAJv/+u2B4XNTs9uXxy4GW3cQ==}
peerDependencies: peerDependencies:
devtools-protocol: '*' devtools-protocol: '*'
dependencies: dependencies:
@ -2372,15 +2387,23 @@ packages:
which: 2.0.2 which: 2.0.2
dev: true dev: true
/css-tree@2.3.1:
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
dependencies:
mdn-data: 2.0.30
source-map-js: 1.0.2
dev: true
/cssesc@3.0.0: /cssesc@3.0.0:
resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
engines: {node: '>=4'} engines: {node: '>=4'}
hasBin: true hasBin: true
dev: true dev: true
/cssstyle@3.0.0: /cssstyle@4.0.1:
resolution: {integrity: sha512-N4u2ABATi3Qplzf0hWbVCdjenim8F3ojEXpBDF5hBpjzW182MjNGLqfmQ0SkSPeQ+V86ZXgeH8aXj6kayd4jgg==} resolution: {integrity: sha512-8ZYiJ3A/3OkDd093CBT/0UKDWry7ak4BdPTFP2+QEP7cmhouyq/Up709ASSj2cK02BbZiMgk7kYjZNS4QP5qrQ==}
engines: {node: '>=14'} engines: {node: '>=18'}
dependencies: dependencies:
rrweb-cssom: 0.6.0 rrweb-cssom: 0.6.0
dev: true dev: true
@ -2935,6 +2958,12 @@ packages:
/estree-walker@2.0.2: /estree-walker@2.0.2:
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
/estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
dependencies:
'@types/estree': 1.0.3
dev: true
/esutils@2.0.3: /esutils@2.0.3:
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -3877,8 +3906,8 @@ packages:
argparse: 2.0.1 argparse: 2.0.1
dev: true dev: true
/jsdom@23.0.1: /jsdom@23.2.0:
resolution: {integrity: sha512-2i27vgvlUsGEBO9+/kJQRbtqtm+191b5zAZrU/UezVmnC2dlDAFLgDYJvAEi94T4kjsRKkezEtLQTgsNEsW2lQ==} resolution: {integrity: sha512-L88oL7D/8ufIES+Zjz7v0aes+oBMh2Xnh3ygWvL0OaICOomKEPKuPnIfBJekiXr+BHbbMjrWn/xqrDQuxFTeyA==}
engines: {node: '>=18'} engines: {node: '>=18'}
peerDependencies: peerDependencies:
canvas: ^2.11.2 canvas: ^2.11.2
@ -3886,7 +3915,8 @@ packages:
canvas: canvas:
optional: true optional: true
dependencies: dependencies:
cssstyle: 3.0.0 '@asamuzakjp/dom-selector': 2.0.2
cssstyle: 4.0.1
data-urls: 5.0.0 data-urls: 5.0.0
decimal.js: 10.4.3 decimal.js: 10.4.3
form-data: 4.0.0 form-data: 4.0.0
@ -3894,7 +3924,6 @@ packages:
http-proxy-agent: 7.0.0 http-proxy-agent: 7.0.0
https-proxy-agent: 7.0.2 https-proxy-agent: 7.0.2
is-potential-custom-element-name: 1.0.1 is-potential-custom-element-name: 1.0.1
nwsapi: 2.2.7
parse5: 7.1.2 parse5: 7.1.2
rrweb-cssom: 0.6.0 rrweb-cssom: 0.6.0
saxes: 6.0.0 saxes: 6.0.0
@ -3905,7 +3934,7 @@ packages:
whatwg-encoding: 3.1.1 whatwg-encoding: 3.1.1
whatwg-mimetype: 4.0.0 whatwg-mimetype: 4.0.0
whatwg-url: 14.0.0 whatwg-url: 14.0.0
ws: 8.15.1 ws: 8.16.0
xml-name-validator: 5.0.0 xml-name-validator: 5.0.0
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
@ -4182,6 +4211,10 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
dev: true
/memorystream@0.3.1: /memorystream@0.3.1:
resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==}
engines: {node: '>= 0.10.0'} engines: {node: '>= 0.10.0'}
@ -4414,10 +4447,6 @@ packages:
path-key: 4.0.0 path-key: 4.0.0
dev: true dev: true
/nwsapi@2.2.7:
resolution: {integrity: sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==}
dev: true
/object-assign@4.1.1: /object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -4950,16 +4979,16 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true dev: true
/puppeteer-core@21.6.1: /puppeteer-core@21.7.0:
resolution: {integrity: sha512-0chaaK/RL9S1U3bsyR4fUeUfoj51vNnjWvXgG6DcsyMjwYNpLcAThv187i1rZCo7QhJP0wZN8plQkjNyrq2h+A==} resolution: {integrity: sha512-elPYPozrgiM3phSy7VDUJCVWQ07SPnOm78fpSaaSNFoQx5sur/MqhTSro9Wz8lOEjqCykGC6WRkwxDgmqcy1dQ==}
engines: {node: '>=16.13.2'} engines: {node: '>=16.13.2'}
dependencies: dependencies:
'@puppeteer/browsers': 1.9.0 '@puppeteer/browsers': 1.9.1
chromium-bidi: 0.5.1(devtools-protocol@0.0.1203626) chromium-bidi: 0.5.2(devtools-protocol@0.0.1203626)
cross-fetch: 4.0.0 cross-fetch: 4.0.0
debug: 4.3.4 debug: 4.3.4
devtools-protocol: 0.0.1203626 devtools-protocol: 0.0.1203626
ws: 8.15.1 ws: 8.16.0
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- encoding - encoding
@ -4967,15 +4996,15 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/puppeteer@21.6.1(typescript@5.2.2): /puppeteer@21.7.0(typescript@5.2.2):
resolution: {integrity: sha512-O+pbc61oj8ln6m8EJKncrsQFmytgRyFYERtk190PeLbJn5JKpmmynn2p1PiFrlhCitAQXLJ0MOy7F0TeyCRqBg==} resolution: {integrity: sha512-Yy+UUy0b9siJezbhHO/heYUoZQUwyqDK1yOQgblTt0l97tspvDVFkcW9toBlnSvSfkDmMI3Dx9cZL6R8bDArHA==}
engines: {node: '>=16.13.2'} engines: {node: '>=16.13.2'}
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true
dependencies: dependencies:
'@puppeteer/browsers': 1.9.0 '@puppeteer/browsers': 1.9.1
cosmiconfig: 8.3.6(typescript@5.2.2) cosmiconfig: 8.3.6(typescript@5.2.2)
puppeteer-core: 21.6.1 puppeteer-core: 21.7.0
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- encoding - encoding
@ -6019,7 +6048,7 @@ packages:
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
dev: true dev: true
/vite-hyper-config@0.2.1(@types/node@20.10.6)(terser@5.22.0)(vite@5.0.2): /vite-hyper-config@0.2.1(@types/node@20.11.0)(terser@5.22.0)(vite@5.0.2):
resolution: {integrity: sha512-ItRIpzWp0XMh/Sn1H0GCWnQIUcBjnSaZy/EbOpJcRr9H/KTBHUSTCEOigE9K0KTN01Z0GDi/8WgVT9+RPL932A==} resolution: {integrity: sha512-ItRIpzWp0XMh/Sn1H0GCWnQIUcBjnSaZy/EbOpJcRr9H/KTBHUSTCEOigE9K0KTN01Z0GDi/8WgVT9+RPL932A==}
engines: {node: '>=18.0.0'} engines: {node: '>=18.0.0'}
peerDependencies: peerDependencies:
@ -6027,8 +6056,8 @@ packages:
dependencies: dependencies:
cac: 6.7.14 cac: 6.7.14
picocolors: 1.0.0 picocolors: 1.0.0
vite: 5.0.2(@types/node@20.10.6)(terser@5.22.0) vite: 5.0.2(@types/node@20.11.0)(terser@5.22.0)
vite-node: 1.1.1(@types/node@20.10.6)(terser@5.22.0) vite-node: 1.1.1(@types/node@20.11.0)(terser@5.22.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- less - less
@ -6040,7 +6069,7 @@ packages:
- terser - terser
dev: true dev: true
/vite-node@1.1.1(@types/node@20.10.6)(terser@5.22.0): /vite-node@1.1.1(@types/node@20.11.0)(terser@5.22.0):
resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==} resolution: {integrity: sha512-2bGE5w4jvym5v8llF6Gu1oBrmImoNSs4WmRVcavnG2me6+8UQntTqLiAMFyiAobp+ZXhj5ZFhI7SmLiFr/jrow==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
@ -6049,7 +6078,28 @@ packages:
debug: 4.3.4 debug: 4.3.4
pathe: 1.1.1 pathe: 1.1.1
picocolors: 1.0.0 picocolors: 1.0.0
vite: 5.0.10(@types/node@20.10.6)(terser@5.22.0) vite: 5.0.10(@types/node@20.11.0)(terser@5.22.0)
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- sass
- stylus
- sugarss
- supports-color
- terser
dev: true
/vite-node@1.2.0(@types/node@20.11.0)(terser@5.22.0):
resolution: {integrity: sha512-ETnQTHeAbbOxl7/pyBck9oAPZZZo+kYnFt1uQDD+hPReOc+wCjXw4r4jHriBRuVDB5isHmPXxrfc1yJnfBERqg==}
engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true
dependencies:
cac: 6.7.14
debug: 4.3.4
pathe: 1.1.1
picocolors: 1.0.0
vite: 5.0.10(@types/node@20.11.0)(terser@5.22.0)
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
- less - less
@ -6079,13 +6129,13 @@ packages:
open: 9.1.0 open: 9.1.0
picocolors: 1.0.0 picocolors: 1.0.0
sirv: 2.0.3 sirv: 2.0.3
vite: 5.0.2(@types/node@20.10.6)(terser@5.22.0) vite: 5.0.2(@types/node@20.11.0)(terser@5.22.0)
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
- supports-color - supports-color
dev: true dev: true
/vite@5.0.10(@types/node@20.10.6)(terser@5.22.0): /vite@5.0.10(@types/node@20.11.0)(terser@5.22.0):
resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==} resolution: {integrity: sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
@ -6113,7 +6163,7 @@ packages:
terser: terser:
optional: true optional: true
dependencies: dependencies:
'@types/node': 20.10.6 '@types/node': 20.11.0
esbuild: 0.19.10 esbuild: 0.19.10
postcss: 8.4.32 postcss: 8.4.32
rollup: 4.4.1 rollup: 4.4.1
@ -6122,7 +6172,7 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/vite@5.0.2(@types/node@20.10.6)(terser@5.22.0): /vite@5.0.2(@types/node@20.11.0)(terser@5.22.0):
resolution: {integrity: sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==} resolution: {integrity: sha512-6CCq1CAJCNM1ya2ZZA7+jS2KgnhbzvxakmlIjN24cF/PXhRMzpM/z8QgsVJA/Dm5fWUWnVEsmtBoMhmerPxT0g==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
@ -6150,7 +6200,7 @@ packages:
terser: terser:
optional: true optional: true
dependencies: dependencies:
'@types/node': 20.10.6 '@types/node': 20.11.0
esbuild: 0.19.10 esbuild: 0.19.10
postcss: 8.4.32 postcss: 8.4.32
rollup: 4.4.1 rollup: 4.4.1
@ -6159,8 +6209,8 @@ packages:
fsevents: 2.3.3 fsevents: 2.3.3
dev: true dev: true
/vitest@1.1.1(@types/node@20.10.6)(jsdom@23.0.1)(terser@5.22.0): /vitest@1.2.0(@types/node@20.11.0)(jsdom@23.2.0)(terser@5.22.0):
resolution: {integrity: sha512-Ry2qs4UOu/KjpXVfOCfQkTnwSXYGrqTbBZxw6reIYEFjSy1QUARRg5pxiI5BEXy+kBVntxUYNMlq4Co+2vD3fQ==} resolution: {integrity: sha512-Ixs5m7BjqvLHXcibkzKRQUvD/XLw0E3rvqaCMlrm/0LMsA0309ZqYvTlPzkhh81VlEyVZXFlwWnkhb6/UMtcaQ==}
engines: {node: ^18.0.0 || >=20.0.0} engines: {node: ^18.0.0 || >=20.0.0}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@ -6184,18 +6234,18 @@ packages:
jsdom: jsdom:
optional: true optional: true
dependencies: dependencies:
'@types/node': 20.10.6 '@types/node': 20.11.0
'@vitest/expect': 1.1.1 '@vitest/expect': 1.2.0
'@vitest/runner': 1.1.1 '@vitest/runner': 1.2.0
'@vitest/snapshot': 1.1.1 '@vitest/snapshot': 1.2.0
'@vitest/spy': 1.1.1 '@vitest/spy': 1.2.0
'@vitest/utils': 1.1.1 '@vitest/utils': 1.2.0
acorn-walk: 8.3.1 acorn-walk: 8.3.1
cac: 6.7.14 cac: 6.7.14
chai: 4.3.10 chai: 4.3.10
debug: 4.3.4 debug: 4.3.4
execa: 8.0.1 execa: 8.0.1
jsdom: 23.0.1 jsdom: 23.2.0
local-pkg: 0.5.0 local-pkg: 0.5.0
magic-string: 0.30.5 magic-string: 0.30.5
pathe: 1.1.1 pathe: 1.1.1
@ -6204,8 +6254,8 @@ packages:
strip-literal: 1.3.0 strip-literal: 1.3.0
tinybench: 2.5.1 tinybench: 2.5.1
tinypool: 0.8.1 tinypool: 0.8.1
vite: 5.0.10(@types/node@20.10.6)(terser@5.22.0) vite: 5.0.10(@types/node@20.11.0)(terser@5.22.0)
vite-node: 1.1.1(@types/node@20.10.6)(terser@5.22.0) vite-node: 1.2.0(@types/node@20.11.0)(terser@5.22.0)
why-is-node-running: 2.2.2 why-is-node-running: 2.2.2
transitivePeerDependencies: transitivePeerDependencies:
- less - less
@ -6362,8 +6412,8 @@ packages:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
dev: true dev: true
/ws@8.15.1: /ws@8.16.0:
resolution: {integrity: sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==} resolution: {integrity: sha512-HS0c//TP7Ina87TfiPUz1rQzMhHrl/SG2guqRcTOIUYD2q8uhUdNHZYJUaQ8aTGPzCh+c6oawMKW35nFl1dxyQ==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
peerDependencies: peerDependencies:
bufferutil: ^4.0.1 bufferutil: ^4.0.1

View File

@ -122,7 +122,7 @@ function createConfig(format, output, plugins = []) {
const isBundlerESMBuild = /esm-bundler/.test(format) const isBundlerESMBuild = /esm-bundler/.test(format)
const isBrowserESMBuild = /esm-browser/.test(format) const isBrowserESMBuild = /esm-browser/.test(format)
const isServerRenderer = name === 'server-renderer' const isServerRenderer = name === 'server-renderer'
const isNodeBuild = format === 'cjs' const isCJSBuild = format === 'cjs'
const isGlobalBuild = /global/.test(format) const isGlobalBuild = /global/.test(format)
const isCompatPackage = const isCompatPackage =
pkg.name === '@vue/compat' || pkg.name === '@vue/compat-canary' pkg.name === '@vue/compat' || pkg.name === '@vue/compat-canary'
@ -131,8 +131,14 @@ function createConfig(format, output, plugins = []) {
(isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) && (isGlobalBuild || isBrowserESMBuild || isBundlerESMBuild) &&
!packageOptions.enableNonBrowserBranches !packageOptions.enableNonBrowserBranches
output.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 (isNodeBuild) { if (isCJSBuild) {
output.esModule = true output.esModule = true
} }
output.sourcemap = !!process.env.SOURCE_MAP output.sourcemap = !!process.env.SOURCE_MAP
@ -170,7 +176,7 @@ function createConfig(format, output, plugins = []) {
tsconfig: path.resolve(__dirname, 'tsconfig.json'), tsconfig: path.resolve(__dirname, 'tsconfig.json'),
sourceMap: output.sourcemap, sourceMap: output.sourcemap,
minify: false, minify: false,
target: isServerRenderer || isNodeBuild ? 'es2019' : 'es2015', target: isServerRenderer || isCJSBuild ? 'es2019' : 'es2015',
define: resolveDefine(), define: resolveDefine(),
}), }),
...resolveNodePlugins(), ...resolveNodePlugins(),
@ -200,9 +206,9 @@ function createConfig(format, output, plugins = []) {
__ESM_BUNDLER__: String(isBundlerESMBuild), __ESM_BUNDLER__: String(isBundlerESMBuild),
__ESM_BROWSER__: String(isBrowserESMBuild), __ESM_BROWSER__: String(isBrowserESMBuild),
// is targeting Node (SSR)? // is targeting Node (SSR)?
__NODE_JS__: String(isNodeBuild), __CJS__: String(isCJSBuild),
// need SSR-specific branches? // need SSR-specific branches?
__SSR__: String(isNodeBuild || isBundlerESMBuild || isServerRenderer), __SSR__: String(isCJSBuild || isBundlerESMBuild || isServerRenderer),
// 2.x compat build // 2.x compat build
__COMPAT__: String(isCompatBuild), __COMPAT__: String(isCompatBuild),

View File

@ -120,7 +120,7 @@ for (const target of targets) {
__GLOBAL__: String(format === 'global'), __GLOBAL__: String(format === 'global'),
__ESM_BUNDLER__: String(format.includes('esm-bundler')), __ESM_BUNDLER__: String(format.includes('esm-bundler')),
__ESM_BROWSER__: String(format.includes('esm-browser')), __ESM_BROWSER__: String(format.includes('esm-browser')),
__NODE_JS__: String(format === 'cjs'), __CJS__: String(format === 'cjs'),
__SSR__: String(format === 'cjs' || format.includes('esm-bundler')), __SSR__: String(format === 'cjs' || format.includes('esm-bundler')),
__COMPAT__: String(target === 'vue-compat'), __COMPAT__: String(target === 'vue-compat'),
__FEATURE_SUSPENSE__: `true`, __FEATURE_SUSPENSE__: `true`,

View File

@ -6,7 +6,10 @@ import { createRequire } from 'node:module'
const require = createRequire(import.meta.url) const require = createRequire(import.meta.url)
export const targets = fs.readdirSync('packages').filter(f => { export const targets = fs.readdirSync('packages').filter(f => {
if (!fs.statSync(`packages/${f}`).isDirectory()) { if (
!fs.statSync(`packages/${f}`).isDirectory() ||
!fs.existsSync(`packages/${f}/package.json`)
) {
return false return false
} }
const pkg = require(`../packages/${f}/package.json`) const pkg = require(`../packages/${f}/package.json`)

View File

@ -11,7 +11,7 @@ export default defineConfig({
__GLOBAL__: false, __GLOBAL__: false,
__ESM_BUNDLER__: true, __ESM_BUNDLER__: true,
__ESM_BROWSER__: false, __ESM_BROWSER__: false,
__NODE_JS__: true, __CJS__: true,
__SSR__: true, __SSR__: true,
__FEATURE_OPTIONS_API__: true, __FEATURE_OPTIONS_API__: true,
__FEATURE_SUSPENSE__: true, __FEATURE_SUSPENSE__: true,