Merge tag 'v3.5.11'
This commit is contained in:
commit
c7fd6ae2dc
49
CHANGELOG.md
49
CHANGELOG.md
|
@ -1,3 +1,52 @@
|
||||||
|
## [3.5.11](https://github.com/vuejs/core/compare/v3.5.10...v3.5.11) (2024-10-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-sfc:** do not skip `TSSatisfiesExpression` when transforming props destructure ([#12062](https://github.com/vuejs/core/issues/12062)) ([2328b05](https://github.com/vuejs/core/commit/2328b051f4efa1f1394b7d4e73b7c3f76e430e7c)), closes [#12061](https://github.com/vuejs/core/issues/12061)
|
||||||
|
* **reactivity:** prevent overwriting `next` property during batch processing ([#12075](https://github.com/vuejs/core/issues/12075)) ([d3f5e6e](https://github.com/vuejs/core/commit/d3f5e6e5319b4ffaa55ca9a2ea3d95d78e76fa58)), closes [#12072](https://github.com/vuejs/core/issues/12072)
|
||||||
|
* **scheduler:** job ordering when the post queue is flushing ([#12090](https://github.com/vuejs/core/issues/12090)) ([577edca](https://github.com/vuejs/core/commit/577edca8e7795436efd710d1c289ea8ea2642b0e))
|
||||||
|
* **types:** correctly infer `TypeProps` when it is `any` ([#12073](https://github.com/vuejs/core/issues/12073)) ([57315ab](https://github.com/vuejs/core/commit/57315ab9688c9741a271d1075bbd28cbe5f71e2f)), closes [#12058](https://github.com/vuejs/core/issues/12058)
|
||||||
|
* **types:** should not intersect `PublicProps` with `Props` ([#12077](https://github.com/vuejs/core/issues/12077)) ([6f85894](https://github.com/vuejs/core/commit/6f8589437635706f825ccec51800effba1d2bf5f))
|
||||||
|
* **types:** infer the first generic type of `Ref` correctly ([#12094](https://github.com/vuejs/core/issues/12094)) ([c97bb84](https://github.com/vuejs/core/commit/c97bb84d0b0a16b012f886b6498e924415ed63e5))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.5.10](https://github.com/vuejs/core/compare/v3.5.9...v3.5.10) (2024-09-27)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **custom-element:** properly set kebab-case props on Vue custom elements ([ea3efa0](https://github.com/vuejs/core/commit/ea3efa09e008918c1d9ba7226833a8b1a7a57244)), closes [#12030](https://github.com/vuejs/core/issues/12030) [#12032](https://github.com/vuejs/core/issues/12032)
|
||||||
|
* **reactivity:** fix nested batch edge case ([93c95dd](https://github.com/vuejs/core/commit/93c95dd4cd416503f43a98a1455f62658d22b0b2))
|
||||||
|
* **reactivity:** only clear notified flags for computed in first batch iteration ([aa9ef23](https://github.com/vuejs/core/commit/aa9ef2386a0cd39a174e5a887ec2b1a3525034fc)), closes [#12045](https://github.com/vuejs/core/issues/12045)
|
||||||
|
* **types/ref:** handle nested refs in UnwrapRef ([#12049](https://github.com/vuejs/core/issues/12049)) ([e2c19c2](https://github.com/vuejs/core/commit/e2c19c20cfee9788519a80c0e53e216b78505994)), closes [#12044](https://github.com/vuejs/core/issues/12044)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.5.9](https://github.com/vuejs/core/compare/v3.5.8...v3.5.9) (2024-09-26)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **reactivity:** fix property dep removal regression ([6001e5c](https://github.com/vuejs/core/commit/6001e5c81a05c894586f9287fbd991677bdd0455)), closes [#12020](https://github.com/vuejs/core/issues/12020) [#12021](https://github.com/vuejs/core/issues/12021)
|
||||||
|
* **reactivity:** fix recursive sync watcher on computed edge case ([10ff159](https://github.com/vuejs/core/commit/10ff15924053d9bd95ad706f78ce09e288213fcf)), closes [#12033](https://github.com/vuejs/core/issues/12033) [#12037](https://github.com/vuejs/core/issues/12037)
|
||||||
|
* **runtime-core:** avoid rendering plain object as VNode ([#12038](https://github.com/vuejs/core/issues/12038)) ([cb34b28](https://github.com/vuejs/core/commit/cb34b28a4a9bf868be4785b001c526163eda342e)), closes [#12035](https://github.com/vuejs/core/issues/12035) [vitejs/vite-plugin-vue#353](https://github.com/vitejs/vite-plugin-vue/issues/353)
|
||||||
|
* **runtime-core:** make useId() always return a string ([a177092](https://github.com/vuejs/core/commit/a177092754642af2f98c33a4feffe8f198c3c950))
|
||||||
|
* **types:** correct type inference of union event names ([#12022](https://github.com/vuejs/core/issues/12022)) ([4da6881](https://github.com/vuejs/core/commit/4da688141d9e7c15b622c289deaa81b11845b2c7))
|
||||||
|
* **vue:** properly cache runtime compilation ([#12019](https://github.com/vuejs/core/issues/12019)) ([fa0ba24](https://github.com/vuejs/core/commit/fa0ba24b3ace02d7ecab65e57c2bea89a2550dcb))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.5.8](https://github.com/vuejs/core/compare/v3.5.7...v3.5.8) (2024-09-22)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **reactivity:** do not remove dep from depsMap when cleaning up deps of computed ([#11995](https://github.com/vuejs/core/issues/11995)) ([0267a58](https://github.com/vuejs/core/commit/0267a588017eee4951ac2a877fe1ccae84cad905))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20)
|
## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20)
|
||||||
|
|
||||||
|
|
||||||
|
|
18
package.json
18
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"packageManager": "pnpm@9.10.0",
|
"packageManager": "pnpm@9.12.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js vue vue-vapor",
|
"dev": "node scripts/dev.js vue vue-vapor",
|
||||||
|
@ -61,14 +61,14 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/parser": "catalog:",
|
"@babel/parser": "catalog:",
|
||||||
"@babel/types": "catalog:",
|
"@babel/types": "catalog:",
|
||||||
"@rollup/plugin-alias": "^5.1.0",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-commonjs": "^26.0.1",
|
"@rollup/plugin-commonjs": "^26.0.3",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@swc/core": "^1.7.26",
|
"@swc/core": "^1.7.28",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^20.16.5",
|
"@types/node": "^20.16.10",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.5.8",
|
||||||
"@types/serve-handler": "^6.1.4",
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitest/coverage-v8": "^2.1.1",
|
"@vitest/coverage-v8": "^2.1.1",
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^5.0.0",
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.23.1",
|
"esbuild": "^0.24.0",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.10.0",
|
"eslint": "^9.10.0",
|
||||||
"eslint-plugin-import-x": "^4.2.1",
|
"eslint-plugin-import-x": "^4.2.1",
|
||||||
|
@ -95,7 +95,7 @@
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~23.3.0",
|
"puppeteer": "~23.3.0",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.21.3",
|
"rollup": "^4.24.0",
|
||||||
"rollup-plugin-dts": "^6.1.1",
|
"rollup-plugin-dts": "^6.1.1",
|
||||||
"rollup-plugin-esbuild": "^6.1.1",
|
"rollup-plugin-esbuild": "^6.1.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
|
|
|
@ -189,6 +189,24 @@ describe('allow getter and setter types to be unrelated', <T>() => {
|
||||||
f.value = ref(1)
|
f.value = ref(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('correctly unwraps nested refs', () => {
|
||||||
|
const obj = {
|
||||||
|
n: 24,
|
||||||
|
ref: ref(24),
|
||||||
|
nestedRef: ref({ n: ref(0) }),
|
||||||
|
}
|
||||||
|
|
||||||
|
const a = ref(obj)
|
||||||
|
expectType<number>(a.value.n)
|
||||||
|
expectType<number>(a.value.ref)
|
||||||
|
expectType<number>(a.value.nestedRef.n)
|
||||||
|
|
||||||
|
const b = reactive({ a })
|
||||||
|
expectType<number>(b.a.n)
|
||||||
|
expectType<number>(b.a.ref)
|
||||||
|
expectType<number>(b.a.nestedRef.n)
|
||||||
|
})
|
||||||
|
|
||||||
// computed
|
// computed
|
||||||
describe('allow computed getter and setter types to be unrelated', () => {
|
describe('allow computed getter and setter types to be unrelated', () => {
|
||||||
const obj = ref({
|
const obj = ref({
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"vue": "^3.4.0"
|
"vue": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.3",
|
"@vitejs/plugin-vue": "^5.1.4",
|
||||||
"vite": "^5.4.5"
|
"vite": "^5.4.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
@ -63,6 +63,6 @@
|
||||||
"postcss-modules": "^6.0.0",
|
"postcss-modules": "^6.0.0",
|
||||||
"postcss-selector-parser": "^6.1.2",
|
"postcss-selector-parser": "^6.1.2",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.78.0"
|
"sass": "^1.79.4"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import { createCache } from './cache'
|
||||||
import type { ImportBinding } from './compileScript'
|
import type { ImportBinding } from './compileScript'
|
||||||
import { isImportUsed } from './script/importUsageCheck'
|
import { isImportUsed } from './script/importUsageCheck'
|
||||||
import type { LRUCache } from 'lru-cache'
|
import type { LRUCache } from 'lru-cache'
|
||||||
|
import { genCacheKey } from '@vue/shared'
|
||||||
|
|
||||||
export const DEFAULT_FILENAME = 'anonymous.vue'
|
export const DEFAULT_FILENAME = 'anonymous.vue'
|
||||||
|
|
||||||
|
@ -105,24 +106,14 @@ export const parseCache:
|
||||||
| Map<string, SFCParseResult>
|
| Map<string, SFCParseResult>
|
||||||
| LRUCache<string, SFCParseResult> = createCache<SFCParseResult>()
|
| LRUCache<string, SFCParseResult> = createCache<SFCParseResult>()
|
||||||
|
|
||||||
function genCacheKey(source: string, options: SFCParseOptions): string {
|
|
||||||
return (
|
|
||||||
source +
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
...options,
|
|
||||||
compiler: { parse: options.compiler?.parse },
|
|
||||||
},
|
|
||||||
(_, val) => (typeof val === 'function' ? val.toString() : val),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export function parse(
|
export function parse(
|
||||||
source: string,
|
source: string,
|
||||||
options: SFCParseOptions = {},
|
options: SFCParseOptions = {},
|
||||||
): SFCParseResult {
|
): SFCParseResult {
|
||||||
const sourceKey = genCacheKey(source, options)
|
const sourceKey = genCacheKey(source, {
|
||||||
|
...options,
|
||||||
|
compiler: { parse: options.compiler?.parse },
|
||||||
|
})
|
||||||
const cache = parseCache.get(sourceKey)
|
const cache = parseCache.get(sourceKey)
|
||||||
if (cache) {
|
if (cache) {
|
||||||
return cache
|
return cache
|
||||||
|
|
|
@ -242,6 +242,7 @@ export function transformDestructuredProps(
|
||||||
parent.type.startsWith('TS') &&
|
parent.type.startsWith('TS') &&
|
||||||
parent.type !== 'TSAsExpression' &&
|
parent.type !== 'TSAsExpression' &&
|
||||||
parent.type !== 'TSNonNullExpression' &&
|
parent.type !== 'TSNonNullExpression' &&
|
||||||
|
parent.type !== 'TSSatisfiesExpression' &&
|
||||||
parent.type !== 'TSTypeAssertion'
|
parent.type !== 'TSTypeAssertion'
|
||||||
) {
|
) {
|
||||||
return this.skip()
|
return this.skip()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -1013,20 +1013,98 @@ describe('reactivity/computed', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('computed should remain live after losing all subscribers', () => {
|
test('computed should remain live after losing all subscribers', () => {
|
||||||
const toggle = ref(true)
|
const state = reactive({ a: 1 })
|
||||||
const state = reactive({
|
|
||||||
a: 1,
|
|
||||||
})
|
|
||||||
const p = computed(() => state.a + 1)
|
const p = computed(() => state.a + 1)
|
||||||
const pp = computed(() => {
|
const { effect: e } = effect(() => p.value)
|
||||||
return toggle.value ? p.value : 111
|
|
||||||
})
|
|
||||||
|
|
||||||
const { effect: e } = effect(() => pp.value)
|
|
||||||
e.stop()
|
e.stop()
|
||||||
|
|
||||||
expect(p.value).toBe(2)
|
expect(p.value).toBe(2)
|
||||||
state.a++
|
state.a++
|
||||||
expect(p.value).toBe(3)
|
expect(p.value).toBe(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #11995
|
||||||
|
test('computed dep cleanup should not cause property dep to be deleted', () => {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const state = reactive({ a: 1 })
|
||||||
|
const p = computed(() => {
|
||||||
|
return toggle.value ? state.a : 111
|
||||||
|
})
|
||||||
|
const pp = computed(() => state.a)
|
||||||
|
effect(() => p.value)
|
||||||
|
|
||||||
|
expect(pp.value).toBe(1)
|
||||||
|
toggle.value = false
|
||||||
|
state.a++
|
||||||
|
expect(pp.value).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
// #12020
|
||||||
|
test('computed value updates correctly after dep cleanup', () => {
|
||||||
|
const obj = reactive({ foo: 1, flag: 1 })
|
||||||
|
const c1 = computed(() => obj.foo)
|
||||||
|
|
||||||
|
let foo
|
||||||
|
effect(() => {
|
||||||
|
foo = obj.flag ? (obj.foo, c1.value) : 0
|
||||||
|
})
|
||||||
|
expect(foo).toBe(1)
|
||||||
|
|
||||||
|
obj.flag = 0
|
||||||
|
expect(foo).toBe(0)
|
||||||
|
|
||||||
|
obj.foo = 2
|
||||||
|
obj.flag = 1
|
||||||
|
expect(foo).toBe(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
// #11928
|
||||||
|
test('should not lead to exponential perf cost with deeply chained computed', () => {
|
||||||
|
const start = {
|
||||||
|
prop1: shallowRef(1),
|
||||||
|
prop2: shallowRef(2),
|
||||||
|
prop3: shallowRef(3),
|
||||||
|
prop4: shallowRef(4),
|
||||||
|
}
|
||||||
|
|
||||||
|
let layer = start
|
||||||
|
|
||||||
|
const LAYERS = 1000
|
||||||
|
|
||||||
|
for (let i = LAYERS; i > 0; i--) {
|
||||||
|
const m = layer
|
||||||
|
const s = {
|
||||||
|
prop1: computed(() => m.prop2.value),
|
||||||
|
prop2: computed(() => m.prop1.value - m.prop3.value),
|
||||||
|
prop3: computed(() => m.prop2.value + m.prop4.value),
|
||||||
|
prop4: computed(() => m.prop3.value),
|
||||||
|
}
|
||||||
|
effect(() => s.prop1.value)
|
||||||
|
effect(() => s.prop2.value)
|
||||||
|
effect(() => s.prop3.value)
|
||||||
|
effect(() => s.prop4.value)
|
||||||
|
|
||||||
|
s.prop1.value
|
||||||
|
s.prop2.value
|
||||||
|
s.prop3.value
|
||||||
|
s.prop4.value
|
||||||
|
|
||||||
|
layer = s
|
||||||
|
}
|
||||||
|
|
||||||
|
const t = performance.now()
|
||||||
|
start.prop1.value = 4
|
||||||
|
start.prop2.value = 3
|
||||||
|
start.prop3.value = 2
|
||||||
|
start.prop4.value = 1
|
||||||
|
expect(performance.now() - t).toBeLessThan(process.env.CI ? 100 : 30)
|
||||||
|
|
||||||
|
const end = layer
|
||||||
|
expect([
|
||||||
|
end.prop1.value,
|
||||||
|
end.prop2.value,
|
||||||
|
end.prop3.value,
|
||||||
|
end.prop4.value,
|
||||||
|
]).toMatchObject([-2, -4, 2, 3])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from '../src/reactive'
|
} from '../src/reactive'
|
||||||
import { computed } from '../src/computed'
|
import { computed } from '../src/computed'
|
||||||
import { effect } from '../src/effect'
|
import { effect } from '../src/effect'
|
||||||
|
import { targetMap } from '../src/dep'
|
||||||
|
|
||||||
describe('reactivity/reactive', () => {
|
describe('reactivity/reactive', () => {
|
||||||
test('Object', () => {
|
test('Object', () => {
|
||||||
|
@ -398,4 +399,14 @@ describe('reactivity/reactive', () => {
|
||||||
a.value++
|
a.value++
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #11979
|
||||||
|
test('should release property Dep instance if it no longer has subscribers', () => {
|
||||||
|
let obj = { x: 1 }
|
||||||
|
let a = reactive(obj)
|
||||||
|
const e = effect(() => a.x)
|
||||||
|
expect(targetMap.get(obj)?.get('x')).toBeTruthy()
|
||||||
|
e.effect.stop()
|
||||||
|
expect(targetMap.get(obj)?.get('x')).toBeFalsy()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
WatchErrorCodes,
|
WatchErrorCodes,
|
||||||
type WatchOptions,
|
type WatchOptions,
|
||||||
type WatchScheduler,
|
type WatchScheduler,
|
||||||
|
computed,
|
||||||
onWatcherCleanup,
|
onWatcherCleanup,
|
||||||
ref,
|
ref,
|
||||||
watch,
|
watch,
|
||||||
|
@ -209,4 +210,71 @@ describe('watch', () => {
|
||||||
source.value++
|
source.value++
|
||||||
expect(dummy).toBe(1)
|
expect(dummy).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12033
|
||||||
|
test('recursive sync watcher on computed', () => {
|
||||||
|
const r = ref(0)
|
||||||
|
const c = computed(() => r.value)
|
||||||
|
|
||||||
|
watch(c, v => {
|
||||||
|
if (v > 1) {
|
||||||
|
r.value--
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(r.value).toBe(0)
|
||||||
|
expect(c.value).toBe(0)
|
||||||
|
|
||||||
|
r.value = 10
|
||||||
|
expect(r.value).toBe(1)
|
||||||
|
expect(c.value).toBe(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
// edge case where a nested endBatch() causes an effect to be batched in a
|
||||||
|
// nested batch loop with its .next mutated, causing the outer loop to end
|
||||||
|
// early
|
||||||
|
test('nested batch edge case', () => {
|
||||||
|
// useClamp from VueUse
|
||||||
|
const clamp = (n: number, min: number, max: number) =>
|
||||||
|
Math.min(max, Math.max(min, n))
|
||||||
|
function useClamp(src: Ref<number>, min: number, max: number) {
|
||||||
|
return computed({
|
||||||
|
get() {
|
||||||
|
return (src.value = clamp(src.value, min, max))
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
src.value = clamp(val, min, max)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const src = ref(1)
|
||||||
|
const clamped = useClamp(src, 1, 5)
|
||||||
|
watch(src, val => (clamped.value = val))
|
||||||
|
|
||||||
|
const spy = vi.fn()
|
||||||
|
watch(clamped, spy)
|
||||||
|
|
||||||
|
src.value = 2
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
src.value = 10
|
||||||
|
expect(spy).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should ensure correct execution order in batch processing', () => {
|
||||||
|
const dummy: number[] = []
|
||||||
|
const n1 = ref(0)
|
||||||
|
const n2 = ref(0)
|
||||||
|
const sum = computed(() => n1.value + n2.value)
|
||||||
|
watch(n1, () => {
|
||||||
|
dummy.push(1)
|
||||||
|
n2.value++
|
||||||
|
})
|
||||||
|
watch(sum, () => dummy.push(2))
|
||||||
|
watch(n1, () => dummy.push(3))
|
||||||
|
|
||||||
|
n1.value++
|
||||||
|
|
||||||
|
expect(dummy).toEqual([1, 2, 3])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
@ -84,9 +84,13 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
isSSR: boolean
|
isSSR: boolean
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
next?: Subscriber = undefined
|
||||||
|
|
||||||
// for backwards compat
|
// for backwards compat
|
||||||
effect: this = this
|
effect: this = this
|
||||||
|
|
||||||
// dev only
|
// dev only
|
||||||
onTrack?: (event: DebuggerEvent) => void
|
onTrack?: (event: DebuggerEvent) => void
|
||||||
// dev only
|
// dev only
|
||||||
|
@ -117,7 +121,7 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
||||||
// avoid infinite self recursion
|
// avoid infinite self recursion
|
||||||
activeSub !== this
|
activeSub !== this
|
||||||
) {
|
) {
|
||||||
batch(this)
|
batch(this, true)
|
||||||
return true
|
return true
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
// TODO warn
|
// TODO warn
|
||||||
|
|
|
@ -85,10 +85,14 @@ export class Dep {
|
||||||
/**
|
/**
|
||||||
* For object property deps cleanup
|
* For object property deps cleanup
|
||||||
*/
|
*/
|
||||||
target?: unknown = undefined
|
|
||||||
map?: KeyToDepMap = undefined
|
map?: KeyToDepMap = undefined
|
||||||
key?: unknown = undefined
|
key?: unknown = undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Subscriber counter
|
||||||
|
*/
|
||||||
|
sc: number = 0
|
||||||
|
|
||||||
constructor(public computed?: ComputedRefImpl | undefined) {
|
constructor(public computed?: ComputedRefImpl | undefined) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
this.subsHead = undefined
|
this.subsHead = undefined
|
||||||
|
@ -113,9 +117,7 @@ export class Dep {
|
||||||
activeSub.depsTail = link
|
activeSub.depsTail = link
|
||||||
}
|
}
|
||||||
|
|
||||||
if (activeSub.flags & EffectFlags.TRACKING) {
|
|
||||||
addSub(link)
|
addSub(link)
|
||||||
}
|
|
||||||
} else if (link.version === -1) {
|
} else if (link.version === -1) {
|
||||||
// reused from last run - already a sub, just sync version
|
// reused from last run - already a sub, just sync version
|
||||||
link.version = this.version
|
link.version = this.version
|
||||||
|
@ -197,6 +199,8 @@ export class Dep {
|
||||||
}
|
}
|
||||||
|
|
||||||
function addSub(link: Link) {
|
function addSub(link: Link) {
|
||||||
|
link.dep.sc++
|
||||||
|
if (link.sub.flags & EffectFlags.TRACKING) {
|
||||||
const computed = link.dep.computed
|
const computed = link.dep.computed
|
||||||
// computed getting its first subscriber
|
// computed getting its first subscriber
|
||||||
// enable tracking + lazily subscribe to all its deps
|
// enable tracking + lazily subscribe to all its deps
|
||||||
|
@ -219,6 +223,7 @@ function addSub(link: Link) {
|
||||||
|
|
||||||
link.dep.subs = link
|
link.dep.subs = link
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -257,7 +262,6 @@ export function track(target: object, type: TrackOpTypes, key: unknown): void {
|
||||||
let dep = depsMap.get(key)
|
let dep = depsMap.get(key)
|
||||||
if (!dep) {
|
if (!dep) {
|
||||||
depsMap.set(key, (dep = new Dep()))
|
depsMap.set(key, (dep = new Dep()))
|
||||||
dep.target = target
|
|
||||||
dep.map = depsMap
|
dep.map = depsMap
|
||||||
dep.key = key
|
dep.key = key
|
||||||
}
|
}
|
||||||
|
@ -378,13 +382,10 @@ export function trigger(
|
||||||
endBatch()
|
endBatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Test only
|
|
||||||
*/
|
|
||||||
export function getDepFromReactive(
|
export function getDepFromReactive(
|
||||||
object: any,
|
object: any,
|
||||||
key: string | number | symbol,
|
key: string | number | symbol,
|
||||||
): Dep | undefined {
|
): Dep | undefined {
|
||||||
// eslint-disable-next-line
|
const depMap = targetMap.get(object)
|
||||||
return targetMap.get(object)?.get(key)
|
return depMap && depMap.get(key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import { extend, hasChanged } from '@vue/shared'
|
import { extend, hasChanged } from '@vue/shared'
|
||||||
import type { ComputedRefImpl } from './computed'
|
import type { ComputedRefImpl } from './computed'
|
||||||
import type { TrackOpTypes, TriggerOpTypes } from './constants'
|
import type { TrackOpTypes, TriggerOpTypes } from './constants'
|
||||||
import { type Link, globalVersion, targetMap } from './dep'
|
import { type Link, globalVersion } from './dep'
|
||||||
import { activeEffectScope } from './effectScope'
|
import { activeEffectScope } from './effectScope'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
@ -234,9 +234,15 @@ export class ReactiveEffect<T = any>
|
||||||
|
|
||||||
let batchDepth = 0
|
let batchDepth = 0
|
||||||
let batchedSub: Subscriber | undefined
|
let batchedSub: Subscriber | undefined
|
||||||
|
let batchedComputed: Subscriber | undefined
|
||||||
|
|
||||||
export function batch(sub: Subscriber): void {
|
export function batch(sub: Subscriber, isComputed = false): void {
|
||||||
sub.flags |= EffectFlags.NOTIFIED
|
sub.flags |= EffectFlags.NOTIFIED
|
||||||
|
if (isComputed) {
|
||||||
|
sub.next = batchedComputed
|
||||||
|
batchedComputed = sub
|
||||||
|
return
|
||||||
|
}
|
||||||
sub.next = batchedSub
|
sub.next = batchedSub
|
||||||
batchedSub = sub
|
batchedSub = sub
|
||||||
}
|
}
|
||||||
|
@ -257,6 +263,17 @@ export function endBatch(): void {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (batchedComputed) {
|
||||||
|
let e: Subscriber | undefined = batchedComputed
|
||||||
|
batchedComputed = undefined
|
||||||
|
while (e) {
|
||||||
|
const next: Subscriber | undefined = e.next
|
||||||
|
e.next = undefined
|
||||||
|
e.flags &= ~EffectFlags.NOTIFIED
|
||||||
|
e = next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let error: unknown
|
let error: unknown
|
||||||
while (batchedSub) {
|
while (batchedSub) {
|
||||||
let e: Subscriber | undefined = batchedSub
|
let e: Subscriber | undefined = batchedSub
|
||||||
|
@ -399,7 +416,7 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeSub(link: Link, fromComputed = false) {
|
function removeSub(link: Link, soft = false) {
|
||||||
const { dep, prevSub, nextSub } = link
|
const { dep, prevSub, nextSub } = link
|
||||||
if (prevSub) {
|
if (prevSub) {
|
||||||
prevSub.nextSub = nextSub
|
prevSub.nextSub = nextSub
|
||||||
|
@ -418,20 +435,23 @@ function removeSub(link: Link, fromComputed = false) {
|
||||||
dep.subsHead = nextSub
|
dep.subsHead = nextSub
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dep.subs) {
|
if (!dep.subs && dep.computed) {
|
||||||
// last subscriber removed
|
|
||||||
if (dep.computed) {
|
|
||||||
// if computed, unsubscribe it from all its deps so this computed and its
|
// if computed, unsubscribe it from all its deps so this computed and its
|
||||||
// value can be GCed
|
// value can be GCed
|
||||||
dep.computed.flags &= ~EffectFlags.TRACKING
|
dep.computed.flags &= ~EffectFlags.TRACKING
|
||||||
for (let l = dep.computed.deps; l; l = l.nextDep) {
|
for (let l = dep.computed.deps; l; l = l.nextDep) {
|
||||||
|
// here we are only "soft" unsubscribing because the computed still keeps
|
||||||
|
// referencing the deps and the dep should not decrease its sub count
|
||||||
removeSub(l, true)
|
removeSub(l, true)
|
||||||
}
|
}
|
||||||
} else if (dep.map && !fromComputed) {
|
|
||||||
// property dep, remove it from the owner depsMap
|
|
||||||
dep.map.delete(dep.key)
|
|
||||||
if (!dep.map.size) targetMap.delete(dep.target!)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!soft && !--dep.sc && dep.map) {
|
||||||
|
// #11979
|
||||||
|
// property dep no longer has effect subscribers, delete it
|
||||||
|
// this mostly is for the case where an object is kept in memory but only a
|
||||||
|
// subset of its properties is tracked at one time
|
||||||
|
dep.map.delete(dep.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -167,7 +167,7 @@ export type DeepReadonly<T> = T extends Builtin
|
||||||
? WeakSet<DeepReadonly<U>>
|
? WeakSet<DeepReadonly<U>>
|
||||||
: T extends Promise<infer U>
|
: T extends Promise<infer U>
|
||||||
? Promise<DeepReadonly<U>>
|
? Promise<DeepReadonly<U>>
|
||||||
: T extends Ref<infer U>
|
: T extends Ref<infer U, unknown>
|
||||||
? Readonly<Ref<DeepReadonly<U>>>
|
? Readonly<Ref<DeepReadonly<U>>>
|
||||||
: T extends {}
|
: T extends {}
|
||||||
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
? { readonly [K in keyof T]: DeepReadonly<T[K]> }
|
||||||
|
|
|
@ -62,7 +62,9 @@ export function ref(value?: unknown) {
|
||||||
|
|
||||||
declare const ShallowRefMarker: unique symbol
|
declare const ShallowRefMarker: unique symbol
|
||||||
|
|
||||||
export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
|
export type ShallowRef<T = any, S = T> = Ref<T, S> & {
|
||||||
|
[ShallowRefMarker]?: true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shallow version of {@link ref()}.
|
* Shallow version of {@link ref()}.
|
||||||
|
@ -487,12 +489,12 @@ export type ShallowUnwrapRef<T> = {
|
||||||
[K in keyof T]: DistributeRef<T[K]>
|
[K in keyof T]: DistributeRef<T[K]>
|
||||||
}
|
}
|
||||||
|
|
||||||
type DistributeRef<T> = T extends Ref<infer V> ? V : T
|
type DistributeRef<T> = T extends Ref<infer V, unknown> ? V : T
|
||||||
|
|
||||||
export type UnwrapRef<T> =
|
export type UnwrapRef<T> =
|
||||||
T extends ShallowRef<infer V>
|
T extends ShallowRef<infer V, unknown>
|
||||||
? V
|
? V
|
||||||
: T extends Ref<infer V>
|
: T extends Ref<infer V, unknown>
|
||||||
? UnwrapRefSimple<V>
|
? UnwrapRefSimple<V>
|
||||||
: UnwrapRefSimple<T>
|
: UnwrapRefSimple<T>
|
||||||
|
|
||||||
|
|
|
@ -1930,7 +1930,7 @@ describe('api: watch', () => {
|
||||||
warn.mockRestore()
|
warn.mockRestore()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should be executed correctly', () => {
|
test('should be executed correctly', () => {
|
||||||
const v = ref(1)
|
const v = ref(1)
|
||||||
let foo = ''
|
let foo = ''
|
||||||
|
|
||||||
|
@ -1957,4 +1957,30 @@ describe('api: watch', () => {
|
||||||
v.value++
|
v.value++
|
||||||
expect(foo).toBe('12')
|
expect(foo).toBe('12')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 12045
|
||||||
|
test('sync watcher should not break pre watchers', async () => {
|
||||||
|
const count1 = ref(0)
|
||||||
|
const count2 = ref(0)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
count1,
|
||||||
|
() => {
|
||||||
|
count2.value++
|
||||||
|
},
|
||||||
|
{ flush: 'sync' },
|
||||||
|
)
|
||||||
|
|
||||||
|
const spy1 = vi.fn()
|
||||||
|
watch([count1, count2], spy1)
|
||||||
|
|
||||||
|
const spy2 = vi.fn()
|
||||||
|
watch(count1, spy2)
|
||||||
|
|
||||||
|
count1.value++
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
expect(spy1).toHaveBeenCalled()
|
||||||
|
expect(spy2).toHaveBeenCalled()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -65,6 +65,15 @@ test('array children -> text children', () => {
|
||||||
expect(inner(root)).toBe('<div>hello</div>')
|
expect(inner(root)).toBe('<div>hello</div>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('plain object child', () => {
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const foo = { foo: '1' }
|
||||||
|
// @ts-expect-error
|
||||||
|
render(h('div', null, [foo]), root)
|
||||||
|
expect('Invalid VNode type').not.toHaveBeenWarned()
|
||||||
|
expect(inner(root)).toBe('<div>[object Object]</div>')
|
||||||
|
})
|
||||||
|
|
||||||
describe('renderer: keyed children', () => {
|
describe('renderer: keyed children', () => {
|
||||||
let root: TestElement
|
let root: TestElement
|
||||||
let elm: TestElement
|
let elm: TestElement
|
||||||
|
|
|
@ -441,6 +441,29 @@ describe('scheduler', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
|
expect(calls).toEqual(['job1', 'job2', 'cb1', 'cb2'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('jobs added during post flush are ordered correctly', async () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
|
||||||
|
const job1: SchedulerJob = () => {
|
||||||
|
calls.push('job1')
|
||||||
|
}
|
||||||
|
job1.id = 1
|
||||||
|
|
||||||
|
const job2: SchedulerJob = () => {
|
||||||
|
calls.push('job2')
|
||||||
|
}
|
||||||
|
job2.id = 2
|
||||||
|
|
||||||
|
queuePostFlushCb(() => {
|
||||||
|
queueJob(job2)
|
||||||
|
queueJob(job1)
|
||||||
|
})
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(calls).toEqual(['job1', 'job2'])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test('sort job based on id', async () => {
|
test('sort job based on id', async () => {
|
||||||
|
@ -758,6 +781,37 @@ describe('scheduler', () => {
|
||||||
expect(spy).toHaveBeenCalledTimes(1)
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('flushPreFlushCbs inside a post job', async () => {
|
||||||
|
const calls: string[] = []
|
||||||
|
const callsAfterFlush: string[] = []
|
||||||
|
|
||||||
|
const job1: SchedulerJob = () => {
|
||||||
|
calls.push('job1')
|
||||||
|
}
|
||||||
|
job1.id = 1
|
||||||
|
job1.flags! |= SchedulerJobFlags.PRE
|
||||||
|
|
||||||
|
const job2: SchedulerJob = () => {
|
||||||
|
calls.push('job2')
|
||||||
|
}
|
||||||
|
job2.id = 2
|
||||||
|
job2.flags! |= SchedulerJobFlags.PRE
|
||||||
|
|
||||||
|
queuePostFlushCb(() => {
|
||||||
|
queueJob(job2)
|
||||||
|
queueJob(job1)
|
||||||
|
|
||||||
|
// e.g. nested app.mount() call
|
||||||
|
flushPreFlushCbs()
|
||||||
|
callsAfterFlush.push(...calls)
|
||||||
|
})
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
|
||||||
|
expect(callsAfterFlush).toEqual(['job1', 'job2'])
|
||||||
|
expect(calls).toEqual(['job1', 'job2'])
|
||||||
|
})
|
||||||
|
|
||||||
it('nextTick should return promise', async () => {
|
it('nextTick should return promise', async () => {
|
||||||
const fn = vi.fn(() => {
|
const fn = vi.fn(() => {
|
||||||
return 1
|
return 1
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-core",
|
"name": "@vue/runtime-core",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -27,7 +27,7 @@ import type {
|
||||||
EmitsToProps,
|
EmitsToProps,
|
||||||
TypeEmitsToOptions,
|
TypeEmitsToOptions,
|
||||||
} from './componentEmits'
|
} from './componentEmits'
|
||||||
import { extend, isFunction } from '@vue/shared'
|
import { type IsKeyValues, extend, isFunction } from '@vue/shared'
|
||||||
import type { VNodeProps } from './vnode'
|
import type { VNodeProps } from './vnode'
|
||||||
import type {
|
import type {
|
||||||
ComponentPublicInstanceConstructor,
|
ComponentPublicInstanceConstructor,
|
||||||
|
@ -79,7 +79,7 @@ export type DefineComponent<
|
||||||
Mixin,
|
Mixin,
|
||||||
Extends,
|
Extends,
|
||||||
E,
|
E,
|
||||||
PP & Props,
|
PP,
|
||||||
Defaults,
|
Defaults,
|
||||||
MakeDefaultsOptional,
|
MakeDefaultsOptional,
|
||||||
{},
|
{},
|
||||||
|
@ -208,15 +208,13 @@ export function defineComponent<
|
||||||
ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
|
ResolvedEmits extends EmitsOptions = {} extends RuntimeEmitsOptions
|
||||||
? TypeEmitsToOptions<TypeEmits>
|
? TypeEmitsToOptions<TypeEmits>
|
||||||
: RuntimeEmitsOptions,
|
: RuntimeEmitsOptions,
|
||||||
InferredProps = unknown extends TypeProps
|
InferredProps = IsKeyValues<TypeProps> extends true
|
||||||
? keyof TypeProps extends never
|
? TypeProps
|
||||||
? string extends RuntimePropsKeys
|
: string extends RuntimePropsKeys
|
||||||
? ComponentObjectPropsOptions extends RuntimePropsOptions
|
? ComponentObjectPropsOptions extends RuntimePropsOptions
|
||||||
? {}
|
? {}
|
||||||
: ExtractPropTypes<RuntimePropsOptions>
|
: ExtractPropTypes<RuntimePropsOptions>
|
||||||
: { [key in RuntimePropsKeys]?: any }
|
: { [key in RuntimePropsKeys]?: any },
|
||||||
: TypeProps
|
|
||||||
: TypeProps,
|
|
||||||
TypeRefs extends Record<string, unknown> = {},
|
TypeRefs extends Record<string, unknown> = {},
|
||||||
TypeEl extends Element = any,
|
TypeEl extends Element = any,
|
||||||
>(
|
>(
|
||||||
|
|
|
@ -66,11 +66,23 @@ export type TypeEmitsToOptions<T extends ComponentTypeEmits> = {
|
||||||
: {})
|
: {})
|
||||||
|
|
||||||
type ParametersToFns<T extends any[]> = {
|
type ParametersToFns<T extends any[]> = {
|
||||||
[K in T[0]]: K extends `${infer C}`
|
[K in T[0]]: IsStringLiteral<K> extends true
|
||||||
? (...args: T extends [C, ...infer Args] ? Args : never) => any
|
? (
|
||||||
|
...args: T extends [e: infer E, ...args: infer P]
|
||||||
|
? K extends E
|
||||||
|
? P
|
||||||
|
: never
|
||||||
|
: never
|
||||||
|
) => any
|
||||||
: never
|
: never
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type IsStringLiteral<T> = T extends string
|
||||||
|
? string extends T
|
||||||
|
? false
|
||||||
|
: true
|
||||||
|
: false
|
||||||
|
|
||||||
export type ShortEmitsToObject<E> =
|
export type ShortEmitsToObject<E> =
|
||||||
E extends Record<string, any[]>
|
E extends Record<string, any[]>
|
||||||
? {
|
? {
|
||||||
|
|
|
@ -4,7 +4,7 @@ import {
|
||||||
} from '../component'
|
} from '../component'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
|
||||||
export function useId(): string | undefined {
|
export function useId(): string {
|
||||||
const i = getCurrentInstance()
|
const i = getCurrentInstance()
|
||||||
if (i) {
|
if (i) {
|
||||||
return (i.appContext.config.idPrefix || 'v') + '-' + i.ids[0] + i.ids[1]++
|
return (i.appContext.config.idPrefix || 'v') + '-' + i.ids[0] + i.ids[1]++
|
||||||
|
@ -14,6 +14,7 @@ export function useId(): string | undefined {
|
||||||
`instance to be associated with.`,
|
`instance to be associated with.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {
|
import {
|
||||||
Comment,
|
|
||||||
Fragment,
|
Fragment,
|
||||||
Static,
|
Static,
|
||||||
Text,
|
Text,
|
||||||
|
Comment as VComment,
|
||||||
type VNode,
|
type VNode,
|
||||||
type VNodeHook,
|
type VNodeHook,
|
||||||
createTextVNode,
|
createTextVNode,
|
||||||
|
@ -195,7 +195,7 @@ export function createHydrationFunctions(
|
||||||
nextNode = nextSibling(node)
|
nextNode = nextSibling(node)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case Comment:
|
case VComment:
|
||||||
if (isTemplateNode(node)) {
|
if (isTemplateNode(node)) {
|
||||||
nextNode = nextSibling(node)
|
nextNode = nextSibling(node)
|
||||||
// wrapped <transition appear>
|
// wrapped <transition appear>
|
||||||
|
|
|
@ -40,11 +40,8 @@ export interface SchedulerJob extends Function {
|
||||||
|
|
||||||
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
export type SchedulerJobs = SchedulerJob | SchedulerJob[]
|
||||||
|
|
||||||
let isFlushing = false
|
|
||||||
let isFlushPending = false
|
|
||||||
|
|
||||||
const queue: SchedulerJob[] = []
|
const queue: SchedulerJob[] = []
|
||||||
let flushIndex = 0
|
let flushIndex = -1
|
||||||
|
|
||||||
const pendingPostFlushCbs: SchedulerJob[] = []
|
const pendingPostFlushCbs: SchedulerJob[] = []
|
||||||
let activePostFlushCbs: SchedulerJob[] | null = null
|
let activePostFlushCbs: SchedulerJob[] | null = null
|
||||||
|
@ -74,7 +71,7 @@ export function nextTick<T = void, R = void>(
|
||||||
// watcher should be inserted immediately before the update job. This allows
|
// watcher should be inserted immediately before the update job. This allows
|
||||||
// watchers to be skipped if the component is unmounted by the parent update.
|
// watchers to be skipped if the component is unmounted by the parent update.
|
||||||
function findInsertionIndex(id: number) {
|
function findInsertionIndex(id: number) {
|
||||||
let start = isFlushing ? flushIndex + 1 : 0
|
let start = flushIndex + 1
|
||||||
let end = queue.length
|
let end = queue.length
|
||||||
|
|
||||||
while (start < end) {
|
while (start < end) {
|
||||||
|
@ -115,8 +112,7 @@ export function queueJob(job: SchedulerJob): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function queueFlush() {
|
function queueFlush() {
|
||||||
if (!isFlushing && !isFlushPending) {
|
if (!currentFlushPromise) {
|
||||||
isFlushPending = true
|
|
||||||
currentFlushPromise = resolvedPromise.then(flushJobs)
|
currentFlushPromise = resolvedPromise.then(flushJobs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,8 +137,8 @@ export function queuePostFlushCb(cb: SchedulerJobs): void {
|
||||||
export function flushPreFlushCbs(
|
export function flushPreFlushCbs(
|
||||||
instance?: ComponentInternalInstance,
|
instance?: ComponentInternalInstance,
|
||||||
seen?: CountMap,
|
seen?: CountMap,
|
||||||
// if currently flushing, skip the current job itself
|
// skip the current job
|
||||||
i: number = isFlushing ? flushIndex + 1 : 0,
|
i: number = flushIndex + 1,
|
||||||
): void {
|
): void {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
seen = seen || new Map()
|
seen = seen || new Map()
|
||||||
|
@ -211,8 +207,6 @@ const getId = (job: SchedulerJob): number =>
|
||||||
job.id == null ? (job.flags! & SchedulerJobFlags.PRE ? -1 : Infinity) : job.id
|
job.id == null ? (job.flags! & SchedulerJobFlags.PRE ? -1 : Infinity) : job.id
|
||||||
|
|
||||||
function flushJobs(seen?: CountMap) {
|
function flushJobs(seen?: CountMap) {
|
||||||
isFlushPending = false
|
|
||||||
isFlushing = true
|
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
seen = seen || new Map()
|
seen = seen || new Map()
|
||||||
}
|
}
|
||||||
|
@ -255,15 +249,13 @@ function flushJobs(seen?: CountMap) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flushIndex = 0
|
flushIndex = -1
|
||||||
queue.length = 0
|
queue.length = 0
|
||||||
|
|
||||||
flushPostFlushCbs(seen)
|
flushPostFlushCbs(seen)
|
||||||
|
|
||||||
isFlushing = false
|
|
||||||
currentFlushPromise = null
|
currentFlushPromise = null
|
||||||
// some postFlushCb queued jobs!
|
// If new jobs have been added to either queue, keep flushing
|
||||||
// keep flushing until it drains.
|
|
||||||
if (queue.length || pendingPostFlushCbs.length) {
|
if (queue.length || pendingPostFlushCbs.length) {
|
||||||
flushJobs(seen)
|
flushJobs(seen)
|
||||||
}
|
}
|
||||||
|
|
|
@ -793,7 +793,7 @@ export function normalizeVNode(child: VNodeChild): VNode {
|
||||||
// #3666, avoid reference pollution when reusing vnode
|
// #3666, avoid reference pollution when reusing vnode
|
||||||
child.slice(),
|
child.slice(),
|
||||||
)
|
)
|
||||||
} else if (typeof child === 'object') {
|
} else if (isVNode(child)) {
|
||||||
// already vnode, this should be the most common since compiled templates
|
// already vnode, this should be the most common since compiled templates
|
||||||
// always produce all-vnode children arrays
|
// always produce all-vnode children arrays
|
||||||
return cloneIfMounted(child)
|
return cloneIfMounted(child)
|
||||||
|
|
|
@ -223,6 +223,21 @@ describe('defineCustomElement', () => {
|
||||||
expect(e.getAttribute('baz-qux')).toBe('four')
|
expect(e.getAttribute('baz-qux')).toBe('four')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('props via hyphen property', async () => {
|
||||||
|
const Comp = defineCustomElement({
|
||||||
|
props: {
|
||||||
|
fooBar: Boolean,
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return 'Comp'
|
||||||
|
},
|
||||||
|
})
|
||||||
|
customElements.define('my-el-comp', Comp)
|
||||||
|
render(h('my-el-comp', { 'foo-bar': true }), container)
|
||||||
|
const el = container.children[0]
|
||||||
|
expect((el as any).outerHTML).toBe('<my-el-comp foo-bar=""></my-el-comp>')
|
||||||
|
})
|
||||||
|
|
||||||
test('attribute -> prop type casting', async () => {
|
test('attribute -> prop type casting', async () => {
|
||||||
const E = defineCustomElement({
|
const E = defineCustomElement({
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-dom",
|
"name": "@vue/runtime-dom",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -160,7 +160,7 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
||||||
|
|
||||||
function setChecked(
|
function setChecked(
|
||||||
el: HTMLInputElement,
|
el: HTMLInputElement,
|
||||||
{ value, oldValue }: DirectiveBinding,
|
{ value }: DirectiveBinding,
|
||||||
vnode: VNode,
|
vnode: VNode,
|
||||||
) {
|
) {
|
||||||
// store the v-model value on the element so it can be accessed by the
|
// store the v-model value on the element so it can be accessed by the
|
||||||
|
@ -225,20 +225,20 @@ export const vModelSelect: ModelDirective<HTMLSelectElement, 'number'> = {
|
||||||
},
|
},
|
||||||
// set value in mounted & updated because <select> relies on its children
|
// set value in mounted & updated because <select> relies on its children
|
||||||
// <option>s.
|
// <option>s.
|
||||||
mounted(el, { value, modifiers: { number } }) {
|
mounted(el, { value }) {
|
||||||
setSelected(el, value, number)
|
setSelected(el, value)
|
||||||
},
|
},
|
||||||
beforeUpdate(el, _binding, vnode) {
|
beforeUpdate(el, _binding, vnode) {
|
||||||
el[assignKey] = getModelAssigner(vnode)
|
el[assignKey] = getModelAssigner(vnode)
|
||||||
},
|
},
|
||||||
updated(el, { value, modifiers: { number } }) {
|
updated(el, { value }) {
|
||||||
if (!el._assigning) {
|
if (!el._assigning) {
|
||||||
setSelected(el, value, number)
|
setSelected(el, value)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
function setSelected(el: HTMLSelectElement, value: any, number: boolean) {
|
function setSelected(el: HTMLSelectElement, value: any) {
|
||||||
const isMultiple = el.multiple
|
const isMultiple = el.multiple
|
||||||
const isArrayValue = isArray(value)
|
const isArrayValue = isArray(value)
|
||||||
if (isMultiple && !isArrayValue && !isSet(value)) {
|
if (isMultiple && !isArrayValue && !isSet(value)) {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import { patchAttr } from './modules/attrs'
|
||||||
import { patchDOMProp } from './modules/props'
|
import { patchDOMProp } from './modules/props'
|
||||||
import { patchEvent } from './modules/events'
|
import { patchEvent } from './modules/events'
|
||||||
import {
|
import {
|
||||||
|
camelize,
|
||||||
isFunction,
|
isFunction,
|
||||||
isModelListener,
|
isModelListener,
|
||||||
isNativeOn,
|
isNativeOn,
|
||||||
|
@ -50,6 +51,12 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
||||||
) {
|
) {
|
||||||
patchAttr(el, key, nextValue, isSVG, parentComponent, key !== 'value')
|
patchAttr(el, key, nextValue, isSVG, parentComponent, key !== 'value')
|
||||||
}
|
}
|
||||||
|
} else if (
|
||||||
|
// #11081 force set props for possible async custom element
|
||||||
|
(el as VueElement)._isVueCE &&
|
||||||
|
(/[A-Z]/.test(key) || !isString(nextValue))
|
||||||
|
) {
|
||||||
|
patchDOMProp(el, camelize(key), nextValue, parentComponent)
|
||||||
} else {
|
} else {
|
||||||
// special case for <input v-model type="checkbox"> with
|
// special case for <input v-model type="checkbox"> with
|
||||||
// :true-value & :false-value
|
// :true-value & :false-value
|
||||||
|
@ -127,14 +134,5 @@ function shouldSetAsProp(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (key in el) {
|
return key in el
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// #11081 force set props for possible async custom element
|
|
||||||
if ((el as VueElement)._isVueCE && (/[A-Z]/.test(key) || !isString(value))) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/server-renderer",
|
"name": "@vue/server-renderer",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/shared",
|
"name": "@vue/shared",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -218,3 +218,12 @@ export function genPropsAccessExp(name: string): string {
|
||||||
? `__props.${name}`
|
? `__props.${name}`
|
||||||
: `__props[${JSON.stringify(name)}]`
|
: `__props[${JSON.stringify(name)}]`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function genCacheKey(source: string, options: any): string {
|
||||||
|
return (
|
||||||
|
source +
|
||||||
|
JSON.stringify(options, (_, val) =>
|
||||||
|
typeof val === 'function' ? val.toString() : val,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -13,6 +13,12 @@ export type LooseRequired<T> = { [P in keyof (T & Required<T>)]: T[P] }
|
||||||
// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
|
// https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360
|
||||||
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N
|
export type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N
|
||||||
|
|
||||||
|
export type IsKeyValues<T, K = string> = IfAny<
|
||||||
|
T,
|
||||||
|
false,
|
||||||
|
T extends object ? (keyof T extends K ? true : false) : false
|
||||||
|
>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility for extracting the parameters from a function overload (for typed emits)
|
* Utility for extracting the parameters from a function overload (for typed emits)
|
||||||
* https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
|
* https://github.com/microsoft/TypeScript/issues/32164#issuecomment-1146737709
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compat",
|
"name": "@vue/compat",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -12,7 +12,13 @@ import {
|
||||||
registerRuntimeCompiler,
|
registerRuntimeCompiler,
|
||||||
warn,
|
warn,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { NOOP, extend, generateCodeFrame, isString } from '@vue/shared'
|
import {
|
||||||
|
NOOP,
|
||||||
|
extend,
|
||||||
|
genCacheKey,
|
||||||
|
generateCodeFrame,
|
||||||
|
isString,
|
||||||
|
} from '@vue/shared'
|
||||||
import type { InternalRenderFunction } from 'packages/runtime-core/src/component'
|
import type { InternalRenderFunction } from 'packages/runtime-core/src/component'
|
||||||
import * as runtimeDom from '@vue/runtime-dom'
|
import * as runtimeDom from '@vue/runtime-dom'
|
||||||
import {
|
import {
|
||||||
|
@ -35,7 +41,7 @@ function compileToFunction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = template
|
const key = genCacheKey(template, options)
|
||||||
const cached = compileCache[key]
|
const cached = compileCache[key]
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached
|
return cached
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "vue",
|
"name": "vue",
|
||||||
"version": "3.5.7",
|
"version": "3.5.11",
|
||||||
"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",
|
||||||
|
|
|
@ -13,9 +13,9 @@ import {
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import * as runtimeDom from '@vue/runtime-dom'
|
import * as runtimeDom from '@vue/runtime-dom'
|
||||||
import {
|
import {
|
||||||
EMPTY_OBJ,
|
|
||||||
NOOP,
|
NOOP,
|
||||||
extend,
|
extend,
|
||||||
|
genCacheKey,
|
||||||
generateCodeFrame,
|
generateCodeFrame,
|
||||||
isString,
|
isString,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
@ -25,19 +25,7 @@ if (__DEV__) {
|
||||||
initDev()
|
initDev()
|
||||||
}
|
}
|
||||||
|
|
||||||
const compileCache = new WeakMap<
|
const compileCache: Record<string, RenderFunction> = Object.create(null)
|
||||||
CompilerOptions,
|
|
||||||
Record<string, RenderFunction>
|
|
||||||
>()
|
|
||||||
|
|
||||||
function getCache(options?: CompilerOptions) {
|
|
||||||
let c = compileCache.get(options ?? EMPTY_OBJ)
|
|
||||||
if (!c) {
|
|
||||||
c = Object.create(null) as Record<string, RenderFunction>
|
|
||||||
compileCache.set(options ?? EMPTY_OBJ, c)
|
|
||||||
}
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
function compileToFunction(
|
function compileToFunction(
|
||||||
template: string | HTMLElement,
|
template: string | HTMLElement,
|
||||||
|
@ -52,9 +40,8 @@ function compileToFunction(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const key = template
|
const key = genCacheKey(template, options)
|
||||||
const cache = getCache(options)
|
const cached = compileCache[key]
|
||||||
const cached = cache[key]
|
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached
|
return cached
|
||||||
}
|
}
|
||||||
|
@ -111,7 +98,7 @@ function compileToFunction(
|
||||||
// mark the function as runtime compiled
|
// mark the function as runtime compiled
|
||||||
;(render as InternalRenderFunction)._rc = true
|
;(render as InternalRenderFunction)._rc = true
|
||||||
|
|
||||||
return (cache[key] = render)
|
return (compileCache[key] = render)
|
||||||
}
|
}
|
||||||
|
|
||||||
registerRuntimeCompiler(compileToFunction)
|
registerRuntimeCompiler(compileToFunction)
|
||||||
|
|
1193
pnpm-lock.yaml
1193
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -306,12 +306,6 @@ async function main() {
|
||||||
if (args.publish) {
|
if (args.publish) {
|
||||||
await buildPackages()
|
await buildPackages()
|
||||||
await publishPackages(targetVersion)
|
await publishPackages(targetVersion)
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
pico.yellow(
|
|
||||||
'\nPublish step skipped (will be done in GitHub actions on successful push)',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// push to GitHub
|
// push to GitHub
|
||||||
|
@ -322,6 +316,15 @@ async function main() {
|
||||||
await runIfNotDry('git', ['push'])
|
await runIfNotDry('git', ['push'])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!args.publish) {
|
||||||
|
console.log(
|
||||||
|
pico.yellow(
|
||||||
|
'\nRelease will be done via GitHub Actions.\n' +
|
||||||
|
'Check status at https://github.com/vuejs/core/actions/workflows/release.yml',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (isDryRun) {
|
if (isDryRun) {
|
||||||
console.log(`\nDry run finished - run git diff to see package changes.`)
|
console.log(`\nDry run finished - run git diff to see package changes.`)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue