Merge branch 'main' into fix/8466
This commit is contained in:
commit
043712047d
|
@ -1,18 +1,17 @@
|
||||||
{
|
{
|
||||||
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||||
extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'],
|
extends: ['config:recommended', 'schedule:weekly', 'group:allNonMajor'],
|
||||||
labels: ['dependencies'],
|
labels: ['dependencies'],
|
||||||
ignorePaths: ['**/__tests__/**'],
|
ignorePaths: ['**/__tests__/**'],
|
||||||
rangeStrategy: 'bump',
|
rangeStrategy: 'bump',
|
||||||
packageRules: [
|
packageRules: [
|
||||||
{
|
{
|
||||||
depTypeList: ['peerDependencies'],
|
matchDepTypes: ['peerDependencies'],
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'test',
|
groupName: 'test',
|
||||||
matchPackageNames: ['vitest', 'jsdom', 'puppeteer'],
|
matchPackageNames: ['vitest', 'jsdom', 'puppeteer', '@vitest{/,}**'],
|
||||||
matchPackagePrefixes: ['@vitest'],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'playground',
|
groupName: 'playground',
|
||||||
|
@ -23,18 +22,28 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'compiler',
|
groupName: 'compiler',
|
||||||
matchPackageNames: ['magic-string'],
|
matchPackageNames: ['magic-string', '@babel{/,}**', 'postcss{/,}**'],
|
||||||
matchPackagePrefixes: ['@babel', 'postcss'],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'build',
|
groupName: 'build',
|
||||||
matchPackageNames: ['vite', '@swc/core'],
|
matchPackageNames: [
|
||||||
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
|
'vite',
|
||||||
|
'@swc/core',
|
||||||
|
'rollup{/,}**',
|
||||||
|
'esbuild{/,}**',
|
||||||
|
'@rollup{/,}**',
|
||||||
|
'@vitejs{/,}**',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'lint',
|
groupName: 'lint',
|
||||||
matchPackageNames: ['simple-git-hooks', 'lint-staged'],
|
matchPackageNames: [
|
||||||
matchPackagePrefixes: ['typescript-eslint', 'eslint', 'prettier'],
|
'simple-git-hooks',
|
||||||
|
'lint-staged',
|
||||||
|
'typescript-eslint{/,}**',
|
||||||
|
'eslint{/,}**',
|
||||||
|
'prettier{/,}**',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ignoreDeps: [
|
ignoreDeps: [
|
||||||
|
|
|
@ -31,4 +31,4 @@ jobs:
|
||||||
- name: Run prettier
|
- name: Run prettier
|
||||||
run: pnpm run format
|
run: pnpm run format
|
||||||
|
|
||||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||||
|
|
|
@ -37,7 +37,7 @@ jobs:
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download Size Data
|
- name: Download Size Data
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: size-data
|
name: size-data
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
|
@ -56,7 +56,7 @@ jobs:
|
||||||
path: temp/size/base.txt
|
path: temp/size/base.txt
|
||||||
|
|
||||||
- name: Download Previous Size Data
|
- name: Download Previous Size Data
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v7
|
||||||
with:
|
with:
|
||||||
branch: ${{ steps.pr-base.outputs.content }}
|
branch: ${{ steps.pr-base.outputs.content }}
|
||||||
workflow: size-data.yml
|
workflow: size-data.yml
|
||||||
|
|
|
@ -13,5 +13,6 @@
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
},
|
||||||
|
"editor.formatOnSave": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
https://vuejs.org/funding.json
|
64
CHANGELOG.md
64
CHANGELOG.md
|
@ -1,3 +1,67 @@
|
||||||
|
## [3.5.13](https://github.com/vuejs/core/compare/v3.5.12...v3.5.13) (2024-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-core:** handle v-memo + v-for with functional key ([#12014](https://github.com/vuejs/core/issues/12014)) ([99009ee](https://github.com/vuejs/core/commit/99009eee0efc238392daba93792d478525b21afa)), closes [#12013](https://github.com/vuejs/core/issues/12013)
|
||||||
|
* **compiler-dom:** properly stringify template string style ([#12392](https://github.com/vuejs/core/issues/12392)) ([2d78539](https://github.com/vuejs/core/commit/2d78539da35322aea5f821b3cf9b02d006abac72)), closes [#12391](https://github.com/vuejs/core/issues/12391)
|
||||||
|
* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
|
||||||
|
* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
|
||||||
|
* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
|
||||||
|
* **reactiivty:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
|
||||||
|
* **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370)
|
||||||
|
* **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533)
|
||||||
|
* **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6))
|
||||||
|
* **ssr:** avoid updating subtree of async component if it is resolved ([#12363](https://github.com/vuejs/core/issues/12363)) ([da7ad5e](https://github.com/vuejs/core/commit/da7ad5e3d24f3e108401188d909d27a4910da095)), closes [#12362](https://github.com/vuejs/core/issues/12362)
|
||||||
|
* **ssr:** ensure v-text updates correctly with custom directives in SSR output ([#12311](https://github.com/vuejs/core/issues/12311)) ([1f75d4e](https://github.com/vuejs/core/commit/1f75d4e6dfe18121ebe443cd3e8105d54f727893)), closes [#12309](https://github.com/vuejs/core/issues/12309)
|
||||||
|
* **ssr:** handle initial selected state for select with v-model + v-for option ([#12399](https://github.com/vuejs/core/issues/12399)) ([4f8d807](https://github.com/vuejs/core/commit/4f8d8078221ee52deed266677a227ad2a6d8dd22)), closes [#12395](https://github.com/vuejs/core/issues/12395)
|
||||||
|
* **teleport:** handle deferred teleport update before mounted ([#12168](https://github.com/vuejs/core/issues/12168)) ([8bff142](https://github.com/vuejs/core/commit/8bff142f99b646e9dd15897ec75368fbf34f1534)), closes [#12161](https://github.com/vuejs/core/issues/12161)
|
||||||
|
* **templateRef:** set ref on cached async component which wrapped in KeepAlive ([#12290](https://github.com/vuejs/core/issues/12290)) ([983eb50](https://github.com/vuejs/core/commit/983eb50a17eac76f1bba4394ad0316c62b72191d)), closes [#4999](https://github.com/vuejs/core/issues/4999) [#5004](https://github.com/vuejs/core/issues/5004)
|
||||||
|
* **test:** update snapshot ([#12169](https://github.com/vuejs/core/issues/12169)) ([828d4a4](https://github.com/vuejs/core/commit/828d4a443919fa2aa4e2e92fbd03a5f04b258eea))
|
||||||
|
* **Transition:** fix transition memory leak edge case ([#12182](https://github.com/vuejs/core/issues/12182)) ([660132d](https://github.com/vuejs/core/commit/660132df6c6a8c14bf75e593dc47d2fdada30322)), closes [#12181](https://github.com/vuejs/core/issues/12181)
|
||||||
|
* **transition:** reflow before leave-active class after leave-from ([#12288](https://github.com/vuejs/core/issues/12288)) ([4b479db](https://github.com/vuejs/core/commit/4b479db61d233b054561402ae94ef08550073ea1)), closes [#2593](https://github.com/vuejs/core/issues/2593)
|
||||||
|
* **types:** defineEmits w/ interface declaration ([#12343](https://github.com/vuejs/core/issues/12343)) ([1022eab](https://github.com/vuejs/core/commit/1022eabaa1aaf8436876f5ec5573cb1e4b3959a6)), closes [#8457](https://github.com/vuejs/core/issues/8457)
|
||||||
|
* **v-once:** setting hasOnce to current block only when in v-once ([#12374](https://github.com/vuejs/core/issues/12374)) ([37300fc](https://github.com/vuejs/core/commit/37300fc26190a7299efddbf98800ffd96d5cad96)), closes [#12371](https://github.com/vuejs/core/issues/12371)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **reactivity:** do not track inner key `__v_skip`` ([#11690](https://github.com/vuejs/core/issues/11690)) ([d637bd6](https://github.com/vuejs/core/commit/d637bd6c0164c2883e6eabd3c2f1f8c258dedfb1))
|
||||||
|
* **runtime-core:** use feature flag for call to resolveMergedOptions ([#12163](https://github.com/vuejs/core/issues/12163)) ([1755ac0](https://github.com/vuejs/core/commit/1755ac0a108ba3486bd8397e56d3bdcd69196594))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.5.12](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12) (2024-10-11)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-dom:** avoid stringify option with null value ([#12096](https://github.com/vuejs/core/issues/12096)) ([f6d9926](https://github.com/vuejs/core/commit/f6d99262364b7444ebab8742158599e8cdd79eaa)), closes [#12093](https://github.com/vuejs/core/issues/12093)
|
||||||
|
* **compiler-sfc:** do not skip TSInstantiationExpression when transforming props destructure ([#12064](https://github.com/vuejs/core/issues/12064)) ([d3ecde8](https://github.com/vuejs/core/commit/d3ecde8a696ff62c8d0ab067fd1d7ee0565b63c5))
|
||||||
|
* **compiler-sfc:** use sass modern api if available and avoid deprecation warning ([#11992](https://github.com/vuejs/core/issues/11992)) ([4474c11](https://github.com/vuejs/core/commit/4474c113d1fb1c26298dd6794275d5b5c7cc4d93))
|
||||||
|
* **compiler:** clone loc to `ifNode` ([#12131](https://github.com/vuejs/core/issues/12131)) ([cde2c06](https://github.com/vuejs/core/commit/cde2c0671b00d4f6111fcbd7aa76e45872f20b0c)), closes [vuejs/language-tools#4911](https://github.com/vuejs/language-tools/issues/4911)
|
||||||
|
* **custom-element:** properly remove hyphenated attribute ([#12143](https://github.com/vuejs/core/issues/12143)) ([e16e9a7](https://github.com/vuejs/core/commit/e16e9a7341e7cfb3c443da4e5e5b06e8158712c3)), closes [#12139](https://github.com/vuejs/core/issues/12139)
|
||||||
|
* **defineModel:** handle kebab-case model correctly ([#12063](https://github.com/vuejs/core/issues/12063)) ([c0418a3](https://github.com/vuejs/core/commit/c0418a3b8fa96a0b108ab71b7aab5d3388f90557)), closes [#12060](https://github.com/vuejs/core/issues/12060)
|
||||||
|
* **deps:** update dependency monaco-editor to ^0.52.0 ([#12119](https://github.com/vuejs/core/issues/12119)) ([f7cbea2](https://github.com/vuejs/core/commit/f7cbea2111c7770a180b640f36f6a5d4d6abc698))
|
||||||
|
* **hydration:** provide compat fallback for idle callback hydration strategy ([#11935](https://github.com/vuejs/core/issues/11935)) ([1ae545a](https://github.com/vuejs/core/commit/1ae545a3786abef983be1c969726489685569c92))
|
||||||
|
* **reactivity:** trigger reactivity for Map key `undefined` ([#12055](https://github.com/vuejs/core/issues/12055)) ([7ad289e](https://github.com/vuejs/core/commit/7ad289e1e7fea654524008ff91e43a8b8a55ef22)), closes [#12054](https://github.com/vuejs/core/issues/12054)
|
||||||
|
* **runtime-core:** allow symbol values for slot prop key ([#12069](https://github.com/vuejs/core/issues/12069)) ([d9d4d4e](https://github.com/vuejs/core/commit/d9d4d4e158cd51a9ddda249f29de8467f60b2792)), closes [#12068](https://github.com/vuejs/core/issues/12068)
|
||||||
|
* **runtime-core:** fix required prop check false positive for kebab-case edge cases ([#12034](https://github.com/vuejs/core/issues/12034)) ([9da1ac1](https://github.com/vuejs/core/commit/9da1ac156552ac449754e1373aac7e349841becb)), closes [#12011](https://github.com/vuejs/core/issues/12011)
|
||||||
|
* **runtime-dom:** prevent unnecessary updates in v-model checkbox when value is unchanged ([#12146](https://github.com/vuejs/core/issues/12146)) ([ea943af](https://github.com/vuejs/core/commit/ea943afe404c4ca4b729906c5e8daf7aa2ccde9b)), closes [#12144](https://github.com/vuejs/core/issues/12144)
|
||||||
|
* **teleport:** handle disabled teleport with updateCssVars ([#12113](https://github.com/vuejs/core/issues/12113)) ([76a8223](https://github.com/vuejs/core/commit/76a8223199c148b79a5c0ea19e235164809760cd)), closes [#12112](https://github.com/vuejs/core/issues/12112)
|
||||||
|
* **transition/ssr:** make transition appear work with Suspense in SSR ([#12047](https://github.com/vuejs/core/issues/12047)) ([f1a4f67](https://github.com/vuejs/core/commit/f1a4f67aedfe83e440c54222213f070774faa421)), closes [#12046](https://github.com/vuejs/core/issues/12046)
|
||||||
|
* **types:** ensure `this.$props` type does not include `string` ([#12123](https://github.com/vuejs/core/issues/12123)) ([704173e](https://github.com/vuejs/core/commit/704173e24276706de672cca6c9507e4dd9651197)), closes [#12122](https://github.com/vuejs/core/issues/12122)
|
||||||
|
* **types:** retain union type narrowing with defaults applied ([#12108](https://github.com/vuejs/core/issues/12108)) ([05685a9](https://github.com/vuejs/core/commit/05685a9d7c42d4cd37169b867833776b91154fed)), closes [#12106](https://github.com/vuejs/core/issues/12106)
|
||||||
|
* **useId:** ensure useId consistency when using serverPrefetch ([#12128](https://github.com/vuejs/core/issues/12128)) ([b4d3534](https://github.com/vuejs/core/commit/b4d35349d8bc39aa15bd3f1094d230e5928b177c)), closes [#12102](https://github.com/vuejs/core/issues/12102)
|
||||||
|
* **watch:** watchEffect clean-up with SSR ([#12097](https://github.com/vuejs/core/issues/12097)) ([b094c72](https://github.com/vuejs/core/commit/b094c72b3d40c52c7124f145a9db028509a11202)), closes [#11956](https://github.com/vuejs/core/issues/11956)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **reactivity:** avoid unnecessary recursion in removeSub ([#12135](https://github.com/vuejs/core/issues/12135)) ([ec917cf](https://github.com/vuejs/core/commit/ec917cfdb9d0169cd0835d3a0e28244242657dc9))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.5.11](https://github.com/vuejs/core/compare/v3.5.10...v3.5.11) (2024-10-03)
|
## [3.5.11](https://github.com/vuejs/core/compare/v3.5.10...v3.5.11) (2024-10-03)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -741,17 +741,6 @@ Note that this is a type-only breaking change in a minor release, which adheres
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.13](https://github.com/vuejs/core/compare/v3.3.12...v3.3.13) (2023-12-19)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-core:** fix v-on with modifiers on inline expression of undefined ([#9866](https://github.com/vuejs/core/issues/9866)) ([bae79dd](https://github.com/vuejs/core/commit/bae79ddf8564a2da4a5365cfeb8d811990f42335)), closes [#9865](https://github.com/vuejs/core/issues/9865)
|
|
||||||
* **runtime-dom:** cache event handlers by key/modifiers ([#9851](https://github.com/vuejs/core/issues/9851)) ([04d2c05](https://github.com/vuejs/core/commit/04d2c05054c26b02fbc1d84839b0ed5cd36455b6)), closes [#9849](https://github.com/vuejs/core/issues/9849)
|
|
||||||
* **types:** extract properties from extended collections ([#9854](https://github.com/vuejs/core/issues/9854)) ([24b1c1d](https://github.com/vuejs/core/commit/24b1c1dd57fd55d998aa231a147500e010b10219)), closes [#9852](https://github.com/vuejs/core/issues/9852)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-beta.3](https://github.com/vuejs/core/compare/v3.3.12...v3.4.0-beta.3) (2023-12-16)
|
# [3.4.0-beta.3](https://github.com/vuejs/core/compare/v3.3.12...v3.4.0-beta.3) (2023-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
@ -764,19 +753,6 @@ Note that this is a type-only breaking change in a minor release, which adheres
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.12](https://github.com/vuejs/core/compare/v3.3.11...v3.3.12) (2023-12-16)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **hydration:** handle appear transition before patch props ([#9837](https://github.com/vuejs/core/issues/9837)) ([e70f4c4](https://github.com/vuejs/core/commit/e70f4c47c553b6e16d8fad70743271ca23802fe7)), closes [#9832](https://github.com/vuejs/core/issues/9832)
|
|
||||||
* **sfc/cssVars:** fix loss of CSS v-bind variables when setting inline style with string value ([#9824](https://github.com/vuejs/core/issues/9824)) ([0a387df](https://github.com/vuejs/core/commit/0a387dfb1d04afb6eae4296b6da76dfdaca77af4)), closes [#9821](https://github.com/vuejs/core/issues/9821)
|
|
||||||
* **ssr:** fix suspense hydration of fallback content ([#7188](https://github.com/vuejs/core/issues/7188)) ([60415b5](https://github.com/vuejs/core/commit/60415b5d67df55f1fd6b176615299c08640fa142))
|
|
||||||
* **types:** add `xmlns:xlink` to `SVGAttributes` ([#9300](https://github.com/vuejs/core/issues/9300)) ([0d61b42](https://github.com/vuejs/core/commit/0d61b429ecf63591d31e09702058fa4c7132e1a7)), closes [#9299](https://github.com/vuejs/core/issues/9299)
|
|
||||||
* **types:** fix `shallowRef` type error ([#9839](https://github.com/vuejs/core/issues/9839)) ([9a57158](https://github.com/vuejs/core/commit/9a571582b53220270e498d8712ea59312c0bef3a))
|
|
||||||
* **types:** support for generic keyof slots ([#8374](https://github.com/vuejs/core/issues/8374)) ([213eba4](https://github.com/vuejs/core/commit/213eba479ce080efc1053fe636f6be4a4c889b44))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-beta.2](https://github.com/vuejs/core/compare/v3.4.0-beta.1...v3.4.0-beta.2) (2023-12-14)
|
# [3.4.0-beta.2](https://github.com/vuejs/core/compare/v3.4.0-beta.1...v3.4.0-beta.2) (2023-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
@ -836,22 +812,6 @@ default.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.11](https://github.com/vuejs/core/compare/v3.3.10...v3.3.11) (2023-12-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **custom-element:** correctly handle number type props in prod ([#8989](https://github.com/vuejs/core/issues/8989)) ([d74d364](https://github.com/vuejs/core/commit/d74d364d62db8e48881af6b5a75ce4fb5f36cc35))
|
|
||||||
* **reactivity:** fix mutation on user proxy of reactive Array ([6ecbd5c](https://github.com/vuejs/core/commit/6ecbd5ce2a7f59314a8326a1d193874b87f4d8c8)), closes [#9742](https://github.com/vuejs/core/issues/9742) [#9751](https://github.com/vuejs/core/issues/9751) [#9750](https://github.com/vuejs/core/issues/9750)
|
|
||||||
* **runtime-dom:** fix width and height prop check condition ([5b00286](https://github.com/vuejs/core/commit/5b002869c533220706f9788b496b8ca8d8e98609)), closes [#9762](https://github.com/vuejs/core/issues/9762)
|
|
||||||
* **shared:** handle Map with symbol keys in toDisplayString ([#9731](https://github.com/vuejs/core/issues/9731)) ([364821d](https://github.com/vuejs/core/commit/364821d6bdb1775e2f55a69bcfb9f40f7acf1506)), closes [#9727](https://github.com/vuejs/core/issues/9727)
|
|
||||||
* **shared:** handle more Symbol cases in toDisplayString ([983d45d](https://github.com/vuejs/core/commit/983d45d4f8eb766b5a16b7ea93b86d3c51618fa6))
|
|
||||||
* **Suspense:** properly get anchor when mount fallback vnode ([#9770](https://github.com/vuejs/core/issues/9770)) ([b700328](https://github.com/vuejs/core/commit/b700328342e17dc16b19316c2e134a26107139d2)), closes [#9769](https://github.com/vuejs/core/issues/9769)
|
|
||||||
* **types:** ref() return type should not be any when initial value is any ([#9768](https://github.com/vuejs/core/issues/9768)) ([cdac121](https://github.com/vuejs/core/commit/cdac12161ec27b45ded48854c3d749664b6d4a6d))
|
|
||||||
* **watch:** should not fire pre watcher on child component unmount ([#7181](https://github.com/vuejs/core/issues/7181)) ([6784f0b](https://github.com/vuejs/core/commit/6784f0b1f8501746ea70d87d18ed63a62cf6b76d)), closes [#7030](https://github.com/vuejs/core/issues/7030)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.10...v3.4.0-alpha.4) (2023-12-04)
|
# [3.4.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.10...v3.4.0-alpha.4) (2023-12-04)
|
||||||
|
|
||||||
|
|
||||||
|
@ -873,37 +833,6 @@ default.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.10](https://github.com/vuejs/core/compare/v3.3.9...v3.3.10) (2023-12-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **app:** prevent template from being cached between apps with different options ([#9724](https://github.com/vuejs/core/issues/9724)) ([ec71585](https://github.com/vuejs/core/commit/ec715854ca12520b2afc9e9b3981cbae05ae5206)), closes [#9618](https://github.com/vuejs/core/issues/9618)
|
|
||||||
* **compiler-sfc:** avoid passing forEach index to genMap ([f12db7f](https://github.com/vuejs/core/commit/f12db7fb564a534cef2e5805cc9f54afe5d72fbf))
|
|
||||||
* **compiler-sfc:** deindent pug/jade templates ([6345197](https://github.com/vuejs/core/commit/634519720a21fb5a6871454e1cadad7053a568b8)), closes [#3231](https://github.com/vuejs/core/issues/3231) [#3842](https://github.com/vuejs/core/issues/3842) [#7723](https://github.com/vuejs/core/issues/7723)
|
|
||||||
* **compiler-sfc:** fix :where and :is selector in scoped mode with multiple selectors ([#9735](https://github.com/vuejs/core/issues/9735)) ([c3e2c55](https://github.com/vuejs/core/commit/c3e2c556b532656b50b8ab5cd2d9eabc26622d63)), closes [#9707](https://github.com/vuejs/core/issues/9707)
|
|
||||||
* **compiler-sfc:** generate more treeshaking friendly code ([#9507](https://github.com/vuejs/core/issues/9507)) ([8d74ca0](https://github.com/vuejs/core/commit/8d74ca0e6fa2738ca6854b7e879ff59419f948c7)), closes [#9500](https://github.com/vuejs/core/issues/9500)
|
|
||||||
* **compiler-sfc:** support inferring generic types ([#8511](https://github.com/vuejs/core/issues/8511)) ([eb5e307](https://github.com/vuejs/core/commit/eb5e307c0be62002e62c4c800d0dfacb39b0d4ca)), closes [#8482](https://github.com/vuejs/core/issues/8482)
|
|
||||||
* **compiler-sfc:** support resolving components from props ([#8785](https://github.com/vuejs/core/issues/8785)) ([7cbcee3](https://github.com/vuejs/core/commit/7cbcee3d831241a8bd3588ae92d3f27e3641e25f))
|
|
||||||
* **compiler-sfc:** throw error when failing to load TS during type resolution ([#8883](https://github.com/vuejs/core/issues/8883)) ([4936d2e](https://github.com/vuejs/core/commit/4936d2e11a8d0ca3704bfe408548cb26bb3fd5e9))
|
|
||||||
* **cssVars:** cssVar names should be double-escaped when generating code for ssr ([#8824](https://github.com/vuejs/core/issues/8824)) ([5199a12](https://github.com/vuejs/core/commit/5199a12f8855cd06f24bf355708b5a2134f63176)), closes [#7823](https://github.com/vuejs/core/issues/7823)
|
|
||||||
* **deps:** update compiler to ^7.23.4 ([#9681](https://github.com/vuejs/core/issues/9681)) ([31f6ebc](https://github.com/vuejs/core/commit/31f6ebc4df84490ed29fb75e7bf4259200eb51f0))
|
|
||||||
* **runtime-core:** Suspense get anchor properly in Transition ([#9309](https://github.com/vuejs/core/issues/9309)) ([65f3fe2](https://github.com/vuejs/core/commit/65f3fe273127a8b68e1222fbb306d28d85f01757)), closes [#8105](https://github.com/vuejs/core/issues/8105)
|
|
||||||
* **runtime-dom:** set width/height with units as attribute ([#8781](https://github.com/vuejs/core/issues/8781)) ([bfc1838](https://github.com/vuejs/core/commit/bfc1838f31199de3f189198a3c234fa7bae91386))
|
|
||||||
* **ssr:** avoid computed being accidentally cached before server render ([#9688](https://github.com/vuejs/core/issues/9688)) ([30d5d93](https://github.com/vuejs/core/commit/30d5d93a92b2154406ec04f8aca6b217fa01177c)), closes [#5300](https://github.com/vuejs/core/issues/5300)
|
|
||||||
* **types:** expose emits as props in functional components ([#9234](https://github.com/vuejs/core/issues/9234)) ([887e54c](https://github.com/vuejs/core/commit/887e54c347ea9eac4c721b5e2288f054873d1d30))
|
|
||||||
* **types:** fix reactive collection types ([#8960](https://github.com/vuejs/core/issues/8960)) ([ad27473](https://github.com/vuejs/core/commit/ad274737015c36906d76f3189203093fa3a2e4e7)), closes [#8904](https://github.com/vuejs/core/issues/8904)
|
|
||||||
* **types:** improve return type withKeys and withModifiers ([#9734](https://github.com/vuejs/core/issues/9734)) ([43c3cfd](https://github.com/vuejs/core/commit/43c3cfdec5ae5d70fa2a21e857abc2d73f1a0d07))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* optimize on* prop check ([38aaa8c](https://github.com/vuejs/core/commit/38aaa8c88648c54fe2616ad9c0961288092fcb44))
|
|
||||||
* **runtime-dom:** cache modifier wrapper functions ([da4a4fb](https://github.com/vuejs/core/commit/da4a4fb5e8eee3c6d31f24ebd79a9d0feca56cb2)), closes [#8882](https://github.com/vuejs/core/issues/8882)
|
|
||||||
* **v-on:** constant handlers with modifiers should not be treated as dynamic ([4d94ebf](https://github.com/vuejs/core/commit/4d94ebfe75174b340d2b794e699cad1add3600a9))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
||||||
|
|
||||||
|
|
||||||
|
@ -960,55 +889,6 @@ default.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.9](https://github.com/vuejs/core/compare/v3.3.8...v3.3.9) (2023-11-25)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-core:** avoid rewriting scope variables in inline for loops ([#7245](https://github.com/vuejs/core/issues/7245)) ([a2d810e](https://github.com/vuejs/core/commit/a2d810eb40cef631f61991ca68b426ee9546aba0)), closes [#7238](https://github.com/vuejs/core/issues/7238)
|
|
||||||
* **compiler-core:** fix `resolveParserPlugins` decorators check ([#9566](https://github.com/vuejs/core/issues/9566)) ([9d0eba9](https://github.com/vuejs/core/commit/9d0eba916f3bf6fb5c03222400edae1a2db7444f)), closes [#9560](https://github.com/vuejs/core/issues/9560)
|
|
||||||
* **compiler-sfc:** consistently escape type-only prop names ([#8654](https://github.com/vuejs/core/issues/8654)) ([3e08d24](https://github.com/vuejs/core/commit/3e08d246dfd8523c54fb8e7a4a6fd5506ffb1bcc)), closes [#8635](https://github.com/vuejs/core/issues/8635) [#8910](https://github.com/vuejs/core/issues/8910) [vitejs/vite-plugin-vue#184](https://github.com/vitejs/vite-plugin-vue/issues/184)
|
|
||||||
* **compiler-sfc:** malformed filename on windows using path.posix.join() ([#9478](https://github.com/vuejs/core/issues/9478)) ([f18a174](https://github.com/vuejs/core/commit/f18a174979626b3429db93c5d5b7ae5448917c70)), closes [#8671](https://github.com/vuejs/core/issues/8671) [#9583](https://github.com/vuejs/core/issues/9583) [#9446](https://github.com/vuejs/core/issues/9446) [#9473](https://github.com/vuejs/core/issues/9473)
|
|
||||||
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([3227e50](https://github.com/vuejs/core/commit/3227e50b32105f8893f7dff2f29278c5b3a9f621))
|
|
||||||
* **compiler-sfc:** support resolve extends interface for defineEmits ([#8470](https://github.com/vuejs/core/issues/8470)) ([9e1b74b](https://github.com/vuejs/core/commit/9e1b74bcd5fa4151f5d1bc02c69fbbfa4762f577)), closes [#8465](https://github.com/vuejs/core/issues/8465)
|
|
||||||
* **hmr/transition:** fix kept-alive component inside transition disappearing after hmr ([#7126](https://github.com/vuejs/core/issues/7126)) ([d11e978](https://github.com/vuejs/core/commit/d11e978fc98dcc83526c167e603b8308f317f786)), closes [#7121](https://github.com/vuejs/core/issues/7121)
|
|
||||||
* **hydration:** force hydration for v-bind with .prop modifier ([364f319](https://github.com/vuejs/core/commit/364f319d214226770d97c98d8fcada80c9e8dde3)), closes [#7490](https://github.com/vuejs/core/issues/7490)
|
|
||||||
* **hydration:** properly hydrate indeterminate prop ([34b5a5d](https://github.com/vuejs/core/commit/34b5a5da4ae9c9faccac237acd7acc8e7e017571)), closes [#7476](https://github.com/vuejs/core/issues/7476)
|
|
||||||
* **reactivity:** clear method on readonly collections should return undefined ([#7316](https://github.com/vuejs/core/issues/7316)) ([657476d](https://github.com/vuejs/core/commit/657476dcdb964be4fbb1277c215c073f3275728e))
|
|
||||||
* **reactivity:** onCleanup also needs to be cleaned ([#8655](https://github.com/vuejs/core/issues/8655)) ([73fd810](https://github.com/vuejs/core/commit/73fd810eebdd383a2b4629f67736c4db1f428abd)), closes [#5151](https://github.com/vuejs/core/issues/5151) [#7695](https://github.com/vuejs/core/issues/7695)
|
|
||||||
* **ssr:** hydration `__vnode` missing for devtools ([#9328](https://github.com/vuejs/core/issues/9328)) ([5156ac5](https://github.com/vuejs/core/commit/5156ac5b38cfa80d3db26f2c9bf40cb22a7521cb))
|
|
||||||
* **types:** allow falsy value types in `StyleValue` ([#7954](https://github.com/vuejs/core/issues/7954)) ([17aa92b](https://github.com/vuejs/core/commit/17aa92b79b31d8bb8b5873ddc599420cb9806db8)), closes [#7955](https://github.com/vuejs/core/issues/7955)
|
|
||||||
* **types:** defineCustomElement using defineComponent return type with emits ([#7937](https://github.com/vuejs/core/issues/7937)) ([5d932a8](https://github.com/vuejs/core/commit/5d932a8e6d14343c9d7fc7c2ecb58ac618b2f938)), closes [#7782](https://github.com/vuejs/core/issues/7782)
|
|
||||||
* **types:** fix `unref` and `toValue` when input union type contains ComputedRef ([#8748](https://github.com/vuejs/core/issues/8748)) ([176d476](https://github.com/vuejs/core/commit/176d47671271b1abc21b1508e9a493c7efca6451)), closes [#8747](https://github.com/vuejs/core/issues/8747) [#8857](https://github.com/vuejs/core/issues/8857)
|
|
||||||
* **types:** fix instance type when props type is incompatible with setup returned type ([#7338](https://github.com/vuejs/core/issues/7338)) ([0e1e8f9](https://github.com/vuejs/core/commit/0e1e8f919e5a74cdaadf9c80ee135088b25e7fa3)), closes [#5885](https://github.com/vuejs/core/issues/5885)
|
|
||||||
* **types:** fix shallowRef return type with union value type ([#7853](https://github.com/vuejs/core/issues/7853)) ([7c44800](https://github.com/vuejs/core/commit/7c448000b0def910c2cfabfdf7ff20a3d6bc844f)), closes [#7852](https://github.com/vuejs/core/issues/7852)
|
|
||||||
* **types:** more precise types for class bindings ([#8012](https://github.com/vuejs/core/issues/8012)) ([46e3374](https://github.com/vuejs/core/commit/46e33744c890bd49482c5e5c5cdea44e00ec84d5))
|
|
||||||
* **types:** remove optional properties from defineProps return type ([#6421](https://github.com/vuejs/core/issues/6421)) ([94c049d](https://github.com/vuejs/core/commit/94c049d930d922069e38ea8700d7ff0970f71e61)), closes [#6420](https://github.com/vuejs/core/issues/6420)
|
|
||||||
* **types:** return type of withDefaults should be readonly ([#8601](https://github.com/vuejs/core/issues/8601)) ([f15debc](https://github.com/vuejs/core/commit/f15debc01acb22d23f5acee97e6f02db88cef11a))
|
|
||||||
* **types:** revert class type restrictions ([5d077c8](https://github.com/vuejs/core/commit/5d077c8754cc14f85d2d6d386df70cf8c0d93842)), closes [#8012](https://github.com/vuejs/core/issues/8012)
|
|
||||||
* **types:** update jsx type definitions ([#8607](https://github.com/vuejs/core/issues/8607)) ([58e2a94](https://github.com/vuejs/core/commit/58e2a94871ae06a909c5f8bad07fb401193e6a38))
|
|
||||||
* **types:** widen ClassValue type ([2424013](https://github.com/vuejs/core/commit/242401305944422d0c361b16101a4d18908927af))
|
|
||||||
* **v-model:** avoid overwriting number input with same value ([#7004](https://github.com/vuejs/core/issues/7004)) ([40f4b77](https://github.com/vuejs/core/commit/40f4b77bb570868cb6e47791078767797e465989)), closes [#7003](https://github.com/vuejs/core/issues/7003)
|
|
||||||
* **v-model:** unnecessary value binding error should apply to dynamic instead of static binding ([2859b65](https://github.com/vuejs/core/commit/2859b653c9a22460e60233cac10fe139e359b046)), closes [#3596](https://github.com/vuejs/core/issues/3596)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.8](https://github.com/vuejs/core/compare/v3.3.7...v3.3.8) (2023-11-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compile-sfc:** support `Error` type in `defineProps` ([#5955](https://github.com/vuejs/core/issues/5955)) ([a989345](https://github.com/vuejs/core/commit/a9893458ec519aae442e1b99e64e6d74685cd22c))
|
|
||||||
* **compiler-core:** known global should be shadowed by local variables in expression rewrite ([#9492](https://github.com/vuejs/core/issues/9492)) ([a75d1c5](https://github.com/vuejs/core/commit/a75d1c5c6242e91a73cc5ba01e6da620dea0b3d9)), closes [#9482](https://github.com/vuejs/core/issues/9482)
|
|
||||||
* **compiler-sfc:** fix dynamic directive arguments usage check for slots ([#9495](https://github.com/vuejs/core/issues/9495)) ([b39fa1f](https://github.com/vuejs/core/commit/b39fa1f8157647859331ce439c42ae016a49b415)), closes [#9493](https://github.com/vuejs/core/issues/9493)
|
|
||||||
* **deps:** update dependency @vue/repl to ^2.6.2 ([#9536](https://github.com/vuejs/core/issues/9536)) ([5cef325](https://github.com/vuejs/core/commit/5cef325f41e3b38657c72fa1a38dedeee1c7a60a))
|
|
||||||
* **deps:** update dependency @vue/repl to ^2.6.3 ([#9540](https://github.com/vuejs/core/issues/9540)) ([176d590](https://github.com/vuejs/core/commit/176d59058c9aecffe9da4d4311e98496684f06d4))
|
|
||||||
* **hydration:** fix tagName access error on comment/text node hydration mismatch ([dd8a0cf](https://github.com/vuejs/core/commit/dd8a0cf5dcde13d2cbd899262a0e07f16e14e489)), closes [#9531](https://github.com/vuejs/core/issues/9531)
|
|
||||||
* **types:** avoid exposing lru-cache types in generated dts ([462aeb3](https://github.com/vuejs/core/commit/462aeb3b600765e219ded2ee9a0ed1e74df61de0)), closes [#9521](https://github.com/vuejs/core/issues/9521)
|
|
||||||
* **warn:** avoid warning on empty children with Suspense ([#3962](https://github.com/vuejs/core/issues/3962)) ([405f345](https://github.com/vuejs/core/commit/405f34587a63a5f1e3d147b9848219ea98acc22d))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
||||||
|
|
||||||
|
|
||||||
|
|
55
package.json
55
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"packageManager": "pnpm@9.12.0",
|
"packageManager": "pnpm@9.15.3",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -22,7 +22,10 @@
|
||||||
"test-dts": "run-s build-dts test-dts-only",
|
"test-dts": "run-s build-dts test-dts-only",
|
||||||
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
||||||
"test-coverage": "vitest run --project unit --coverage",
|
"test-coverage": "vitest run --project unit --coverage",
|
||||||
"test-bench": "vitest bench",
|
"prebench": "node scripts/build.js -pf esm-browser reactivity",
|
||||||
|
"prebench-compare": "node scripts/build.js -pf esm-browser reactivity",
|
||||||
|
"bench": "vitest bench --project=unit --outputJson=temp/bench.json",
|
||||||
|
"bench-compare": "vitest bench --project=unit --compare=temp/bench.json",
|
||||||
"release": "node scripts/release.js",
|
"release": "node scripts/release.js",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||||
|
@ -62,52 +65,52 @@
|
||||||
"@babel/parser": "catalog:",
|
"@babel/parser": "catalog:",
|
||||||
"@babel/types": "catalog:",
|
"@babel/types": "catalog:",
|
||||||
"@rollup/plugin-alias": "^5.1.1",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-commonjs": "^28.0.0",
|
"@rollup/plugin-commonjs": "^28.0.2",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@swc/core": "^1.7.28",
|
"@swc/core": "^1.10.7",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^20.16.10",
|
"@types/node": "^22.10.5",
|
||||||
"@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.8",
|
||||||
"@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.24.0",
|
"esbuild": "^0.24.2",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.12.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-plugin-import-x": "^4.3.1",
|
"eslint-plugin-import-x": "^4.6.1",
|
||||||
"@vitest/eslint-plugin": "^1.0.1",
|
"@vitest/eslint-plugin": "^1.1.25",
|
||||||
"estree-walker": "catalog:",
|
"estree-walker": "catalog:",
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^26.0.0",
|
||||||
"lint-staged": "^15.2.10",
|
"lint-staged": "^15.3.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"magic-string": "^0.30.11",
|
"magic-string": "^0.30.17",
|
||||||
"markdown-table": "^3.0.3",
|
"markdown-table": "^3.0.4",
|
||||||
"marked": "13.0.3",
|
"marked": "13.0.3",
|
||||||
"npm-run-all2": "^6.2.3",
|
"npm-run-all2": "^7.0.2",
|
||||||
"picocolors": "^1.1.0",
|
"picocolors": "^1.1.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.4.2",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~23.3.0",
|
"puppeteer": "~24.0.0",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.24.0",
|
"rollup": "^4.30.1",
|
||||||
"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",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.6.3",
|
||||||
"serve": "^14.2.3",
|
"serve": "^14.2.4",
|
||||||
"serve-handler": "^6.1.5",
|
"serve-handler": "^6.1.6",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.11.1",
|
||||||
"todomvc-app-css": "^2.4.3",
|
"todomvc-app-css": "^2.4.3",
|
||||||
"tslib": "^2.7.0",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.8.0",
|
"typescript-eslint": "^8.19.1",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"vitest": "^2.1.1"
|
"vitest": "^2.1.8"
|
||||||
},
|
},
|
||||||
"pnpm": {
|
"pnpm": {
|
||||||
"peerDependencyRules": {
|
"peerDependencyRules": {
|
||||||
|
|
|
@ -2068,3 +2068,13 @@ expectString(instance.actionText)
|
||||||
// public prop on $props should be optional
|
// public prop on $props should be optional
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
expectString(instance.$props.actionText)
|
expectString(instance.$props.actionText)
|
||||||
|
|
||||||
|
// #12122
|
||||||
|
defineComponent({
|
||||||
|
props: { foo: String },
|
||||||
|
render() {
|
||||||
|
expectType<{ readonly foo?: string }>(this.$props)
|
||||||
|
// @ts-expect-error
|
||||||
|
expectType<string>(this.$props)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
|
@ -240,6 +240,23 @@ describe('withDefaults w/ defineProp type is different from the defaults type',
|
||||||
res1.value
|
res1.value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('withDefaults w/ defineProp discriminate union type', () => {
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<
|
||||||
|
{ type: 'button'; buttonType?: 'submit' } | { type: 'link'; href: string }
|
||||||
|
>(),
|
||||||
|
{
|
||||||
|
type: 'button',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (props.type === 'button') {
|
||||||
|
expectType<'submit' | undefined>(props.buttonType)
|
||||||
|
}
|
||||||
|
if (props.type === 'link') {
|
||||||
|
expectType<string>(props.href)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
describe('defineProps w/ runtime declaration', () => {
|
describe('defineProps w/ runtime declaration', () => {
|
||||||
// runtime declaration
|
// runtime declaration
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
|
@ -289,6 +306,14 @@ describe('defineEmits w/ type declaration', () => {
|
||||||
emit2('baz')
|
emit2('baz')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('defineEmits w/ interface declaration', () => {
|
||||||
|
interface Emits {
|
||||||
|
foo: [value: string]
|
||||||
|
}
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
emit('foo', 'hi')
|
||||||
|
})
|
||||||
|
|
||||||
describe('defineEmits w/ alt type declaration', () => {
|
describe('defineEmits w/ alt type declaration', () => {
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
foo: [id: string]
|
foo: [id: string]
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"vite": "catalog:"
|
"vite": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^4.4.2",
|
"@vue/repl": "^4.4.3",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
|
|
|
@ -123,6 +123,7 @@ onMounted(() => {
|
||||||
:prod="productionMode"
|
:prod="productionMode"
|
||||||
:ssr="useSSRMode"
|
:ssr="useSSRMode"
|
||||||
:autoSave="autoSave"
|
:autoSave="autoSave"
|
||||||
|
:theme="theme"
|
||||||
@toggle-theme="toggleTheme"
|
@toggle-theme="toggleTheme"
|
||||||
@toggle-prod="toggleProdMode"
|
@toggle-prod="toggleProdMode"
|
||||||
@toggle-ssr="toggleSSR"
|
@toggle-ssr="toggleSSR"
|
||||||
|
|
|
@ -15,6 +15,7 @@ const props = defineProps<{
|
||||||
prod: boolean
|
prod: boolean
|
||||||
ssr: boolean
|
ssr: boolean
|
||||||
autoSave: boolean
|
autoSave: boolean
|
||||||
|
theme: 'dark' | 'light'
|
||||||
}>()
|
}>()
|
||||||
const emit = defineEmits([
|
const emit = defineEmits([
|
||||||
'toggle-theme',
|
'toggle-theme',
|
||||||
|
@ -45,6 +46,7 @@ function resetVueVersion() {
|
||||||
|
|
||||||
async function copyLink(e: MouseEvent) {
|
async function copyLink(e: MouseEvent) {
|
||||||
if (e.metaKey) {
|
if (e.metaKey) {
|
||||||
|
resetVueVersion()
|
||||||
// hidden logic for going to local debug from play.vuejs.org
|
// hidden logic for going to local debug from play.vuejs.org
|
||||||
window.location.href = 'http://localhost:5173/' + window.location.hash
|
window.location.href = 'http://localhost:5173/' + window.location.hash
|
||||||
return
|
return
|
||||||
|
@ -117,7 +119,11 @@ function toggleDark() {
|
||||||
>
|
>
|
||||||
<span>{{ autoSave ? 'AutoSave ON' : 'AutoSave OFF' }}</span>
|
<span>{{ autoSave ? 'AutoSave ON' : 'AutoSave OFF' }}</span>
|
||||||
</button>
|
</button>
|
||||||
<button title="Toggle dark mode" class="toggle-dark" @click="toggleDark">
|
<button
|
||||||
|
:title="`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`"
|
||||||
|
class="toggle-dark"
|
||||||
|
@click="toggleDark"
|
||||||
|
>
|
||||||
<Sun class="light" />
|
<Sun class="light" />
|
||||||
<Moon class="dark" />
|
<Moon class="dark" />
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"vue": "^3.4.0"
|
"vue": "^3.4.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.2.1",
|
||||||
"vite": "^5.4.8"
|
"vite": "^6.0.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"enableNonBrowserBranches": true
|
"enableNonBrowserBranches": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"monaco-editor": "^0.52.0",
|
"monaco-editor": "^0.52.2",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform > element v-for key expression prefixing + v-memo 1`] = `
|
||||||
|
"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
|
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, ___, _cached) => {
|
||||||
|
const _memo = (_ctx.getLetter(data))
|
||||||
|
if (_cached && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached
|
||||||
|
const _item = (_openBlock(), _createElementBlock("span", {
|
||||||
|
key: _ctx.getId(data)
|
||||||
|
}))
|
||||||
|
_item.memo = _memo
|
||||||
|
return _item
|
||||||
|
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: v-memo transform > on component 1`] = `
|
exports[`compiler: v-memo transform > on component 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
|
||||||
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
||||||
return _cache[0] || (
|
return _cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -28,7 +28,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -47,7 +47,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -66,7 +66,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
|
|
@ -53,4 +53,12 @@ describe('compiler: v-memo transform', () => {
|
||||||
),
|
),
|
||||||
).toMatchSnapshot()
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('element v-for key expression prefixing + v-memo', () => {
|
||||||
|
expect(
|
||||||
|
compile(
|
||||||
|
`<span v-for="data of tableData" :key="getId(data)" v-memo="getLetter(data)"></span>`,
|
||||||
|
),
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
|
|
@ -418,6 +418,7 @@ export interface CacheExpression extends Node {
|
||||||
index: number
|
index: number
|
||||||
value: JSChildNode
|
value: JSChildNode
|
||||||
needPauseTracking: boolean
|
needPauseTracking: boolean
|
||||||
|
inVOnce: boolean
|
||||||
needArraySpread: boolean
|
needArraySpread: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -774,12 +775,14 @@ export function createCacheExpression(
|
||||||
index: number,
|
index: number,
|
||||||
value: JSChildNode,
|
value: JSChildNode,
|
||||||
needPauseTracking: boolean = false,
|
needPauseTracking: boolean = false,
|
||||||
|
inVOnce: boolean = false,
|
||||||
): CacheExpression {
|
): CacheExpression {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index,
|
index,
|
||||||
value,
|
value,
|
||||||
needPauseTracking: needPauseTracking,
|
needPauseTracking: needPauseTracking,
|
||||||
|
inVOnce,
|
||||||
needArraySpread: false,
|
needArraySpread: false,
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1017,7 +1017,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||||
push(`_cache[${node.index}] || (`)
|
push(`_cache[${node.index}] || (`)
|
||||||
if (needPauseTracking) {
|
if (needPauseTracking) {
|
||||||
indent()
|
indent()
|
||||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
push(`${helper(SET_BLOCK_TRACKING)}(-1`)
|
||||||
|
if (node.inVOnce) push(`, true`)
|
||||||
|
push(`),`)
|
||||||
newline()
|
newline()
|
||||||
push(`(`)
|
push(`(`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -933,6 +933,10 @@ function getLoc(start: number, end?: number): SourceLocation {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function cloneLoc(loc: SourceLocation): SourceLocation {
|
||||||
|
return getLoc(loc.start.offset, loc.end.offset)
|
||||||
|
}
|
||||||
|
|
||||||
function setLocEnd(loc: SourceLocation, end: number) {
|
function setLocEnd(loc: SourceLocation, end: number) {
|
||||||
loc.end = tokenizer.getPos(end)
|
loc.end = tokenizer.getPos(end)
|
||||||
loc.source = getSlice(loc.start.offset, end)
|
loc.source = getSlice(loc.start.offset, end)
|
||||||
|
|
|
@ -116,7 +116,7 @@ export interface TransformContext
|
||||||
addIdentifiers(exp: ExpressionNode | string): void
|
addIdentifiers(exp: ExpressionNode | string): void
|
||||||
removeIdentifiers(exp: ExpressionNode | string): void
|
removeIdentifiers(exp: ExpressionNode | string): void
|
||||||
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
||||||
cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
|
cache(exp: JSChildNode, isVNode?: boolean, inVOnce?: boolean): CacheExpression
|
||||||
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
||||||
|
|
||||||
// 2.x Compat only
|
// 2.x Compat only
|
||||||
|
@ -297,11 +297,12 @@ export function createTransformContext(
|
||||||
identifier.hoisted = exp
|
identifier.hoisted = exp
|
||||||
return identifier
|
return identifier
|
||||||
},
|
},
|
||||||
cache(exp, isVNode = false) {
|
cache(exp, isVNode = false, inVOnce = false) {
|
||||||
const cacheExp = createCacheExpression(
|
const cacheExp = createCacheExpression(
|
||||||
context.cached.length,
|
context.cached.length,
|
||||||
exp,
|
exp,
|
||||||
isVNode,
|
isVNode,
|
||||||
|
inVOnce,
|
||||||
)
|
)
|
||||||
context.cached.push(cacheExp)
|
context.cached.push(cacheExp)
|
||||||
return cacheExp
|
return cacheExp
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
isStaticPropertyKey,
|
isStaticPropertyKey,
|
||||||
walkIdentifiers,
|
walkIdentifiers,
|
||||||
} from '../babelUtils'
|
} from '../babelUtils'
|
||||||
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
|
import { advancePositionWithClone, findDir, isSimpleIdentifier } from '../utils'
|
||||||
import {
|
import {
|
||||||
genPropsAccessExp,
|
genPropsAccessExp,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
|
@ -54,6 +54,7 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||||
)
|
)
|
||||||
} else if (node.type === NodeTypes.ELEMENT) {
|
} else if (node.type === NodeTypes.ELEMENT) {
|
||||||
// handle directives on element
|
// handle directives on element
|
||||||
|
const memo = findDir(node, 'memo')
|
||||||
for (let i = 0; i < node.props.length; i++) {
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
const dir = node.props[i]
|
const dir = node.props[i]
|
||||||
// do not process for v-on & v-for since they are special handled
|
// do not process for v-on & v-for since they are special handled
|
||||||
|
@ -65,7 +66,14 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
exp &&
|
exp &&
|
||||||
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
!(dir.name === 'on' && arg)
|
!(dir.name === 'on' && arg) &&
|
||||||
|
// key has been processed in transformFor(vMemo + vFor)
|
||||||
|
!(
|
||||||
|
memo &&
|
||||||
|
arg &&
|
||||||
|
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
arg.content === 'key'
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
dir.exp = processExpression(
|
dir.exp = processExpression(
|
||||||
exp,
|
exp,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { camelize } from '@vue/shared'
|
||||||
import { CAMELIZE } from '../runtimeHelpers'
|
import { CAMELIZE } from '../runtimeHelpers'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
|
|
||||||
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
|
// v-bind without arg is handled directly in ./transformElement.ts due to its affecting
|
||||||
// codegen for the entire props object. This transform here is only for v-bind
|
// codegen for the entire props object. This transform here is only for v-bind
|
||||||
// *with* args.
|
// *with* args.
|
||||||
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
|
|
|
@ -63,17 +63,27 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
|
||||||
const isTemplate = isTemplateNode(node)
|
const isTemplate = isTemplateNode(node)
|
||||||
const memo = findDir(node, 'memo')
|
const memo = findDir(node, 'memo')
|
||||||
const keyProp = findProp(node, `key`, false, true)
|
const keyProp = findProp(node, `key`, false, true)
|
||||||
if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
|
const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
|
||||||
|
if (isDirKey && !keyProp.exp) {
|
||||||
// resolve :key shorthand #10882
|
// resolve :key shorthand #10882
|
||||||
transformBindShorthand(keyProp, context)
|
transformBindShorthand(keyProp, context)
|
||||||
}
|
}
|
||||||
const keyExp =
|
let keyExp =
|
||||||
keyProp &&
|
keyProp &&
|
||||||
(keyProp.type === NodeTypes.ATTRIBUTE
|
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||||
? keyProp.value
|
? keyProp.value
|
||||||
? createSimpleExpression(keyProp.value.content, true)
|
? createSimpleExpression(keyProp.value.content, true)
|
||||||
: undefined
|
: undefined
|
||||||
: keyProp.exp)
|
: keyProp.exp)
|
||||||
|
|
||||||
|
if (memo && keyExp && isDirKey) {
|
||||||
|
if (!__BROWSER__) {
|
||||||
|
keyProp.exp = keyExp = processExpression(
|
||||||
|
keyExp as SimpleExpressionNode,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
const keyProperty =
|
const keyProperty =
|
||||||
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ import {
|
||||||
import { ErrorCodes, createCompilerError } from '../errors'
|
import { ErrorCodes, createCompilerError } from '../errors'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
import { validateBrowserExpression } from '../validateExpression'
|
import { validateBrowserExpression } from '../validateExpression'
|
||||||
|
import { cloneLoc } from '../parser'
|
||||||
import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
|
import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
|
||||||
import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
@ -110,7 +111,7 @@ export function processIf(
|
||||||
const branch = createIfBranch(node, dir)
|
const branch = createIfBranch(node, dir)
|
||||||
const ifNode: IfNode = {
|
const ifNode: IfNode = {
|
||||||
type: NodeTypes.IF,
|
type: NodeTypes.IF,
|
||||||
loc: node.loc,
|
loc: cloneLoc(node.loc),
|
||||||
branches: [branch],
|
branches: [branch],
|
||||||
}
|
}
|
||||||
context.replaceNode(ifNode)
|
context.replaceNode(ifNode)
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { hasScopeRef, isFnExpression, isMemberExpression } from '../utils'
|
||||||
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
||||||
|
|
||||||
export interface VOnDirectiveNode extends DirectiveNode {
|
export interface VOnDirectiveNode extends DirectiveNode {
|
||||||
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
|
// v-on without arg is handled directly in ./transformElement.ts due to its affecting
|
||||||
// codegen for the entire props object. This transform here is only for v-on
|
// codegen for the entire props object. This transform here is only for v-on
|
||||||
// *with* args.
|
// *with* args.
|
||||||
arg: ExpressionNode
|
arg: ExpressionNode
|
||||||
|
|
|
@ -17,7 +17,11 @@ export const transformOnce: NodeTransform = (node, context) => {
|
||||||
context.inVOnce = false
|
context.inVOnce = false
|
||||||
const cur = context.currentNode as ElementNode | IfNode | ForNode
|
const cur = context.currentNode as ElementNode | IfNode | ForNode
|
||||||
if (cur.codegenNode) {
|
if (cur.codegenNode) {
|
||||||
cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */)
|
cur.codegenNode = context.cache(
|
||||||
|
cur.codegenNode,
|
||||||
|
true /* isVNode */,
|
||||||
|
true /* inVOnce */,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,33 @@ return function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`stringify static html > serializing template string style 1`] = `
|
||||||
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||||
|
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||||
|
])))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`stringify static html > should bail for <option> elements with null values 1`] = `
|
||||||
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
return function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||||
|
_createElementVNode("select", null, [
|
||||||
|
_createElementVNode("option", { value: null }),
|
||||||
|
_createElementVNode("option", { value: "1" }),
|
||||||
|
_createElementVNode("option", { value: "1" }),
|
||||||
|
_createElementVNode("option", { value: "1" }),
|
||||||
|
_createElementVNode("option", { value: "1" }),
|
||||||
|
_createElementVNode("option", { value: "1" })
|
||||||
|
], -1 /* HOISTED */)
|
||||||
|
])))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
|
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,27 @@ describe('stringify static html', () => {
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12391
|
||||||
|
test('serializing template string style', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><div :style="\`color:red;\`">${repeat(
|
||||||
|
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</div></div>`,
|
||||||
|
)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.cached).toMatchObject([
|
||||||
|
cachedArrayStaticNodeMatcher(
|
||||||
|
`<div style="color:red;">${repeat(
|
||||||
|
`<span class="foo bar">1 + false</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</div>`,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('escape', () => {
|
test('escape', () => {
|
||||||
const { ast, code } = compileWithStringify(
|
const { ast, code } = compileWithStringify(
|
||||||
`<div><div>${repeat(
|
`<div><div>${repeat(
|
||||||
|
@ -470,6 +491,17 @@ describe('stringify static html', () => {
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should bail for <option> elements with null values', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><select><option :value="null" />${repeat(
|
||||||
|
`<option value="1" />`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</select></div>`,
|
||||||
|
)
|
||||||
|
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('eligible content (elements > 20) + non-eligible content', () => {
|
test('eligible content (elements > 20) + non-eligible content', () => {
|
||||||
const { code } = compileWithStringify(
|
const { code } = compileWithStringify(
|
||||||
`<div>${repeat(
|
`<div>${repeat(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
@ -261,8 +261,7 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
||||||
isOptionTag &&
|
isOptionTag &&
|
||||||
isStaticArgOf(p.arg, 'value') &&
|
isStaticArgOf(p.arg, 'value') &&
|
||||||
p.exp &&
|
p.exp &&
|
||||||
p.exp.ast &&
|
!p.exp.isStatic
|
||||||
p.exp.ast.type !== 'StringLiteral'
|
|
||||||
) {
|
) {
|
||||||
return bail()
|
return bail()
|
||||||
}
|
}
|
||||||
|
|
|
@ -233,6 +233,33 @@ export default /*@__PURE__*/_defineComponent({
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`defineProps > w/ extends intersection type 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
type Foo = {
|
||||||
|
x?: number;
|
||||||
|
};
|
||||||
|
interface Props extends Foo {
|
||||||
|
z: number
|
||||||
|
y: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default /*@__PURE__*/_defineComponent({
|
||||||
|
props: {
|
||||||
|
z: { type: Number, required: true },
|
||||||
|
y: { type: String, required: true },
|
||||||
|
x: { type: Number, required: false }
|
||||||
|
},
|
||||||
|
setup(__props: any, { expose: __expose }) {
|
||||||
|
__expose();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,6 +295,31 @@ export default /*@__PURE__*/_defineComponent({
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`defineProps > w/ intersection type 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
type Foo = {
|
||||||
|
x?: number;
|
||||||
|
};
|
||||||
|
type Bar = {
|
||||||
|
y: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default /*@__PURE__*/_defineComponent({
|
||||||
|
props: {
|
||||||
|
x: { type: Number, required: false },
|
||||||
|
y: { type: String, required: true }
|
||||||
|
},
|
||||||
|
setup(__props: any, { expose: __expose }) {
|
||||||
|
__expose();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -320,3 +320,22 @@ return { rest }
|
||||||
|
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`sfc reactive props destructure > with TSInstantiationExpression 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
type Foo = <T extends string | number>(data: T) => void
|
||||||
|
|
||||||
|
export default /*@__PURE__*/_defineComponent({
|
||||||
|
props: {
|
||||||
|
value: { type: Function }
|
||||||
|
},
|
||||||
|
setup(__props: any) {
|
||||||
|
|
||||||
|
|
||||||
|
const foo = __props.value<123>
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
|
@ -261,6 +261,51 @@ const props = defineProps({ foo: String })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('w/ extends intersection type', () => {
|
||||||
|
const { content, bindings } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
type Foo = {
|
||||||
|
x?: number;
|
||||||
|
};
|
||||||
|
interface Props extends Foo {
|
||||||
|
z: number
|
||||||
|
y: string
|
||||||
|
}
|
||||||
|
defineProps<Props>()
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`z: { type: Number, required: true }`)
|
||||||
|
expect(content).toMatch(`y: { type: String, required: true }`)
|
||||||
|
expect(content).toMatch(`x: { type: Number, required: false }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
x: BindingTypes.PROPS,
|
||||||
|
y: BindingTypes.PROPS,
|
||||||
|
z: BindingTypes.PROPS,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('w/ intersection type', () => {
|
||||||
|
const { content, bindings } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
type Foo = {
|
||||||
|
x?: number;
|
||||||
|
};
|
||||||
|
type Bar = {
|
||||||
|
y: string;
|
||||||
|
};
|
||||||
|
defineProps<Foo & Bar>()
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`y: { type: String, required: true }`)
|
||||||
|
expect(content).toMatch(`x: { type: Number, required: false }`)
|
||||||
|
expect(bindings).toStrictEqual({
|
||||||
|
x: BindingTypes.PROPS,
|
||||||
|
y: BindingTypes.PROPS,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('w/ exported interface', () => {
|
test('w/ exported interface', () => {
|
||||||
const { content, bindings } = compile(`
|
const { content, bindings } = compile(`
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -198,6 +198,21 @@ describe('sfc reactive props destructure', () => {
|
||||||
}`)
|
}`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with TSInstantiationExpression', () => {
|
||||||
|
const { content } = compile(
|
||||||
|
`
|
||||||
|
<script setup lang="ts">
|
||||||
|
type Foo = <T extends string | number>(data: T) => void
|
||||||
|
const { value } = defineProps<{ value: Foo }>()
|
||||||
|
const foo = value<123>
|
||||||
|
</script>
|
||||||
|
`,
|
||||||
|
{ isProd: true },
|
||||||
|
)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`const foo = __props.value<123>`)
|
||||||
|
})
|
||||||
|
|
||||||
test('aliasing', () => {
|
test('aliasing', () => {
|
||||||
const { content, bindings } = compile(`
|
const { content, bindings } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
"module": "dist/compiler-sfc.esm-browser.js",
|
"module": "dist/compiler-sfc.esm-browser.js",
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"estree-walker": "catalog:",
|
"estree-walker": "catalog:",
|
||||||
"magic-string": "catalog:",
|
"magic-string": "catalog:",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.49",
|
||||||
"source-map-js": "catalog:"
|
"source-map-js": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "10.1.0",
|
"lru-cache": "10.1.0",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "~9.0.5",
|
"minimatch": "~10.0.1",
|
||||||
"postcss-modules": "^6.0.0",
|
"postcss-modules": "^6.0.1",
|
||||||
"postcss-selector-parser": "^6.1.2",
|
"postcss-selector-parser": "^7.0.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.79.4"
|
"sass": "^1.83.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
import {
|
import {
|
||||||
BindingTypes,
|
BindingTypes,
|
||||||
|
TS_NODE_TYPES,
|
||||||
extractIdentifiers,
|
extractIdentifiers,
|
||||||
isFunctionType,
|
isFunctionType,
|
||||||
isInDestructureAssignment,
|
isInDestructureAssignment,
|
||||||
|
@ -240,10 +241,7 @@ export function transformDestructuredProps(
|
||||||
if (
|
if (
|
||||||
parent &&
|
parent &&
|
||||||
parent.type.startsWith('TS') &&
|
parent.type.startsWith('TS') &&
|
||||||
parent.type !== 'TSAsExpression' &&
|
!TS_NODE_TYPES.includes(parent.type)
|
||||||
parent.type !== 'TSNonNullExpression' &&
|
|
||||||
parent.type !== 'TSSatisfiesExpression' &&
|
|
||||||
parent.type !== 'TSTypeAssertion'
|
|
||||||
) {
|
) {
|
||||||
return this.skip()
|
return this.skip()
|
||||||
}
|
}
|
||||||
|
|
|
@ -189,8 +189,7 @@ function rewriteSelector(
|
||||||
// global: replace with inner selector and do not inject [id].
|
// global: replace with inner selector and do not inject [id].
|
||||||
// ::v-global(.foo) -> .foo
|
// ::v-global(.foo) -> .foo
|
||||||
if (value === ':global' || value === '::v-global') {
|
if (value === ':global' || value === '::v-global') {
|
||||||
selectorRoot.insertAfter(selector, n.nodes[0])
|
selector.replaceWith(n.nodes[0])
|
||||||
selectorRoot.removeChild(selector)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,28 +23,48 @@ export interface StylePreprocessorResults {
|
||||||
|
|
||||||
// .scss/.sass processor
|
// .scss/.sass processor
|
||||||
const scss: StylePreprocessor = (source, map, options, load = require) => {
|
const scss: StylePreprocessor = (source, map, options, load = require) => {
|
||||||
const nodeSass = load('sass')
|
const nodeSass: typeof import('sass') = load('sass')
|
||||||
const finalOptions = {
|
const { compileString, renderSync } = nodeSass
|
||||||
...options,
|
|
||||||
data: getSource(source, options.filename, options.additionalData),
|
const data = getSource(source, options.filename, options.additionalData)
|
||||||
file: options.filename,
|
let css: string
|
||||||
outFile: options.filename,
|
let dependencies: string[]
|
||||||
sourceMap: !!map,
|
let sourceMap: any
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = nodeSass.renderSync(finalOptions)
|
if (compileString) {
|
||||||
const dependencies = result.stats.includedFiles
|
const { pathToFileURL, fileURLToPath }: typeof import('url') = load('url')
|
||||||
if (map) {
|
|
||||||
return {
|
const result = compileString(data, {
|
||||||
code: result.css.toString(),
|
...options,
|
||||||
map: merge(map, JSON.parse(result.map.toString())),
|
url: pathToFileURL(options.filename),
|
||||||
errors: [],
|
sourceMap: !!map,
|
||||||
dependencies,
|
})
|
||||||
}
|
css = result.css
|
||||||
|
dependencies = result.loadedUrls.map(url => fileURLToPath(url))
|
||||||
|
sourceMap = map ? result.sourceMap! : undefined
|
||||||
|
} else {
|
||||||
|
const result = renderSync({
|
||||||
|
...options,
|
||||||
|
data,
|
||||||
|
file: options.filename,
|
||||||
|
outFile: options.filename,
|
||||||
|
sourceMap: !!map,
|
||||||
|
})
|
||||||
|
css = result.css.toString()
|
||||||
|
dependencies = result.stats.includedFiles
|
||||||
|
sourceMap = map ? JSON.parse(result.map!.toString()) : undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return { code: result.css.toString(), errors: [], dependencies }
|
if (map) {
|
||||||
|
return {
|
||||||
|
code: css,
|
||||||
|
errors: [],
|
||||||
|
dependencies,
|
||||||
|
map: merge(map, sourceMap!),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { code: css, errors: [], dependencies }
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return { code: '', errors: [e], dependencies: [] }
|
return { code: '', errors: [e], dependencies: [] }
|
||||||
}
|
}
|
||||||
|
|
|
@ -337,6 +337,39 @@ describe('ssr: element', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('custom dir with v-text', () => {
|
||||||
|
expect(getCompiledString(`<div v-xxx v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_ssrGetDirectiveProps(_ctx, _directive_xxx))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom dir with v-text and normal attrs', () => {
|
||||||
|
expect(getCompiledString(`<div class="test" v-xxx v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_mergeProps({ class: "test" }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mulptiple custom dirs with v-text', () => {
|
||||||
|
expect(getCompiledString(`<div v-xxx v-yyy v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_mergeProps(_ssrGetDirectiveProps(_ctx, _directive_xxx), _ssrGetDirectiveProps(_ctx, _directive_yyy)))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
test('custom dir with object v-bind', () => {
|
test('custom dir with object v-bind', () => {
|
||||||
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
|
|
|
@ -52,6 +52,52 @@ describe('ssr: v-model', () => {
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
compileWithWrapper(
|
||||||
|
`<select v-model="model"><option v-for="i in items" :value="i"></option></select>`,
|
||||||
|
).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select><!--[-->\`)
|
||||||
|
_ssrRenderList(_ctx.items, (i) => {
|
||||||
|
_push(\`<option\${
|
||||||
|
_ssrRenderAttr("value", i)
|
||||||
|
}\${
|
||||||
|
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||||
|
? _ssrLooseContain(_ctx.model, i)
|
||||||
|
: _ssrLooseEqual(_ctx.model, i))) ? " selected" : ""
|
||||||
|
}></option>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!--]--></select></div>\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
compileWithWrapper(
|
||||||
|
`<select v-model="model"><option v-if="true" :value="i"></option></select>`,
|
||||||
|
).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select>\`)
|
||||||
|
if (true) {
|
||||||
|
_push(\`<option\${
|
||||||
|
_ssrRenderAttr("value", _ctx.i)
|
||||||
|
}\${
|
||||||
|
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||||
|
? _ssrLooseContain(_ctx.model, _ctx.i)
|
||||||
|
: _ssrLooseEqual(_ctx.model, _ctx.i))) ? " selected" : ""
|
||||||
|
}></option>\`)
|
||||||
|
} else {
|
||||||
|
_push(\`<!---->\`)
|
||||||
|
}
|
||||||
|
_push(\`</select></div>\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileWithWrapper(
|
compileWithWrapper(
|
||||||
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
|
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"description": "@vue/compiler-ssr",
|
"description": "@vue/compiler-ssr",
|
||||||
"main": "dist/compiler-ssr.cjs.js",
|
"main": "dist/compiler-ssr.cjs.js",
|
||||||
"types": "dist/compiler-ssr.d.ts",
|
"types": "dist/compiler-ssr.d.ts",
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
createSequenceExpression,
|
createSequenceExpression,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createTemplateLiteral,
|
createTemplateLiteral,
|
||||||
|
findDir,
|
||||||
hasDynamicKeyVBind,
|
hasDynamicKeyVBind,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
isStaticExp,
|
isStaticExp,
|
||||||
|
@ -164,24 +165,28 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
} else if (directives.length && !node.children.length) {
|
} else if (directives.length && !node.children.length) {
|
||||||
const tempId = `_temp${context.temps++}`
|
// v-text directive has higher priority than the merged props
|
||||||
propsExp.arguments = [
|
const vText = findDir(node, 'text')
|
||||||
createAssignmentExpression(
|
if (!vText) {
|
||||||
createSimpleExpression(tempId, false),
|
const tempId = `_temp${context.temps++}`
|
||||||
mergedProps,
|
propsExp.arguments = [
|
||||||
),
|
createAssignmentExpression(
|
||||||
]
|
createSimpleExpression(tempId, false),
|
||||||
rawChildrenMap.set(
|
mergedProps,
|
||||||
node,
|
),
|
||||||
createConditionalExpression(
|
]
|
||||||
createSimpleExpression(`"textContent" in ${tempId}`, false),
|
rawChildrenMap.set(
|
||||||
createCallExpression(context.helper(SSR_INTERPOLATE), [
|
node,
|
||||||
createSimpleExpression(`${tempId}.textContent`, false),
|
createConditionalExpression(
|
||||||
]),
|
createSimpleExpression(`"textContent" in ${tempId}`, false),
|
||||||
createSimpleExpression(`${tempId}.innerHTML ?? ''`, false),
|
createCallExpression(context.helper(SSR_INTERPOLATE), [
|
||||||
false,
|
createSimpleExpression(`${tempId}.textContent`, false),
|
||||||
),
|
]),
|
||||||
)
|
createSimpleExpression(`${tempId}.innerHTML ?? ''`, false),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needTagForRuntime) {
|
if (needTagForRuntime) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
type ExpressionNode,
|
type ExpressionNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
type PlainElementNode,
|
type PlainElementNode,
|
||||||
|
type TemplateChildNode,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createConditionalExpression,
|
createConditionalExpression,
|
||||||
createDOMCompilerError,
|
createDOMCompilerError,
|
||||||
|
@ -162,11 +163,18 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
checkDuplicatedValue()
|
checkDuplicatedValue()
|
||||||
node.children = [createInterpolation(model, model.loc)]
|
node.children = [createInterpolation(model, model.loc)]
|
||||||
} else if (node.tag === 'select') {
|
} else if (node.tag === 'select') {
|
||||||
node.children.forEach(child => {
|
const processChildren = (children: TemplateChildNode[]) => {
|
||||||
if (child.type === NodeTypes.ELEMENT) {
|
children.forEach(child => {
|
||||||
processOption(child as PlainElementNode)
|
if (child.type === NodeTypes.ELEMENT) {
|
||||||
}
|
processOption(child as PlainElementNode)
|
||||||
})
|
} else if (child.type === NodeTypes.FOR) {
|
||||||
|
processChildren(child.children)
|
||||||
|
} else if (child.type === NodeTypes.IF) {
|
||||||
|
child.branches.forEach(b => processChildren(b.children))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
processChildren(node.children)
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createDOMCompilerError(
|
createDOMCompilerError(
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { type ComputedRef, type Ref, computed, effect, ref } from '../src'
|
import type { ComputedRef, Ref } from '../src'
|
||||||
|
import { computed, effect, ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
|
declare module '../dist/reactivity.esm-browser.prod' {
|
||||||
|
function computed(...args: any[]): any
|
||||||
|
}
|
||||||
|
|
||||||
describe('computed', () => {
|
describe('computed', () => {
|
||||||
bench('create computed', () => {
|
bench('create computed', () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { type Ref, effect, ref } from '../src'
|
import type { Ref } from '../src'
|
||||||
|
import { effect, ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
describe('effect', () => {
|
describe('effect', () => {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { effect, reactive, shallowReadArray } from '../src'
|
import {
|
||||||
|
effect,
|
||||||
|
reactive,
|
||||||
|
shallowReadArray,
|
||||||
|
} from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { type ComputedRef, computed, reactive } from '../src'
|
import type { ComputedRef } from '../src'
|
||||||
|
import { computed, reactive } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
function createMap(obj: Record<string, any>) {
|
function createMap(obj: Record<string, any>) {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { reactive } from '../src'
|
import { reactive } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
bench('create reactive obj', () => {
|
bench('create reactive obj', () => {
|
||||||
reactive({ a: 1 })
|
reactive({ a: 1 })
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { ref } from '../src/index'
|
import { ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
describe('ref', () => {
|
describe('ref', () => {
|
||||||
bench('create ref', () => {
|
bench('create ref', () => {
|
||||||
|
|
|
@ -1107,4 +1107,36 @@ describe('reactivity/computed', () => {
|
||||||
end.prop4.value,
|
end.prop4.value,
|
||||||
]).toMatchObject([-2, -4, 2, 3])
|
]).toMatchObject([-2, -4, 2, 3])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('performance when removing dependencies from deeply nested computeds', () => {
|
||||||
|
const base = ref(1)
|
||||||
|
const trigger = ref(true)
|
||||||
|
const computeds: ComputedRef<number>[] = []
|
||||||
|
|
||||||
|
const LAYERS = 30
|
||||||
|
|
||||||
|
for (let i = 0; i < LAYERS; i++) {
|
||||||
|
const earlier = [...computeds]
|
||||||
|
|
||||||
|
computeds.push(
|
||||||
|
computed(() => {
|
||||||
|
return base.value + earlier.reduce((sum, c) => sum + c.value, 0)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const tail = computed(() =>
|
||||||
|
trigger.value ? computeds[computeds.length - 1].value : 0,
|
||||||
|
)
|
||||||
|
|
||||||
|
const t0 = performance.now()
|
||||||
|
expect(tail.value).toBe(2 ** (LAYERS - 1))
|
||||||
|
const t1 = performance.now()
|
||||||
|
expect(t1 - t0).toBeLessThan(process.env.CI ? 100 : 30)
|
||||||
|
|
||||||
|
trigger.value = false
|
||||||
|
expect(tail.value).toBe(0)
|
||||||
|
const t2 = performance.now()
|
||||||
|
expect(t2 - t1).toBeLessThan(process.env.CI ? 100 : 30)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -176,7 +176,7 @@ describe('reactivity/effect/scope', () => {
|
||||||
|
|
||||||
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
||||||
|
|
||||||
expect(scope.effects.length).toBe(1)
|
expect(scope.effects.length).toBe(0)
|
||||||
|
|
||||||
counter.num = 7
|
counter.num = 7
|
||||||
expect(dummy).toBe(0)
|
expect(dummy).toBe(0)
|
||||||
|
@ -322,4 +322,44 @@ describe('reactivity/effect/scope', () => {
|
||||||
scope.resume()
|
scope.resume()
|
||||||
expect(fnSpy).toHaveBeenCalledTimes(3)
|
expect(fnSpy).toHaveBeenCalledTimes(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('removing a watcher while stopping its effectScope', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const scope = effectScope()
|
||||||
|
let watcherCalls = 0
|
||||||
|
let cleanupCalls = 0
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
const stop1 = watch(count, () => {
|
||||||
|
watcherCalls++
|
||||||
|
})
|
||||||
|
watch(count, (val, old, onCleanup) => {
|
||||||
|
watcherCalls++
|
||||||
|
onCleanup(() => {
|
||||||
|
cleanupCalls++
|
||||||
|
stop1()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
watch(count, () => {
|
||||||
|
watcherCalls++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(watcherCalls).toBe(0)
|
||||||
|
expect(cleanupCalls).toBe(0)
|
||||||
|
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(watcherCalls).toBe(3)
|
||||||
|
expect(cleanupCalls).toBe(0)
|
||||||
|
|
||||||
|
scope.stop()
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(watcherCalls).toBe(3)
|
||||||
|
expect(cleanupCalls).toBe(1)
|
||||||
|
|
||||||
|
expect(scope.effects.length).toBe(0)
|
||||||
|
expect(scope.cleanups.length).toBe(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -409,4 +409,14 @@ describe('reactivity/reactive', () => {
|
||||||
e.effect.stop()
|
e.effect.stop()
|
||||||
expect(targetMap.get(obj)?.get('x')).toBeFalsy()
|
expect(targetMap.get(obj)?.get('x')).toBeFalsy()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should trigger reactivity when Map key is undefined', () => {
|
||||||
|
const map = reactive(new Map())
|
||||||
|
const c = computed(() => map.get(void 0))
|
||||||
|
|
||||||
|
expect(c.value).toBe(void 0)
|
||||||
|
|
||||||
|
map.set(void 0, 1)
|
||||||
|
expect(c.value).toBe(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -51,6 +51,7 @@ describe('reactivity/reactive/Array', () => {
|
||||||
const raw = {}
|
const raw = {}
|
||||||
const arr = reactive([{}, {}])
|
const arr = reactive([{}, {}])
|
||||||
arr.push(raw)
|
arr.push(raw)
|
||||||
|
|
||||||
expect(arr.indexOf(raw)).toBe(2)
|
expect(arr.indexOf(raw)).toBe(2)
|
||||||
expect(arr.indexOf(raw, 3)).toBe(-1)
|
expect(arr.indexOf(raw, 3)).toBe(-1)
|
||||||
expect(arr.includes(raw)).toBe(true)
|
expect(arr.includes(raw)).toBe(true)
|
||||||
|
@ -89,6 +90,84 @@ describe('reactivity/reactive/Array', () => {
|
||||||
expect(index).toBe(1)
|
expect(index).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// only non-existent reactive will try to search by using its raw value
|
||||||
|
describe('Array identity methods should not be called more than necessary', () => {
|
||||||
|
const identityMethods = ['includes', 'indexOf', 'lastIndexOf'] as const
|
||||||
|
|
||||||
|
function instrumentArr(rawTarget: any[]) {
|
||||||
|
identityMethods.forEach(key => {
|
||||||
|
const spy = vi.fn(rawTarget[key] as any)
|
||||||
|
rawTarget[key] = spy
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function searchValue(target: any[], ...args: unknown[]) {
|
||||||
|
return identityMethods.map(key => (target[key] as any)(...args))
|
||||||
|
}
|
||||||
|
|
||||||
|
function unInstrumentArr(rawTarget: any[]) {
|
||||||
|
identityMethods.forEach(key => {
|
||||||
|
;(rawTarget[key] as any).mockClear()
|
||||||
|
// relink to prototype method
|
||||||
|
rawTarget[key] = Array.prototype[key] as any
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectHaveBeenCalledTimes(rawTarget: any[], times: number) {
|
||||||
|
identityMethods.forEach(key => {
|
||||||
|
expect(rawTarget[key]).toHaveBeenCalledTimes(times)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
test('should be called once with a non-existent raw value', () => {
|
||||||
|
const reactiveArr = reactive([])
|
||||||
|
instrumentArr(toRaw(reactiveArr))
|
||||||
|
const searchResult = searchValue(reactiveArr, {})
|
||||||
|
|
||||||
|
expectHaveBeenCalledTimes(toRaw(reactiveArr), 1)
|
||||||
|
expect(searchResult).toStrictEqual([false, -1, -1])
|
||||||
|
|
||||||
|
unInstrumentArr(toRaw(reactiveArr))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should be called once with an existent reactive value', () => {
|
||||||
|
const existReactiveValue = reactive({})
|
||||||
|
const reactiveArr = reactive([existReactiveValue, existReactiveValue])
|
||||||
|
|
||||||
|
instrumentArr(toRaw(reactiveArr))
|
||||||
|
const searchResult = searchValue(reactiveArr, existReactiveValue)
|
||||||
|
|
||||||
|
expectHaveBeenCalledTimes(toRaw(reactiveArr), 1)
|
||||||
|
expect(searchResult).toStrictEqual([true, 0, 1])
|
||||||
|
|
||||||
|
unInstrumentArr(toRaw(reactiveArr))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should be called twice with a non-existent reactive value', () => {
|
||||||
|
const reactiveArr = reactive([])
|
||||||
|
instrumentArr(toRaw(reactiveArr))
|
||||||
|
const searchResult = searchValue(reactiveArr, reactive({}))
|
||||||
|
|
||||||
|
expectHaveBeenCalledTimes(toRaw(reactiveArr), 2)
|
||||||
|
expect(searchResult).toStrictEqual([false, -1, -1])
|
||||||
|
|
||||||
|
unInstrumentArr(toRaw(reactiveArr))
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should be called twice with a non-existent reactive value, but the raw value exists', () => {
|
||||||
|
const existRaw = {}
|
||||||
|
const reactiveArr = reactive([existRaw, existRaw])
|
||||||
|
|
||||||
|
instrumentArr(toRaw(reactiveArr))
|
||||||
|
const searchResult = searchValue(reactiveArr, reactive(existRaw))
|
||||||
|
|
||||||
|
expectHaveBeenCalledTimes(toRaw(reactiveArr), 2)
|
||||||
|
expect(searchResult).toStrictEqual([true, 0, 1])
|
||||||
|
|
||||||
|
unInstrumentArr(toRaw(reactiveArr))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('delete on Array should not trigger length dependency', () => {
|
test('delete on Array should not trigger length dependency', () => {
|
||||||
const arr = reactive([1, 2, 3])
|
const arr = reactive([1, 2, 3])
|
||||||
const fn = vi.fn()
|
const fn = vi.fn()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
@ -53,6 +53,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get(target: Target, key: string | symbol, receiver: object): any {
|
get(target: Target, key: string | symbol, receiver: object): any {
|
||||||
|
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
|
||||||
|
|
||||||
const isReadonly = this._isReadonly,
|
const isReadonly = this._isReadonly,
|
||||||
isShallow = this._isShallow
|
isShallow = this._isShallow
|
||||||
if (key === ReactiveFlags.IS_REACTIVE) {
|
if (key === ReactiveFlags.IS_REACTIVE) {
|
||||||
|
|
|
@ -8,7 +8,14 @@ import {
|
||||||
} from './reactive'
|
} from './reactive'
|
||||||
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
|
import { ITERATE_KEY, MAP_KEY_ITERATE_KEY, track, trigger } from './dep'
|
||||||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||||
import { capitalize, hasChanged, hasOwn, isMap, toRawType } from '@vue/shared'
|
import {
|
||||||
|
capitalize,
|
||||||
|
extend,
|
||||||
|
hasChanged,
|
||||||
|
hasOwn,
|
||||||
|
isMap,
|
||||||
|
toRawType,
|
||||||
|
} from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
type CollectionTypes = IterableCollections | WeakCollections
|
type CollectionTypes = IterableCollections | WeakCollections
|
||||||
|
@ -23,152 +30,6 @@ const toShallow = <T extends unknown>(value: T): T => value
|
||||||
const getProto = <T extends CollectionTypes>(v: T): any =>
|
const getProto = <T extends CollectionTypes>(v: T): any =>
|
||||||
Reflect.getPrototypeOf(v)
|
Reflect.getPrototypeOf(v)
|
||||||
|
|
||||||
function get(
|
|
||||||
target: MapTypes,
|
|
||||||
key: unknown,
|
|
||||||
isReadonly = false,
|
|
||||||
isShallow = false,
|
|
||||||
) {
|
|
||||||
// #1772: readonly(reactive(Map)) should return readonly + reactive version
|
|
||||||
// of the value
|
|
||||||
target = target[ReactiveFlags.RAW]
|
|
||||||
const rawTarget = toRaw(target)
|
|
||||||
const rawKey = toRaw(key)
|
|
||||||
if (!isReadonly) {
|
|
||||||
if (hasChanged(key, rawKey)) {
|
|
||||||
track(rawTarget, TrackOpTypes.GET, key)
|
|
||||||
}
|
|
||||||
track(rawTarget, TrackOpTypes.GET, rawKey)
|
|
||||||
}
|
|
||||||
const { has } = getProto(rawTarget)
|
|
||||||
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
|
|
||||||
if (has.call(rawTarget, key)) {
|
|
||||||
return wrap(target.get(key))
|
|
||||||
} else if (has.call(rawTarget, rawKey)) {
|
|
||||||
return wrap(target.get(rawKey))
|
|
||||||
} else if (target !== rawTarget) {
|
|
||||||
// #3602 readonly(reactive(Map))
|
|
||||||
// ensure that the nested reactive `Map` can do tracking for itself
|
|
||||||
target.get(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function has(this: CollectionTypes, key: unknown, isReadonly = false): boolean {
|
|
||||||
const target = this[ReactiveFlags.RAW]
|
|
||||||
const rawTarget = toRaw(target)
|
|
||||||
const rawKey = toRaw(key)
|
|
||||||
if (!isReadonly) {
|
|
||||||
if (hasChanged(key, rawKey)) {
|
|
||||||
track(rawTarget, TrackOpTypes.HAS, key)
|
|
||||||
}
|
|
||||||
track(rawTarget, TrackOpTypes.HAS, rawKey)
|
|
||||||
}
|
|
||||||
return key === rawKey
|
|
||||||
? target.has(key)
|
|
||||||
: target.has(key) || target.has(rawKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
function size(target: IterableCollections, isReadonly = false) {
|
|
||||||
target = target[ReactiveFlags.RAW]
|
|
||||||
!isReadonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
|
||||||
return Reflect.get(target, 'size', target)
|
|
||||||
}
|
|
||||||
|
|
||||||
function add(this: SetTypes, value: unknown, _isShallow = false) {
|
|
||||||
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
|
|
||||||
value = toRaw(value)
|
|
||||||
}
|
|
||||||
const target = toRaw(this)
|
|
||||||
const proto = getProto(target)
|
|
||||||
const hadKey = proto.has.call(target, value)
|
|
||||||
if (!hadKey) {
|
|
||||||
target.add(value)
|
|
||||||
trigger(target, TriggerOpTypes.ADD, value, value)
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
function set(this: MapTypes, key: unknown, value: unknown, _isShallow = false) {
|
|
||||||
if (!_isShallow && !isShallow(value) && !isReadonly(value)) {
|
|
||||||
value = toRaw(value)
|
|
||||||
}
|
|
||||||
const target = toRaw(this)
|
|
||||||
const { has, get } = getProto(target)
|
|
||||||
|
|
||||||
let hadKey = has.call(target, key)
|
|
||||||
if (!hadKey) {
|
|
||||||
key = toRaw(key)
|
|
||||||
hadKey = has.call(target, key)
|
|
||||||
} else if (__DEV__) {
|
|
||||||
checkIdentityKeys(target, has, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldValue = get.call(target, key)
|
|
||||||
target.set(key, value)
|
|
||||||
if (!hadKey) {
|
|
||||||
trigger(target, TriggerOpTypes.ADD, key, value)
|
|
||||||
} else if (hasChanged(value, oldValue)) {
|
|
||||||
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
|
|
||||||
}
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteEntry(this: CollectionTypes, key: unknown) {
|
|
||||||
const target = toRaw(this)
|
|
||||||
const { has, get } = getProto(target)
|
|
||||||
let hadKey = has.call(target, key)
|
|
||||||
if (!hadKey) {
|
|
||||||
key = toRaw(key)
|
|
||||||
hadKey = has.call(target, key)
|
|
||||||
} else if (__DEV__) {
|
|
||||||
checkIdentityKeys(target, has, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
const oldValue = get ? get.call(target, key) : undefined
|
|
||||||
// forward the operation before queueing reactions
|
|
||||||
const result = target.delete(key)
|
|
||||||
if (hadKey) {
|
|
||||||
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function clear(this: IterableCollections) {
|
|
||||||
const target = toRaw(this)
|
|
||||||
const hadItems = target.size !== 0
|
|
||||||
const oldTarget = __DEV__
|
|
||||||
? isMap(target)
|
|
||||||
? new Map(target)
|
|
||||||
: new Set(target)
|
|
||||||
: undefined
|
|
||||||
// forward the operation before queueing reactions
|
|
||||||
const result = target.clear()
|
|
||||||
if (hadItems) {
|
|
||||||
trigger(target, TriggerOpTypes.CLEAR, undefined, undefined, oldTarget)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
function createForEach(isReadonly: boolean, isShallow: boolean) {
|
|
||||||
return function forEach(
|
|
||||||
this: IterableCollections,
|
|
||||||
callback: Function,
|
|
||||||
thisArg?: unknown,
|
|
||||||
) {
|
|
||||||
const observed = this
|
|
||||||
const target = observed[ReactiveFlags.RAW]
|
|
||||||
const rawTarget = toRaw(target)
|
|
||||||
const wrap = isShallow ? toShallow : isReadonly ? toReadonly : toReactive
|
|
||||||
!isReadonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
|
|
||||||
return target.forEach((value: unknown, key: unknown) => {
|
|
||||||
// important: make sure the callback is
|
|
||||||
// 1. invoked with the reactive map as `this` and 3rd arg
|
|
||||||
// 2. the value received should be a corresponding reactive/readonly.
|
|
||||||
return callback.call(thisArg, wrap(value), wrap(key), observed)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createIterableMethod(
|
function createIterableMethod(
|
||||||
method: string | symbol,
|
method: string | symbol,
|
||||||
isReadonly: boolean,
|
isReadonly: boolean,
|
||||||
|
@ -232,74 +93,158 @@ function createReadonlyMethod(type: TriggerOpTypes): Function {
|
||||||
|
|
||||||
type Instrumentations = Record<string | symbol, Function | number>
|
type Instrumentations = Record<string | symbol, Function | number>
|
||||||
|
|
||||||
function createInstrumentations() {
|
function createInstrumentations(
|
||||||
const mutableInstrumentations: Instrumentations = {
|
readonly: boolean,
|
||||||
|
shallow: boolean,
|
||||||
|
): Instrumentations {
|
||||||
|
const instrumentations: Instrumentations = {
|
||||||
get(this: MapTypes, key: unknown) {
|
get(this: MapTypes, key: unknown) {
|
||||||
return get(this, key)
|
// #1772: readonly(reactive(Map)) should return readonly + reactive version
|
||||||
|
// of the value
|
||||||
|
const target = this[ReactiveFlags.RAW]
|
||||||
|
const rawTarget = toRaw(target)
|
||||||
|
const rawKey = toRaw(key)
|
||||||
|
if (!readonly) {
|
||||||
|
if (hasChanged(key, rawKey)) {
|
||||||
|
track(rawTarget, TrackOpTypes.GET, key)
|
||||||
|
}
|
||||||
|
track(rawTarget, TrackOpTypes.GET, rawKey)
|
||||||
|
}
|
||||||
|
const { has } = getProto(rawTarget)
|
||||||
|
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
|
||||||
|
if (has.call(rawTarget, key)) {
|
||||||
|
return wrap(target.get(key))
|
||||||
|
} else if (has.call(rawTarget, rawKey)) {
|
||||||
|
return wrap(target.get(rawKey))
|
||||||
|
} else if (target !== rawTarget) {
|
||||||
|
// #3602 readonly(reactive(Map))
|
||||||
|
// ensure that the nested reactive `Map` can do tracking for itself
|
||||||
|
target.get(key)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
get size() {
|
get size() {
|
||||||
return size(this as unknown as IterableCollections)
|
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
|
||||||
|
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||||
|
return Reflect.get(target, 'size', target)
|
||||||
|
},
|
||||||
|
has(this: CollectionTypes, key: unknown): boolean {
|
||||||
|
const target = this[ReactiveFlags.RAW]
|
||||||
|
const rawTarget = toRaw(target)
|
||||||
|
const rawKey = toRaw(key)
|
||||||
|
if (!readonly) {
|
||||||
|
if (hasChanged(key, rawKey)) {
|
||||||
|
track(rawTarget, TrackOpTypes.HAS, key)
|
||||||
|
}
|
||||||
|
track(rawTarget, TrackOpTypes.HAS, rawKey)
|
||||||
|
}
|
||||||
|
return key === rawKey
|
||||||
|
? target.has(key)
|
||||||
|
: target.has(key) || target.has(rawKey)
|
||||||
|
},
|
||||||
|
forEach(this: IterableCollections, callback: Function, thisArg?: unknown) {
|
||||||
|
const observed = this
|
||||||
|
const target = observed[ReactiveFlags.RAW]
|
||||||
|
const rawTarget = toRaw(target)
|
||||||
|
const wrap = shallow ? toShallow : readonly ? toReadonly : toReactive
|
||||||
|
!readonly && track(rawTarget, TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||||
|
return target.forEach((value: unknown, key: unknown) => {
|
||||||
|
// important: make sure the callback is
|
||||||
|
// 1. invoked with the reactive map as `this` and 3rd arg
|
||||||
|
// 2. the value received should be a corresponding reactive/readonly.
|
||||||
|
return callback.call(thisArg, wrap(value), wrap(key), observed)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
has,
|
|
||||||
add,
|
|
||||||
set,
|
|
||||||
delete: deleteEntry,
|
|
||||||
clear,
|
|
||||||
forEach: createForEach(false, false),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const shallowInstrumentations: Instrumentations = {
|
extend(
|
||||||
get(this: MapTypes, key: unknown) {
|
instrumentations,
|
||||||
return get(this, key, false, true)
|
readonly
|
||||||
},
|
? {
|
||||||
get size() {
|
add: createReadonlyMethod(TriggerOpTypes.ADD),
|
||||||
return size(this as unknown as IterableCollections)
|
set: createReadonlyMethod(TriggerOpTypes.SET),
|
||||||
},
|
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
|
||||||
has,
|
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
|
||||||
add(this: SetTypes, value: unknown) {
|
}
|
||||||
return add.call(this, value, true)
|
: {
|
||||||
},
|
add(this: SetTypes, value: unknown) {
|
||||||
set(this: MapTypes, key: unknown, value: unknown) {
|
if (!shallow && !isShallow(value) && !isReadonly(value)) {
|
||||||
return set.call(this, key, value, true)
|
value = toRaw(value)
|
||||||
},
|
}
|
||||||
delete: deleteEntry,
|
const target = toRaw(this)
|
||||||
clear,
|
const proto = getProto(target)
|
||||||
forEach: createForEach(false, true),
|
const hadKey = proto.has.call(target, value)
|
||||||
}
|
if (!hadKey) {
|
||||||
|
target.add(value)
|
||||||
|
trigger(target, TriggerOpTypes.ADD, value, value)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
set(this: MapTypes, key: unknown, value: unknown) {
|
||||||
|
if (!shallow && !isShallow(value) && !isReadonly(value)) {
|
||||||
|
value = toRaw(value)
|
||||||
|
}
|
||||||
|
const target = toRaw(this)
|
||||||
|
const { has, get } = getProto(target)
|
||||||
|
|
||||||
const readonlyInstrumentations: Instrumentations = {
|
let hadKey = has.call(target, key)
|
||||||
get(this: MapTypes, key: unknown) {
|
if (!hadKey) {
|
||||||
return get(this, key, true)
|
key = toRaw(key)
|
||||||
},
|
hadKey = has.call(target, key)
|
||||||
get size() {
|
} else if (__DEV__) {
|
||||||
return size(this as unknown as IterableCollections, true)
|
checkIdentityKeys(target, has, key)
|
||||||
},
|
}
|
||||||
has(this: MapTypes, key: unknown) {
|
|
||||||
return has.call(this, key, true)
|
|
||||||
},
|
|
||||||
add: createReadonlyMethod(TriggerOpTypes.ADD),
|
|
||||||
set: createReadonlyMethod(TriggerOpTypes.SET),
|
|
||||||
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
|
|
||||||
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
|
|
||||||
forEach: createForEach(true, false),
|
|
||||||
}
|
|
||||||
|
|
||||||
const shallowReadonlyInstrumentations: Instrumentations = {
|
const oldValue = get.call(target, key)
|
||||||
get(this: MapTypes, key: unknown) {
|
target.set(key, value)
|
||||||
return get(this, key, true, true)
|
if (!hadKey) {
|
||||||
},
|
trigger(target, TriggerOpTypes.ADD, key, value)
|
||||||
get size() {
|
} else if (hasChanged(value, oldValue)) {
|
||||||
return size(this as unknown as IterableCollections, true)
|
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
|
||||||
},
|
}
|
||||||
has(this: MapTypes, key: unknown) {
|
return this
|
||||||
return has.call(this, key, true)
|
},
|
||||||
},
|
delete(this: CollectionTypes, key: unknown) {
|
||||||
add: createReadonlyMethod(TriggerOpTypes.ADD),
|
const target = toRaw(this)
|
||||||
set: createReadonlyMethod(TriggerOpTypes.SET),
|
const { has, get } = getProto(target)
|
||||||
delete: createReadonlyMethod(TriggerOpTypes.DELETE),
|
let hadKey = has.call(target, key)
|
||||||
clear: createReadonlyMethod(TriggerOpTypes.CLEAR),
|
if (!hadKey) {
|
||||||
forEach: createForEach(true, true),
|
key = toRaw(key)
|
||||||
}
|
hadKey = has.call(target, key)
|
||||||
|
} else if (__DEV__) {
|
||||||
|
checkIdentityKeys(target, has, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldValue = get ? get.call(target, key) : undefined
|
||||||
|
// forward the operation before queueing reactions
|
||||||
|
const result = target.delete(key)
|
||||||
|
if (hadKey) {
|
||||||
|
trigger(target, TriggerOpTypes.DELETE, key, undefined, oldValue)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
clear(this: IterableCollections) {
|
||||||
|
const target = toRaw(this)
|
||||||
|
const hadItems = target.size !== 0
|
||||||
|
const oldTarget = __DEV__
|
||||||
|
? isMap(target)
|
||||||
|
? new Map(target)
|
||||||
|
: new Set(target)
|
||||||
|
: undefined
|
||||||
|
// forward the operation before queueing reactions
|
||||||
|
const result = target.clear()
|
||||||
|
if (hadItems) {
|
||||||
|
trigger(
|
||||||
|
target,
|
||||||
|
TriggerOpTypes.CLEAR,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
oldTarget,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
const iteratorMethods = [
|
const iteratorMethods = [
|
||||||
'keys',
|
'keys',
|
||||||
|
@ -309,39 +254,14 @@ function createInstrumentations() {
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
iteratorMethods.forEach(method => {
|
iteratorMethods.forEach(method => {
|
||||||
mutableInstrumentations[method] = createIterableMethod(method, false, false)
|
instrumentations[method] = createIterableMethod(method, readonly, shallow)
|
||||||
readonlyInstrumentations[method] = createIterableMethod(method, true, false)
|
|
||||||
shallowInstrumentations[method] = createIterableMethod(method, false, true)
|
|
||||||
shallowReadonlyInstrumentations[method] = createIterableMethod(
|
|
||||||
method,
|
|
||||||
true,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return [
|
return instrumentations
|
||||||
mutableInstrumentations,
|
|
||||||
readonlyInstrumentations,
|
|
||||||
shallowInstrumentations,
|
|
||||||
shallowReadonlyInstrumentations,
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const [
|
|
||||||
mutableInstrumentations,
|
|
||||||
readonlyInstrumentations,
|
|
||||||
shallowInstrumentations,
|
|
||||||
shallowReadonlyInstrumentations,
|
|
||||||
] = /* @__PURE__*/ createInstrumentations()
|
|
||||||
|
|
||||||
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
|
function createInstrumentationGetter(isReadonly: boolean, shallow: boolean) {
|
||||||
const instrumentations = shallow
|
const instrumentations = createInstrumentations(isReadonly, shallow)
|
||||||
? isReadonly
|
|
||||||
? shallowReadonlyInstrumentations
|
|
||||||
: shallowInstrumentations
|
|
||||||
: isReadonly
|
|
||||||
? readonlyInstrumentations
|
|
||||||
: mutableInstrumentations
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
target: CollectionTypes,
|
target: CollectionTypes,
|
||||||
|
|
|
@ -340,7 +340,7 @@ export function trigger(
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// schedule runs for SET | ADD | DELETE
|
// schedule runs for SET | ADD | DELETE
|
||||||
if (key !== void 0) {
|
if (key !== void 0 || depsMap.has(void 0)) {
|
||||||
run(depsMap.get(key))
|
run(depsMap.get(key))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -426,23 +426,24 @@ function removeSub(link: Link, soft = false) {
|
||||||
nextSub.prevSub = prevSub
|
nextSub.prevSub = prevSub
|
||||||
link.nextSub = undefined
|
link.nextSub = undefined
|
||||||
}
|
}
|
||||||
if (dep.subs === link) {
|
|
||||||
// was previous tail, point new tail to prev
|
|
||||||
dep.subs = prevSub
|
|
||||||
}
|
|
||||||
if (__DEV__ && dep.subsHead === link) {
|
if (__DEV__ && dep.subsHead === link) {
|
||||||
// was previous head, point new head to next
|
// was previous head, point new head to next
|
||||||
dep.subsHead = nextSub
|
dep.subsHead = nextSub
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!dep.subs && dep.computed) {
|
if (dep.subs === link) {
|
||||||
// if computed, unsubscribe it from all its deps so this computed and its
|
// was previous tail, point new tail to prev
|
||||||
// value can be GCed
|
dep.subs = prevSub
|
||||||
dep.computed.flags &= ~EffectFlags.TRACKING
|
|
||||||
for (let l = dep.computed.deps; l; l = l.nextDep) {
|
if (!prevSub && dep.computed) {
|
||||||
// here we are only "soft" unsubscribing because the computed still keeps
|
// if computed, unsubscribe it from all its deps so this computed and its
|
||||||
// referencing the deps and the dep should not decrease its sub count
|
// value can be GCed
|
||||||
removeSub(l, true)
|
dep.computed.flags &= ~EffectFlags.TRACKING
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,10 @@ export class EffectScope {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
private _active = true
|
private _active = true
|
||||||
|
/**
|
||||||
|
* @internal track `on` calls, allow `on` call multiple times
|
||||||
|
*/
|
||||||
|
private _on = 0
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -99,12 +103,16 @@ export class EffectScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevScope: EffectScope | undefined
|
||||||
/**
|
/**
|
||||||
* This should only be called on non-detached scopes
|
* This should only be called on non-detached scopes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
on(): void {
|
on(): void {
|
||||||
activeEffectScope = this
|
if (++this._on === 1) {
|
||||||
|
this.prevScope = activeEffectScope
|
||||||
|
activeEffectScope = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,23 +120,33 @@ export class EffectScope {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
off(): void {
|
off(): void {
|
||||||
activeEffectScope = this.parent
|
if (this._on > 0 && --this._on === 0) {
|
||||||
|
activeEffectScope = this.prevScope
|
||||||
|
this.prevScope = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(fromParent?: boolean): void {
|
stop(fromParent?: boolean): void {
|
||||||
if (this._active) {
|
if (this._active) {
|
||||||
|
this._active = false
|
||||||
let i, l
|
let i, l
|
||||||
for (i = 0, l = this.effects.length; i < l; i++) {
|
for (i = 0, l = this.effects.length; i < l; i++) {
|
||||||
this.effects[i].stop()
|
this.effects[i].stop()
|
||||||
}
|
}
|
||||||
|
this.effects.length = 0
|
||||||
|
|
||||||
for (i = 0, l = this.cleanups.length; i < l; i++) {
|
for (i = 0, l = this.cleanups.length; i < l; i++) {
|
||||||
this.cleanups[i]()
|
this.cleanups[i]()
|
||||||
}
|
}
|
||||||
|
this.cleanups.length = 0
|
||||||
|
|
||||||
if (this.scopes) {
|
if (this.scopes) {
|
||||||
for (i = 0, l = this.scopes.length; i < l; i++) {
|
for (i = 0, l = this.scopes.length; i < l; i++) {
|
||||||
this.scopes[i].stop(true)
|
this.scopes[i].stop(true)
|
||||||
}
|
}
|
||||||
|
this.scopes.length = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// nested scope, dereference from parent to avoid memory leaks
|
// nested scope, dereference from parent to avoid memory leaks
|
||||||
if (!this.detached && this.parent && !fromParent) {
|
if (!this.detached && this.parent && !fromParent) {
|
||||||
// optimized O(1) removal
|
// optimized O(1) removal
|
||||||
|
@ -139,7 +157,6 @@ export class EffectScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.parent = undefined
|
this.parent = undefined
|
||||||
this._active = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -213,7 +213,7 @@ export function watch(
|
||||||
const scope = getCurrentScope()
|
const scope = getCurrentScope()
|
||||||
const watchHandle: WatchHandle = () => {
|
const watchHandle: WatchHandle = () => {
|
||||||
effect.stop()
|
effect.stop()
|
||||||
if (scope) {
|
if (scope && scope.active) {
|
||||||
remove(scope.effects, effect)
|
remove(scope.effects, effect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,13 @@ import {
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import {
|
import {
|
||||||
type DebuggerEvent,
|
type DebuggerEvent,
|
||||||
EffectFlags,
|
|
||||||
ITERATE_KEY,
|
ITERATE_KEY,
|
||||||
type Ref,
|
type Ref,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
TrackOpTypes,
|
TrackOpTypes,
|
||||||
TriggerOpTypes,
|
TriggerOpTypes,
|
||||||
effectScope,
|
effectScope,
|
||||||
|
onScopeDispose,
|
||||||
shallowReactive,
|
shallowReactive,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
toRef,
|
toRef,
|
||||||
|
@ -1341,7 +1341,7 @@ describe('api: watch', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
expect(instance!.scope.effects[0].flags & EffectFlags.ACTIVE).toBeFalsy()
|
expect(instance!.scope.effects.length).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
|
test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
|
||||||
|
@ -1983,4 +1983,31 @@ describe('api: watch', () => {
|
||||||
expect(spy1).toHaveBeenCalled()
|
expect(spy1).toHaveBeenCalled()
|
||||||
expect(spy2).toHaveBeenCalled()
|
expect(spy2).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12631
|
||||||
|
test('this.$watch w/ onScopeDispose', () => {
|
||||||
|
const onCleanup = vi.fn()
|
||||||
|
const toggle = ref(true)
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
render() {},
|
||||||
|
created(this: any) {
|
||||||
|
this.$watch(
|
||||||
|
() => 1,
|
||||||
|
function () {},
|
||||||
|
)
|
||||||
|
onScopeDispose(onCleanup)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const App = defineComponent({
|
||||||
|
render() {
|
||||||
|
return toggle.value ? h(Comp) : null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
createApp(App).mount(root)
|
||||||
|
expect(onCleanup).toBeCalledTimes(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -333,6 +333,30 @@ describe('component props', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//#12011
|
||||||
|
test('replace camelize with hyphenate to handle props key', () => {
|
||||||
|
const Comp = {
|
||||||
|
props: {
|
||||||
|
hasB4BProp: { type: Boolean, required: true },
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(
|
||||||
|
h('div', {}, [
|
||||||
|
h(Comp, {
|
||||||
|
'has-b-4-b-prop': true,
|
||||||
|
}),
|
||||||
|
h(Comp, {
|
||||||
|
'has-b4-b-prop': true,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
nodeOps.createElement('div'),
|
||||||
|
)
|
||||||
|
expect(`Missing required prop: "hasB4BProp"`).not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
test('warn props mutation', () => {
|
test('warn props mutation', () => {
|
||||||
let instance: ComponentInternalInstance
|
let instance: ComponentInternalInstance
|
||||||
let setupProps: any
|
let setupProps: any
|
||||||
|
|
|
@ -87,6 +87,49 @@ describe('renderer: teleport', () => {
|
||||||
`</div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('update before mounted with defer', async () => {
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const foo = ref('foo')
|
||||||
|
const Header = {
|
||||||
|
props: { foo: String },
|
||||||
|
setup(props: any) {
|
||||||
|
return () => h('div', props.foo)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Footer = {
|
||||||
|
setup() {
|
||||||
|
foo.value = 'bar'
|
||||||
|
return () => h('div', 'Footer')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createDOMApp({
|
||||||
|
render() {
|
||||||
|
return show.value
|
||||||
|
? [
|
||||||
|
h(
|
||||||
|
Teleport,
|
||||||
|
{ to: '#targetId', defer: true },
|
||||||
|
h(Header, { foo: foo.value }),
|
||||||
|
),
|
||||||
|
h(Footer),
|
||||||
|
h('div', { id: 'targetId' }),
|
||||||
|
]
|
||||||
|
: [h('div')]
|
||||||
|
},
|
||||||
|
}).mount(root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(`"<div></div>"`)
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--teleport start--><!--teleport end--><div>Footer</div><div id="targetId"><div>bar</div></div>"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function runSharedTests(deferMode: boolean) {
|
function runSharedTests(deferMode: boolean) {
|
||||||
|
|
|
@ -32,6 +32,12 @@ describe('renderSlot', () => {
|
||||||
expect(vnode.key).toBe('foo')
|
expect(vnode.key).toBe('foo')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should allow symbol values for slot prop key', () => {
|
||||||
|
const key = Symbol()
|
||||||
|
const vnode = renderSlot({ default: () => [h('div')] }, 'default', { key })
|
||||||
|
expect(vnode.key).toBe('_default')
|
||||||
|
})
|
||||||
|
|
||||||
it('should render slot fallback', () => {
|
it('should render slot fallback', () => {
|
||||||
const vnode = renderSlot({}, 'default', { key: 'foo' }, () => ['fallback'])
|
const vnode = renderSlot({}, 'default', { key: 'foo' }, () => ['fallback'])
|
||||||
expect(vnode.children).toEqual(['fallback'])
|
expect(vnode.children).toEqual(['fallback'])
|
||||||
|
|
|
@ -8,6 +8,7 @@ import {
|
||||||
defineAsyncComponent,
|
defineAsyncComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
|
onServerPrefetch,
|
||||||
useId,
|
useId,
|
||||||
} from 'vue'
|
} from 'vue'
|
||||||
import { renderToString } from '@vue/server-renderer'
|
import { renderToString } from '@vue/server-renderer'
|
||||||
|
@ -145,6 +146,40 @@ describe('useId', () => {
|
||||||
expect(await getOutput(() => factory(16, 0))).toBe(expected)
|
expect(await getOutput(() => factory(16, 0))).toBe(expected)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('components with serverPrefetch', async () => {
|
||||||
|
const factory = (): ReturnType<TestCaseFactory> => {
|
||||||
|
const SPOne = defineComponent({
|
||||||
|
setup() {
|
||||||
|
onServerPrefetch(() => {})
|
||||||
|
return () => h(BasicComponentWithUseId)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const SPTwo = defineComponent({
|
||||||
|
render() {
|
||||||
|
return h(BasicComponentWithUseId)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const app = createApp({
|
||||||
|
setup() {
|
||||||
|
const id1 = useId()
|
||||||
|
const id2 = useId()
|
||||||
|
return () => [id1, ' ', id2, ' ', h(SPOne), ' ', h(SPTwo)]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
return [app, []]
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected =
|
||||||
|
'v-0 v-1 ' + // root
|
||||||
|
'v-0-0 v-0-1 ' + // inside first async subtree
|
||||||
|
'v-2 v-3' // inside second async subtree
|
||||||
|
// assert different async resolution order does not affect id stable-ness
|
||||||
|
expect(await getOutput(() => factory())).toBe(expected)
|
||||||
|
expect(await getOutput(() => factory())).toBe(expected)
|
||||||
|
})
|
||||||
|
|
||||||
test('async setup()', async () => {
|
test('async setup()', async () => {
|
||||||
const factory = (
|
const factory = (
|
||||||
delay1: number,
|
delay1: number,
|
||||||
|
|
|
@ -153,10 +153,10 @@ describe('useModel', () => {
|
||||||
|
|
||||||
const compRender = vi.fn()
|
const compRender = vi.fn()
|
||||||
const Comp = defineComponent({
|
const Comp = defineComponent({
|
||||||
props: ['fooBar'],
|
props: ['foo-bar'],
|
||||||
emits: ['update:fooBar'],
|
emits: ['update:foo-bar'],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
foo = useModel(props, 'fooBar')
|
foo = useModel(props, 'foo-bar')
|
||||||
return () => {
|
return () => {
|
||||||
compRender()
|
compRender()
|
||||||
return foo.value
|
return foo.value
|
||||||
|
@ -192,10 +192,10 @@ describe('useModel', () => {
|
||||||
|
|
||||||
const compRender = vi.fn()
|
const compRender = vi.fn()
|
||||||
const Comp = defineComponent({
|
const Comp = defineComponent({
|
||||||
props: ['fooBar'],
|
props: ['foo-bar'],
|
||||||
emits: ['update:fooBar'],
|
emits: ['update:foo-bar'],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
foo = useModel(props, 'fooBar')
|
foo = useModel(props, 'foo-bar')
|
||||||
return () => {
|
return () => {
|
||||||
compRender()
|
compRender()
|
||||||
return foo.value
|
return foo.value
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
onServerPrefetch,
|
||||||
openBlock,
|
openBlock,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
|
@ -518,6 +519,45 @@ describe('SSR hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with data-allow-mismatch component when using onServerPrefetch', async () => {
|
||||||
|
const Comp = {
|
||||||
|
template: `
|
||||||
|
<div>Comp2</div>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
let foo: any
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
const flag = ref(true)
|
||||||
|
foo = () => {
|
||||||
|
flag.value = false
|
||||||
|
}
|
||||||
|
onServerPrefetch(() => (flag.value = false))
|
||||||
|
return { flag }
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Comp,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<span data-allow-mismatch>
|
||||||
|
<Comp v-if="flag"></Comp>
|
||||||
|
</span>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
// hydrate
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.innerHTML = await renderToString(h(App))
|
||||||
|
createSSRApp(App).mount(container)
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
'<span data-allow-mismatch=""><div>Comp2</div></span>',
|
||||||
|
)
|
||||||
|
foo()
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
'<span data-allow-mismatch=""><!--v-if--></span>',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('Teleport unmount (full integration)', async () => {
|
test('Teleport unmount (full integration)', async () => {
|
||||||
const Comp1 = {
|
const Comp1 = {
|
||||||
template: `
|
template: `
|
||||||
|
@ -1284,6 +1324,84 @@ describe('SSR hydration', () => {
|
||||||
resolve({})
|
resolve({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//#12362
|
||||||
|
test('nested async wrapper', async () => {
|
||||||
|
const Toggle = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
r(
|
||||||
|
defineComponent({
|
||||||
|
setup(_, { slots }) {
|
||||||
|
const show = ref(false)
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
show.value = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return () =>
|
||||||
|
withDirectives(
|
||||||
|
h('div', null, [renderSlot(slots, 'default')]),
|
||||||
|
[[vShow, show.value]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const Wrapper = defineAsyncComponent(() => {
|
||||||
|
return new Promise(r => {
|
||||||
|
r(
|
||||||
|
defineComponent({
|
||||||
|
render(this: any) {
|
||||||
|
return renderSlot(this.$slots, 'default')
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
const fn = vi.fn()
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
fn()
|
||||||
|
count.value++
|
||||||
|
})
|
||||||
|
return () => h('div', count.value)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(Toggle, null, {
|
||||||
|
default: () =>
|
||||||
|
h(Wrapper, null, {
|
||||||
|
default: () =>
|
||||||
|
h(Wrapper, null, {
|
||||||
|
default: () => h(Child),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.innerHTML = await renderToString(h(App))
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<div style="display:none;"><!--[--><!--[--><!--[--><div>0</div><!--]--><!--]--><!--]--></div>"`,
|
||||||
|
)
|
||||||
|
|
||||||
|
createSSRApp(App).mount(root)
|
||||||
|
await nextTick()
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<div style=""><!--[--><!--[--><!--[--><div>1</div><!--]--><!--]--><!--]--></div>"`,
|
||||||
|
)
|
||||||
|
expect(fn).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('unmount async wrapper before load (fragment)', async () => {
|
test('unmount async wrapper before load (fragment)', async () => {
|
||||||
let resolve: any
|
let resolve: any
|
||||||
const AsyncComp = defineAsyncComponent(
|
const AsyncComp = defineAsyncComponent(
|
||||||
|
@ -1613,6 +1731,36 @@ describe('SSR hydration', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Suspense + transition appear', async () => {
|
||||||
|
const { vnode, container } = mountWithHydration(
|
||||||
|
`<template><div>foo</div></template>`,
|
||||||
|
() =>
|
||||||
|
h(Suspense, {}, () =>
|
||||||
|
h(
|
||||||
|
Transition,
|
||||||
|
{ appear: true },
|
||||||
|
{
|
||||||
|
default: () => h('div', 'foo'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(vnode.el).toBe(container.firstChild)
|
||||||
|
// wait for hydration to finish
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
|
||||||
|
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
class="v-enter-from v-enter-active"
|
||||||
|
>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
await nextTick()
|
||||||
|
expect(vnode.el).toBe(container.firstChild)
|
||||||
|
})
|
||||||
|
|
||||||
// #10607
|
// #10607
|
||||||
test('update component stable slot (prod + optimized mode)', async () => {
|
test('update component stable slot (prod + optimized mode)', async () => {
|
||||||
__DEV__ = false
|
__DEV__ = false
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
import {
|
import {
|
||||||
Fragment,
|
Fragment,
|
||||||
type FunctionalComponent,
|
type FunctionalComponent,
|
||||||
|
Teleport,
|
||||||
createBlock,
|
createBlock,
|
||||||
createCommentVNode,
|
createCommentVNode,
|
||||||
createElementBlock,
|
createElementBlock,
|
||||||
|
@ -391,6 +392,26 @@ describe('attribute fallthrough', () => {
|
||||||
expect(`Extraneous non-emits event listeners`).toHaveBeenWarned()
|
expect(`Extraneous non-emits event listeners`).toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should warn when fallthrough fails on teleport root node', () => {
|
||||||
|
const Parent = {
|
||||||
|
render() {
|
||||||
|
return h(Child, { class: 'parent' })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const root = document.createElement('div')
|
||||||
|
|
||||||
|
const Child = defineComponent({
|
||||||
|
render() {
|
||||||
|
return h(Teleport, { to: root }, h('div'))
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
document.body.appendChild(root)
|
||||||
|
render(h(Parent), root)
|
||||||
|
|
||||||
|
expect(`Extraneous non-props attributes (class)`).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
it('should dedupe same listeners when $attrs is used during render', () => {
|
it('should dedupe same listeners when $attrs is used during render', () => {
|
||||||
const click = vi.fn()
|
const click = vi.fn()
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
serializeInner as inner,
|
serializeInner as inner,
|
||||||
nextTick,
|
nextTick,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
|
onBeforeMount,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
openBlock,
|
openBlock,
|
||||||
|
@ -1199,7 +1200,7 @@ describe('renderer: optimized mode', () => {
|
||||||
createBlock('div', null, [
|
createBlock('div', null, [
|
||||||
createVNode('div', null, [
|
createVNode('div', null, [
|
||||||
cache[0] ||
|
cache[0] ||
|
||||||
(setBlockTracking(-1),
|
(setBlockTracking(-1, true),
|
||||||
((cache[0] = createVNode('div', null, [
|
((cache[0] = createVNode('div', null, [
|
||||||
createVNode(Child),
|
createVNode(Child),
|
||||||
])).cacheIndex = 0),
|
])).cacheIndex = 0),
|
||||||
|
@ -1233,4 +1234,64 @@ describe('renderer: optimized mode', () => {
|
||||||
expect(inner(root)).toBe('<!--v-if-->')
|
expect(inner(root)).toBe('<!--v-if-->')
|
||||||
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12371
|
||||||
|
test('unmount children when the user calls a compiled slot', async () => {
|
||||||
|
const beforeMountSpy = vi.fn()
|
||||||
|
const beforeUnmountSpy = vi.fn()
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onBeforeMount(beforeMountSpy)
|
||||||
|
onBeforeUnmount(beforeUnmountSpy)
|
||||||
|
return () => 'child'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = {
|
||||||
|
setup(_: any, { slots }: SetupContext) {
|
||||||
|
return () => (
|
||||||
|
openBlock(),
|
||||||
|
createElementBlock('section', null, [
|
||||||
|
(openBlock(),
|
||||||
|
createElementBlock('div', { key: 1 }, [
|
||||||
|
createTextVNode(slots.header!() ? 'foo' : 'bar', 1 /* TEXT */),
|
||||||
|
renderSlot(slots, 'content'),
|
||||||
|
])),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const app = createApp({
|
||||||
|
render() {
|
||||||
|
return show.value
|
||||||
|
? (openBlock(),
|
||||||
|
createBlock(Wrapper, null, {
|
||||||
|
header: withCtx(() => [createVNode({})]),
|
||||||
|
content: withCtx(() => [createVNode(Child)]),
|
||||||
|
_: 1,
|
||||||
|
}))
|
||||||
|
: createCommentVNode('v-if', true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.mount(root)
|
||||||
|
expect(inner(root)).toMatchInlineSnapshot(`"<!--v-if-->"`)
|
||||||
|
expect(beforeMountSpy).toHaveBeenCalledTimes(0)
|
||||||
|
expect(beforeUnmountSpy).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toMatchInlineSnapshot(
|
||||||
|
`"<section><div>foochild</div></section>"`,
|
||||||
|
)
|
||||||
|
expect(beforeMountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
show.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toBe('<!--v-if-->')
|
||||||
|
expect(beforeUnmountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import {
|
import {
|
||||||
|
KeepAlive,
|
||||||
|
defineAsyncComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
@ -217,6 +219,7 @@ describe('api: template refs', () => {
|
||||||
}
|
}
|
||||||
render(h(Comp), root)
|
render(h(Comp), root)
|
||||||
expect(state.refKey).toBe(root.children[0])
|
expect(state.refKey).toBe(root.children[0])
|
||||||
|
expect('Template ref "refKey" used on a non-ref value').toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
test('multiple root refs', () => {
|
test('multiple root refs', () => {
|
||||||
|
@ -537,4 +540,68 @@ describe('api: template refs', () => {
|
||||||
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
|
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with async component which nested in KeepAlive', async () => {
|
||||||
|
const AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(resolve =>
|
||||||
|
setTimeout(() =>
|
||||||
|
resolve(
|
||||||
|
defineComponent({
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
name: 'AsyncComp',
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
name: 'Comp',
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggle = ref(false)
|
||||||
|
const instanceRef = ref<any>(null)
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
render: () => {
|
||||||
|
return h(KeepAlive, () =>
|
||||||
|
toggle.value
|
||||||
|
? h(AsyncComp, { ref: instanceRef })
|
||||||
|
: h(Comp, { ref: instanceRef }),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(App), root)
|
||||||
|
expect(instanceRef.value.name).toBe('Comp')
|
||||||
|
|
||||||
|
// switch to async component
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value).toBe(null)
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
expect(instanceRef.value.name).toBe('AsyncComp')
|
||||||
|
|
||||||
|
// switch back to normal component
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value.name).toBe('Comp')
|
||||||
|
|
||||||
|
// switch to async component again
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value.name).toBe('AsyncComp')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -629,7 +629,7 @@ describe('vnode', () => {
|
||||||
const vnode =
|
const vnode =
|
||||||
(openBlock(),
|
(openBlock(),
|
||||||
createBlock('div', null, [
|
createBlock('div', null, [
|
||||||
setBlockTracking(-1),
|
setBlockTracking(-1, true),
|
||||||
(vnode1 = (openBlock(), createBlock('div'))),
|
(vnode1 = (openBlock(), createBlock('div'))),
|
||||||
setBlockTracking(1),
|
setBlockTracking(1),
|
||||||
vnode1,
|
vnode1,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-core",
|
"name": "@vue/runtime-core",
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"description": "@vue/runtime-core",
|
"description": "@vue/runtime-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-core.esm-bundler.js",
|
"module": "dist/runtime-core.esm-bundler.js",
|
||||||
|
|
|
@ -265,7 +265,7 @@ export function defineComponent<
|
||||||
Mixin,
|
Mixin,
|
||||||
Extends,
|
Extends,
|
||||||
ResolvedEmits,
|
ResolvedEmits,
|
||||||
RuntimeEmitsKeys,
|
{},
|
||||||
{},
|
{},
|
||||||
false,
|
false,
|
||||||
InjectOptions,
|
InjectOptions,
|
||||||
|
|
|
@ -149,9 +149,7 @@ export function defineEmits() {
|
||||||
return null as any
|
return null as any
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ComponentTypeEmits =
|
export type ComponentTypeEmits = ((...args: any[]) => any) | Record<string, any>
|
||||||
| ((...args: any[]) => any)
|
|
||||||
| Record<string, any[]>
|
|
||||||
|
|
||||||
type RecordToUnion<T extends Record<string, any>> = T[keyof T]
|
type RecordToUnion<T extends Record<string, any>> = T[keyof T]
|
||||||
|
|
||||||
|
@ -331,21 +329,23 @@ type PropsWithDefaults<
|
||||||
T,
|
T,
|
||||||
Defaults extends InferDefaults<T>,
|
Defaults extends InferDefaults<T>,
|
||||||
BKeys extends keyof T,
|
BKeys extends keyof T,
|
||||||
> = Readonly<MappedOmit<T, keyof Defaults>> & {
|
> = T extends unknown
|
||||||
readonly [K in keyof Defaults as K extends keyof T
|
? Readonly<MappedOmit<T, keyof Defaults>> & {
|
||||||
? K
|
readonly [K in keyof Defaults as K extends keyof T
|
||||||
: never]-?: K extends keyof T
|
? K
|
||||||
? Defaults[K] extends undefined
|
: never]-?: K extends keyof T
|
||||||
? IfAny<Defaults[K], NotUndefined<T[K]>, T[K]>
|
? Defaults[K] extends undefined
|
||||||
: NotUndefined<T[K]>
|
? IfAny<Defaults[K], NotUndefined<T[K]>, T[K]>
|
||||||
: never
|
: NotUndefined<T[K]>
|
||||||
} & {
|
: never
|
||||||
readonly [K in BKeys]-?: K extends keyof Defaults
|
} & {
|
||||||
? Defaults[K] extends undefined
|
readonly [K in BKeys]-?: K extends keyof Defaults
|
||||||
? boolean | undefined
|
? Defaults[K] extends undefined
|
||||||
: boolean
|
? boolean | undefined
|
||||||
: boolean
|
: boolean
|
||||||
}
|
: boolean
|
||||||
|
}
|
||||||
|
: never
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vue `<script setup>` compiler macro for providing props default values when
|
* Vue `<script setup>` compiler macro for providing props default values when
|
||||||
|
|
|
@ -856,11 +856,10 @@ function setupStatefulComponent(
|
||||||
// 2. call setup()
|
// 2. call setup()
|
||||||
const { setup } = Component
|
const { setup } = Component
|
||||||
if (setup) {
|
if (setup) {
|
||||||
|
pauseTracking()
|
||||||
const setupContext = (instance.setupContext =
|
const setupContext = (instance.setupContext =
|
||||||
setup.length > 1 ? createSetupContext(instance) : null)
|
setup.length > 1 ? createSetupContext(instance) : null)
|
||||||
|
|
||||||
const reset = setCurrentInstance(instance)
|
const reset = setCurrentInstance(instance)
|
||||||
pauseTracking()
|
|
||||||
const setupResult = callWithErrorHandling(
|
const setupResult = callWithErrorHandling(
|
||||||
setup,
|
setup,
|
||||||
instance,
|
instance,
|
||||||
|
@ -870,12 +869,16 @@ function setupStatefulComponent(
|
||||||
setupContext,
|
setupContext,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
const isAsyncSetup = isPromise(setupResult)
|
||||||
resetTracking()
|
resetTracking()
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
if (isPromise(setupResult)) {
|
if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) {
|
||||||
// async setup, mark as async boundary for useId()
|
// async setup / serverPrefetch, mark as async boundary for useId()
|
||||||
if (!isAsyncWrapper(instance)) markAsyncBoundary(instance)
|
markAsyncBoundary(instance)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAsyncSetup) {
|
||||||
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
|
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
|
||||||
if (isSSR) {
|
if (isSSR) {
|
||||||
// return the promise so server-renderer can wait on it
|
// return the promise so server-renderer can wait on it
|
||||||
|
@ -1003,7 +1006,7 @@ export function finishComponentSetup(
|
||||||
instance.vnode.props &&
|
instance.vnode.props &&
|
||||||
instance.vnode.props['inline-template']) ||
|
instance.vnode.props['inline-template']) ||
|
||||||
Component.template ||
|
Component.template ||
|
||||||
resolveMergedOptions(instance).template
|
(__FEATURE_OPTIONS_API__ && resolveMergedOptions(instance).template)
|
||||||
if (template) {
|
if (template) {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
startMeasure(instance, `compile`)
|
startMeasure(instance, `compile`)
|
||||||
|
|
|
@ -416,20 +416,20 @@ interface LegacyOptions<
|
||||||
extends?: Extends
|
extends?: Extends
|
||||||
|
|
||||||
// lifecycle
|
// lifecycle
|
||||||
beforeCreate?(): void
|
beforeCreate?(): any
|
||||||
created?(): void
|
created?(): any
|
||||||
beforeMount?(): void
|
beforeMount?(): any
|
||||||
mounted?(): void
|
mounted?(): any
|
||||||
beforeUpdate?(): void
|
beforeUpdate?(): any
|
||||||
updated?(): void
|
updated?(): any
|
||||||
activated?(): void
|
activated?(): any
|
||||||
deactivated?(): void
|
deactivated?(): any
|
||||||
/** @deprecated use `beforeUnmount` instead */
|
/** @deprecated use `beforeUnmount` instead */
|
||||||
beforeDestroy?(): void
|
beforeDestroy?(): any
|
||||||
beforeUnmount?(): void
|
beforeUnmount?(): any
|
||||||
/** @deprecated use `unmounted` instead */
|
/** @deprecated use `unmounted` instead */
|
||||||
destroyed?(): void
|
destroyed?(): any
|
||||||
unmounted?(): void
|
unmounted?(): any
|
||||||
renderTracked?: DebuggerHook
|
renderTracked?: DebuggerHook
|
||||||
renderTriggered?: DebuggerHook
|
renderTriggered?: DebuggerHook
|
||||||
errorCaptured?: ErrorCapturedHook
|
errorCaptured?: ErrorCapturedHook
|
||||||
|
|
|
@ -654,6 +654,7 @@ function validateProps(
|
||||||
) {
|
) {
|
||||||
const resolvedValues = toRaw(props)
|
const resolvedValues = toRaw(props)
|
||||||
const options = instance.propsOptions[0]
|
const options = instance.propsOptions[0]
|
||||||
|
const camelizePropsKey = Object.keys(rawProps).map(key => camelize(key))
|
||||||
for (const key in options) {
|
for (const key in options) {
|
||||||
let opt = options[key]
|
let opt = options[key]
|
||||||
if (opt == null) continue
|
if (opt == null) continue
|
||||||
|
@ -662,7 +663,7 @@ function validateProps(
|
||||||
resolvedValues[key],
|
resolvedValues[key],
|
||||||
opt,
|
opt,
|
||||||
__DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
|
__DEV__ ? shallowReadonly(resolvedValues) : resolvedValues,
|
||||||
!hasOwn(rawProps, key) && !hasOwn(rawProps, hyphenate(key)),
|
!camelizePropsKey.includes(key),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -190,7 +190,7 @@ export function renderComponentRoot(
|
||||||
`Extraneous non-props attributes (` +
|
`Extraneous non-props attributes (` +
|
||||||
`${extraAttrs.join(', ')}) ` +
|
`${extraAttrs.join(', ')}) ` +
|
||||||
`were passed to component but could not be automatically inherited ` +
|
`were passed to component but could not be automatically inherited ` +
|
||||||
`because component renders fragment or text root nodes.`,
|
`because component renders fragment or text or teleport root nodes.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (eventAttrs.length) {
|
if (eventAttrs.length) {
|
||||||
|
|
|
@ -198,8 +198,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
setTransitionHooks(innerChild, enterHooks)
|
setTransitionHooks(innerChild, enterHooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
const oldChild = instance.subTree
|
let oldInnerChild = instance.subTree && getInnerChild(instance.subTree)
|
||||||
const oldInnerChild = oldChild && getInnerChild(oldChild)
|
|
||||||
|
|
||||||
// handle mode
|
// handle mode
|
||||||
if (
|
if (
|
||||||
|
@ -208,7 +207,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
!isSameVNodeType(innerChild, oldInnerChild) &&
|
!isSameVNodeType(innerChild, oldInnerChild) &&
|
||||||
recursiveGetSubtree(instance).type !== Comment
|
recursiveGetSubtree(instance).type !== Comment
|
||||||
) {
|
) {
|
||||||
const leavingHooks = resolveTransitionHooks(
|
let leavingHooks = resolveTransitionHooks(
|
||||||
oldInnerChild,
|
oldInnerChild,
|
||||||
rawProps,
|
rawProps,
|
||||||
state,
|
state,
|
||||||
|
@ -228,6 +227,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
instance.update()
|
instance.update()
|
||||||
}
|
}
|
||||||
delete leavingHooks.afterLeave
|
delete leavingHooks.afterLeave
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
return emptyPlaceholder(child)
|
return emptyPlaceholder(child)
|
||||||
} else if (mode === 'in-out' && innerChild.type !== Comment) {
|
} else if (mode === 'in-out' && innerChild.type !== Comment) {
|
||||||
|
@ -238,18 +238,27 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
) => {
|
) => {
|
||||||
const leavingVNodesCache = getLeavingNodesForType(
|
const leavingVNodesCache = getLeavingNodesForType(
|
||||||
state,
|
state,
|
||||||
oldInnerChild,
|
oldInnerChild!,
|
||||||
)
|
)
|
||||||
leavingVNodesCache[String(oldInnerChild.key)] = oldInnerChild
|
leavingVNodesCache[String(oldInnerChild!.key)] = oldInnerChild!
|
||||||
// early removal callback
|
// early removal callback
|
||||||
el[leaveCbKey] = () => {
|
el[leaveCbKey] = () => {
|
||||||
earlyRemove()
|
earlyRemove()
|
||||||
el[leaveCbKey] = undefined
|
el[leaveCbKey] = undefined
|
||||||
delete enterHooks.delayedLeave
|
delete enterHooks.delayedLeave
|
||||||
|
oldInnerChild = undefined
|
||||||
|
}
|
||||||
|
enterHooks.delayedLeave = () => {
|
||||||
|
delayedLeave()
|
||||||
|
delete enterHooks.delayedLeave
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
enterHooks.delayedLeave = delayedLeave
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
|
} else if (oldInnerChild) {
|
||||||
|
oldInnerChild = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
return child
|
return child
|
||||||
|
|
|
@ -147,7 +147,7 @@ export const TeleportImpl = {
|
||||||
}
|
}
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
mount(target, targetAnchor)
|
mount(target, targetAnchor)
|
||||||
updateCssVars(n2)
|
updateCssVars(n2, false)
|
||||||
}
|
}
|
||||||
} else if (__DEV__ && !disabled) {
|
} else if (__DEV__ && !disabled) {
|
||||||
warn(
|
warn(
|
||||||
|
@ -160,15 +160,36 @@ export const TeleportImpl = {
|
||||||
|
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
mount(container, mainAnchor)
|
mount(container, mainAnchor)
|
||||||
updateCssVars(n2)
|
updateCssVars(n2, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTeleportDeferred(n2.props)) {
|
if (isTeleportDeferred(n2.props)) {
|
||||||
queuePostRenderEffect(mountToTarget, parentSuspense)
|
queuePostRenderEffect(() => {
|
||||||
|
mountToTarget()
|
||||||
|
n2.el!.__isMounted = true
|
||||||
|
}, parentSuspense)
|
||||||
} else {
|
} else {
|
||||||
mountToTarget()
|
mountToTarget()
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (isTeleportDeferred(n2.props) && !n1.el!.__isMounted) {
|
||||||
|
queuePostRenderEffect(() => {
|
||||||
|
TeleportImpl.process(
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
container,
|
||||||
|
anchor,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense,
|
||||||
|
namespace,
|
||||||
|
slotScopeIds,
|
||||||
|
optimized,
|
||||||
|
internals,
|
||||||
|
)
|
||||||
|
delete n1.el!.__isMounted
|
||||||
|
}, parentSuspense)
|
||||||
|
return
|
||||||
|
}
|
||||||
// update content
|
// update content
|
||||||
n2.el = n1.el
|
n2.el = n1.el
|
||||||
n2.targetStart = n1.targetStart
|
n2.targetStart = n1.targetStart
|
||||||
|
@ -267,7 +288,7 @@ export const TeleportImpl = {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateCssVars(n2)
|
updateCssVars(n2, disabled)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -389,12 +410,13 @@ function hydrateTeleport(
|
||||||
querySelector,
|
querySelector,
|
||||||
))
|
))
|
||||||
if (target) {
|
if (target) {
|
||||||
|
const disabled = isTeleportDisabled(vnode.props)
|
||||||
// if multiple teleports rendered to the same target element, we need to
|
// if multiple teleports rendered to the same target element, we need to
|
||||||
// pick up from where the last teleport finished instead of the first node
|
// pick up from where the last teleport finished instead of the first node
|
||||||
const targetNode =
|
const targetNode =
|
||||||
(target as TeleportTargetElement)._lpa || target.firstChild
|
(target as TeleportTargetElement)._lpa || target.firstChild
|
||||||
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||||
if (isTeleportDisabled(vnode.props)) {
|
if (disabled) {
|
||||||
vnode.anchor = hydrateChildren(
|
vnode.anchor = hydrateChildren(
|
||||||
nextSibling(node),
|
nextSibling(node),
|
||||||
vnode,
|
vnode,
|
||||||
|
@ -446,7 +468,7 @@ function hydrateTeleport(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateCssVars(vnode)
|
updateCssVars(vnode, disabled)
|
||||||
}
|
}
|
||||||
return vnode.anchor && nextSibling(vnode.anchor as Node)
|
return vnode.anchor && nextSibling(vnode.anchor as Node)
|
||||||
}
|
}
|
||||||
|
@ -462,13 +484,20 @@ export const Teleport = TeleportImpl as unknown as {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCssVars(vnode: VNode) {
|
function updateCssVars(vnode: VNode, isDisabled: boolean) {
|
||||||
// presence of .ut method indicates owner component uses css vars.
|
// presence of .ut method indicates owner component uses css vars.
|
||||||
// code path here can assume browser environment.
|
// code path here can assume browser environment.
|
||||||
const ctx = vnode.ctx
|
const ctx = vnode.ctx
|
||||||
if (ctx && ctx.ut) {
|
if (ctx && ctx.ut) {
|
||||||
let node = vnode.targetStart
|
let node, anchor
|
||||||
while (node && node !== vnode.targetAnchor) {
|
if (isDisabled) {
|
||||||
|
node = vnode.el
|
||||||
|
anchor = vnode.anchor
|
||||||
|
} else {
|
||||||
|
node = vnode.targetStart
|
||||||
|
anchor = vnode.targetAnchor
|
||||||
|
}
|
||||||
|
while (node && node !== anchor) {
|
||||||
if (node.nodeType === 1) node.setAttribute('data-v-owner', ctx.uid)
|
if (node.nodeType === 1) node.setAttribute('data-v-owner', ctx.uid)
|
||||||
node = node.nextSibling
|
node = node.nextSibling
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ import {
|
||||||
isVNode,
|
isVNode,
|
||||||
openBlock,
|
openBlock,
|
||||||
} from '../vnode'
|
} from '../vnode'
|
||||||
import { PatchFlags, SlotFlags } from '@vue/shared'
|
import { PatchFlags, SlotFlags, isSymbol } from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
import { isAsyncWrapper } from '../apiAsyncComponent'
|
import { isAsyncWrapper } from '../apiAsyncComponent'
|
||||||
|
|
||||||
|
@ -72,15 +72,16 @@ export function renderSlot(
|
||||||
}
|
}
|
||||||
openBlock()
|
openBlock()
|
||||||
const validSlotContent = slot && ensureValidVNode(slot(props))
|
const validSlotContent = slot && ensureValidVNode(slot(props))
|
||||||
|
const slotKey =
|
||||||
|
props.key ||
|
||||||
|
// slot content array of a dynamic conditional slot may have a branch
|
||||||
|
// key attached in the `createSlots` helper, respect that
|
||||||
|
(validSlotContent && (validSlotContent as any).key)
|
||||||
const rendered = createBlock(
|
const rendered = createBlock(
|
||||||
Fragment,
|
Fragment,
|
||||||
{
|
{
|
||||||
key:
|
key:
|
||||||
(props.key ||
|
(slotKey && !isSymbol(slotKey) ? slotKey : `_${name}`) +
|
||||||
// slot content array of a dynamic conditional slot may have a branch
|
|
||||||
// key attached in the `createSlots` helper, respect that
|
|
||||||
(validSlotContent && (validSlotContent as any).key) ||
|
|
||||||
`_${name}`) +
|
|
||||||
// #7256 force differentiate fallback content from actual content
|
// #7256 force differentiate fallback content from actual content
|
||||||
(!validSlotContent && fallback ? '_fb' : ''),
|
(!validSlotContent && fallback ? '_fb' : ''),
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,14 +28,14 @@ export function useModel(
|
||||||
return ref() as any
|
return ref() as any
|
||||||
}
|
}
|
||||||
|
|
||||||
if (__DEV__ && !(i.propsOptions[0] as NormalizedProps)[name]) {
|
const camelizedName = camelize(name)
|
||||||
|
if (__DEV__ && !(i.propsOptions[0] as NormalizedProps)[camelizedName]) {
|
||||||
warn(`useModel() called with prop "${name}" which is not declared.`)
|
warn(`useModel() called with prop "${name}" which is not declared.`)
|
||||||
return ref() as any
|
return ref() as any
|
||||||
}
|
}
|
||||||
|
|
||||||
const camelizedName = camelize(name)
|
|
||||||
const hyphenatedName = hyphenate(name)
|
const hyphenatedName = hyphenate(name)
|
||||||
const modifiers = getModelModifiers(props, name)
|
const modifiers = getModelModifiers(props, camelizedName)
|
||||||
|
|
||||||
const res = customRef((track, trigger) => {
|
const res = customRef((track, trigger) => {
|
||||||
let localValue: any
|
let localValue: any
|
||||||
|
@ -43,7 +43,7 @@ export function useModel(
|
||||||
let prevEmittedValue: any
|
let prevEmittedValue: any
|
||||||
|
|
||||||
watchSyncEffect(() => {
|
watchSyncEffect(() => {
|
||||||
const propValue = props[name]
|
const propValue = props[camelizedName]
|
||||||
if (hasChanged(localValue, propValue)) {
|
if (hasChanged(localValue, propValue)) {
|
||||||
localValue = propValue
|
localValue = propValue
|
||||||
trigger()
|
trigger()
|
||||||
|
|
|
@ -11,7 +11,7 @@ import {
|
||||||
normalizeVNode,
|
normalizeVNode,
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import { flushPostFlushCbs } from './scheduler'
|
import { flushPostFlushCbs } from './scheduler'
|
||||||
import type { ComponentInternalInstance } from './component'
|
import type { ComponentInternalInstance, ComponentOptions } from './component'
|
||||||
import { invokeDirectiveHook } from './directives'
|
import { invokeDirectiveHook } from './directives'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import {
|
import {
|
||||||
|
@ -41,6 +41,7 @@ import {
|
||||||
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
import type { TeleportImpl, TeleportVNode } from './components/Teleport'
|
||||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
import { isReactive } from '@vue/reactivity'
|
import { isReactive } from '@vue/reactivity'
|
||||||
|
import { updateHOCHostEl } from './componentRenderUtils'
|
||||||
|
|
||||||
export type RootHydrateFunction = (
|
export type RootHydrateFunction = (
|
||||||
vnode: VNode<Node, Element>,
|
vnode: VNode<Node, Element>,
|
||||||
|
@ -307,7 +308,10 @@ export function createHydrationFunctions(
|
||||||
// if component is async, it may get moved / unmounted before its
|
// if component is async, it may get moved / unmounted before its
|
||||||
// inner component is loaded, so we need to give it a placeholder
|
// inner component is loaded, so we need to give it a placeholder
|
||||||
// vnode that matches its adopted DOM.
|
// vnode that matches its adopted DOM.
|
||||||
if (isAsyncWrapper(vnode)) {
|
if (
|
||||||
|
isAsyncWrapper(vnode) &&
|
||||||
|
!(vnode.type as ComponentOptions).__asyncResolved
|
||||||
|
) {
|
||||||
let subTree
|
let subTree
|
||||||
if (isFragmentStart) {
|
if (isFragmentStart) {
|
||||||
subTree = createVNode(Fragment)
|
subTree = createVNode(Fragment)
|
||||||
|
@ -385,7 +389,10 @@ export function createHydrationFunctions(
|
||||||
let needCallTransitionHooks = false
|
let needCallTransitionHooks = false
|
||||||
if (isTemplateNode(el)) {
|
if (isTemplateNode(el)) {
|
||||||
needCallTransitionHooks =
|
needCallTransitionHooks =
|
||||||
needTransition(parentSuspense, transition) &&
|
needTransition(
|
||||||
|
null, // no need check parentSuspense in hydration
|
||||||
|
transition,
|
||||||
|
) &&
|
||||||
parentComponent &&
|
parentComponent &&
|
||||||
parentComponent.vnode.props &&
|
parentComponent.vnode.props &&
|
||||||
parentComponent.vnode.props.appear
|
parentComponent.vnode.props.appear
|
||||||
|
@ -713,6 +720,11 @@ export function createHydrationFunctions(
|
||||||
getContainerType(container),
|
getContainerType(container),
|
||||||
slotScopeIds,
|
slotScopeIds,
|
||||||
)
|
)
|
||||||
|
// the component vnode's el should be updated when a mismatch occurs.
|
||||||
|
if (parentComponent) {
|
||||||
|
parentComponent.vnode.el = vnode.el
|
||||||
|
updateHOCHostEl(parentComponent, vnode.el)
|
||||||
|
}
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import { isString } from '@vue/shared'
|
import { getGlobalThis, isString } from '@vue/shared'
|
||||||
import { DOMNodeTypes, isComment } from './hydration'
|
import { DOMNodeTypes, isComment } from './hydration'
|
||||||
|
|
||||||
|
// Polyfills for Safari support
|
||||||
|
// see https://caniuse.com/requestidlecallback
|
||||||
|
const requestIdleCallback: Window['requestIdleCallback'] =
|
||||||
|
getGlobalThis().requestIdleCallback || (cb => setTimeout(cb, 1))
|
||||||
|
const cancelIdleCallback: Window['cancelIdleCallback'] =
|
||||||
|
getGlobalThis().cancelIdleCallback || (id => clearTimeout(id))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lazy hydration strategy for async components.
|
* A lazy hydration strategy for async components.
|
||||||
* @param hydrate - call this to perform the actual hydration.
|
* @param hydrate - call this to perform the actual hydration.
|
||||||
|
|
|
@ -15,7 +15,7 @@ import { isRef, toRaw } from '@vue/reactivity'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import type { SchedulerJob } from './scheduler'
|
import type { SchedulerJob } from './scheduler'
|
||||||
import { queuePostRenderEffect } from './renderer'
|
import { queuePostRenderEffect } from './renderer'
|
||||||
import { getComponentPublicInstance } from './component'
|
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
||||||
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,8 +42,18 @@ export function setRef(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isAsyncWrapper(vnode) && !isUnmount) {
|
if (isAsyncWrapper(vnode) && !isUnmount) {
|
||||||
// when mounting async components, nothing needs to be done,
|
// #4999 if an async component already resolved and cached by KeepAlive,
|
||||||
// because the template ref is forwarded to inner component
|
// we need to set the ref to inner component
|
||||||
|
if (
|
||||||
|
vnode.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE &&
|
||||||
|
(vnode.type as ComponentOptions).__asyncResolved &&
|
||||||
|
vnode.component!.subTree.component
|
||||||
|
) {
|
||||||
|
setRef(rawRef, oldRawRef, parentSuspense, vnode.component!.subTree)
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, nothing needs to be done because the template ref
|
||||||
|
// is forwarded to inner component
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,8 +79,17 @@ export function setRef(
|
||||||
setupState === EMPTY_OBJ
|
setupState === EMPTY_OBJ
|
||||||
? () => false
|
? () => false
|
||||||
: (key: string) => {
|
: (key: string) => {
|
||||||
if (__DEV__ && knownTemplateRefs.has(rawSetupState[key] as any)) {
|
if (__DEV__) {
|
||||||
return false
|
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
|
||||||
|
warn(
|
||||||
|
`Template ref "${key}" used on a non-ref value. ` +
|
||||||
|
`It will not work in the production build.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (knownTemplateRefs.has(rawSetupState[key] as any)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return hasOwn(rawSetupState, key)
|
return hasOwn(rawSetupState, key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -301,7 +301,7 @@ export let isBlockTreeEnabled = 1
|
||||||
*
|
*
|
||||||
* ``` js
|
* ``` js
|
||||||
* _cache[1] || (
|
* _cache[1] || (
|
||||||
* setBlockTracking(-1),
|
* setBlockTracking(-1, true),
|
||||||
* _cache[1] = createVNode(...),
|
* _cache[1] = createVNode(...),
|
||||||
* setBlockTracking(1),
|
* setBlockTracking(1),
|
||||||
* _cache[1]
|
* _cache[1]
|
||||||
|
@ -310,11 +310,11 @@ export let isBlockTreeEnabled = 1
|
||||||
*
|
*
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function setBlockTracking(value: number): void {
|
export function setBlockTracking(value: number, inVOnce = false): void {
|
||||||
isBlockTreeEnabled += value
|
isBlockTreeEnabled += value
|
||||||
if (value < 0 && currentBlock) {
|
if (value < 0 && currentBlock && inVOnce) {
|
||||||
// mark current block so it doesn't take fast path and skip possible
|
// mark current block so it doesn't take fast path and skip possible
|
||||||
// nested components duriung unmount
|
// nested components during unmount
|
||||||
currentBlock.hasOnce = true
|
currentBlock.hasOnce = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -396,6 +396,38 @@ describe('defineCustomElement', () => {
|
||||||
expect(e.value).toBe('hi')
|
expect(e.value).toBe('hi')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12214
|
||||||
|
test('Boolean prop with default true', async () => {
|
||||||
|
const E = defineCustomElement({
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return String(this.foo)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
customElements.define('my-el-default-true', E)
|
||||||
|
container.innerHTML = `<my-el-default-true></my-el-default-true>`
|
||||||
|
const e = container.childNodes[0] as HTMLElement & { foo: any },
|
||||||
|
shadowRoot = e.shadowRoot as ShadowRoot
|
||||||
|
expect(shadowRoot.innerHTML).toBe('true')
|
||||||
|
e.foo = undefined
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('true')
|
||||||
|
e.foo = false
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('false')
|
||||||
|
e.foo = null
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('null')
|
||||||
|
e.foo = ''
|
||||||
|
await nextTick()
|
||||||
|
expect(shadowRoot.innerHTML).toBe('true')
|
||||||
|
})
|
||||||
|
|
||||||
test('support direct setup function syntax with extra options', () => {
|
test('support direct setup function syntax with extra options', () => {
|
||||||
const E = defineCustomElement(
|
const E = defineCustomElement(
|
||||||
props => {
|
props => {
|
||||||
|
@ -1386,4 +1418,39 @@ describe('defineCustomElement', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(e.shadowRoot!.innerHTML).toBe(`false,boolean`)
|
expect(e.shadowRoot!.innerHTML).toBe(`false,boolean`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('hyphenated attr removal', async () => {
|
||||||
|
const E = defineCustomElement({
|
||||||
|
props: {
|
||||||
|
fooBar: {
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return this.fooBar
|
||||||
|
},
|
||||||
|
})
|
||||||
|
customElements.define('el-hyphenated-attr-removal', E)
|
||||||
|
const toggle = ref(true)
|
||||||
|
const Comp = {
|
||||||
|
render() {
|
||||||
|
return h('el-hyphenated-attr-removal', {
|
||||||
|
'foo-bar': toggle.value ? '' : null,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(h(Comp), container)
|
||||||
|
const el = container.children[0]
|
||||||
|
expect(el.hasAttribute('foo-bar')).toBe(true)
|
||||||
|
expect((el as any).outerHTML).toBe(
|
||||||
|
`<el-hyphenated-attr-removal foo-bar=""></el-hyphenated-attr-removal>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(el.hasAttribute('foo-bar')).toBe(false)
|
||||||
|
expect((el as any).outerHTML).toBe(
|
||||||
|
`<el-hyphenated-attr-removal></el-hyphenated-attr-removal>`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
defineCustomElement,
|
defineCustomElement,
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
onMounted,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
@ -350,6 +351,7 @@ describe('useCssVars', () => {
|
||||||
expect(() => render(h(App), root)).not.toThrow(TypeError)
|
expect(() => render(h(App), root)).not.toThrow(TypeError)
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(target.children.length).toBe(0)
|
expect(target.children.length).toBe(0)
|
||||||
|
expect(root.children[0].outerHTML.includes('data-v-owner')).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('with string style', async () => {
|
test('with string style', async () => {
|
||||||
|
@ -383,6 +385,44 @@ describe('useCssVars', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with delay mount child', async () => {
|
||||||
|
const state = reactive({ color: 'red' })
|
||||||
|
const value = ref(false)
|
||||||
|
const root = document.createElement('div')
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
const childEl = root.children[0]
|
||||||
|
expect(getComputedStyle(childEl!).getPropertyValue(`--color`)).toBe(
|
||||||
|
`red`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
return () => h('div', { id: 'childId' })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
useCssVars(() => state)
|
||||||
|
return () => (value.value ? h(Child) : [h('span')])
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render(h(App), root)
|
||||||
|
await nextTick()
|
||||||
|
// css vars use with fallback tree
|
||||||
|
for (const c of [].slice.call(root.children as any)) {
|
||||||
|
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe(`red`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// mount child
|
||||||
|
value.value = true
|
||||||
|
await nextTick()
|
||||||
|
for (const c of [].slice.call(root.children as any)) {
|
||||||
|
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe(`red`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// #8826
|
// #8826
|
||||||
test('with custom element', async () => {
|
test('with custom element', async () => {
|
||||||
const state = reactive({ color: 'red' })
|
const state = reactive({ color: 'red' })
|
||||||
|
@ -404,4 +444,25 @@ describe('useCssVars', () => {
|
||||||
`<css-vars-ce style="--color: red;"></css-vars-ce>`,
|
`<css-vars-ce style="--color: red;"></css-vars-ce>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should set vars before child component onMount hook', () => {
|
||||||
|
const state = reactive({ color: 'red' })
|
||||||
|
const root = document.createElement('div')
|
||||||
|
let colorInOnMount
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
useCssVars(() => state)
|
||||||
|
onMounted(() => {
|
||||||
|
colorInOnMount = (
|
||||||
|
root.children[0] as HTMLElement
|
||||||
|
).style.getPropertyValue(`--color`)
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
render(h(App), root)
|
||||||
|
expect(colorInOnMount).toBe(`red`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-dom",
|
"name": "@vue/runtime-dom",
|
||||||
"version": "3.5.11",
|
"version": "3.5.13",
|
||||||
"description": "@vue/runtime-dom",
|
"description": "@vue/runtime-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-dom.esm-bundler.js",
|
"module": "dist/runtime-dom.esm-bundler.js",
|
||||||
|
|
|
@ -505,6 +505,8 @@ export class VueElement
|
||||||
}
|
}
|
||||||
// reflect
|
// reflect
|
||||||
if (shouldReflect) {
|
if (shouldReflect) {
|
||||||
|
const ob = this._ob
|
||||||
|
ob && ob.disconnect()
|
||||||
if (val === true) {
|
if (val === true) {
|
||||||
this.setAttribute(hyphenate(key), '')
|
this.setAttribute(hyphenate(key), '')
|
||||||
} else if (typeof val === 'string' || typeof val === 'number') {
|
} else if (typeof val === 'string' || typeof val === 'number') {
|
||||||
|
@ -512,6 +514,7 @@ export class VueElement
|
||||||
} else if (!val) {
|
} else if (!val) {
|
||||||
this.removeAttribute(hyphenate(key))
|
this.removeAttribute(hyphenate(key))
|
||||||
}
|
}
|
||||||
|
ob && ob.observe(this, { attributes: true })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,13 @@ export function resolveTransitionProps(
|
||||||
onAppearCancelled = onEnterCancelled,
|
onAppearCancelled = onEnterCancelled,
|
||||||
} = baseProps
|
} = baseProps
|
||||||
|
|
||||||
const finishEnter = (el: Element, isAppear: boolean, done?: () => void) => {
|
const finishEnter = (
|
||||||
|
el: Element & { _enterCancelled?: boolean },
|
||||||
|
isAppear: boolean,
|
||||||
|
done?: () => void,
|
||||||
|
isCancelled?: boolean,
|
||||||
|
) => {
|
||||||
|
el._enterCancelled = isCancelled
|
||||||
removeTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
removeTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
||||||
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
|
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
|
||||||
done && done()
|
done && done()
|
||||||
|
@ -240,7 +246,10 @@ export function resolveTransitionProps(
|
||||||
},
|
},
|
||||||
onEnter: makeEnterHook(false),
|
onEnter: makeEnterHook(false),
|
||||||
onAppear: makeEnterHook(true),
|
onAppear: makeEnterHook(true),
|
||||||
onLeave(el: Element & { _isLeaving?: boolean }, done) {
|
onLeave(
|
||||||
|
el: Element & { _isLeaving?: boolean; _enterCancelled?: boolean },
|
||||||
|
done,
|
||||||
|
) {
|
||||||
el._isLeaving = true
|
el._isLeaving = true
|
||||||
const resolve = () => finishLeave(el, done)
|
const resolve = () => finishLeave(el, done)
|
||||||
addTransitionClass(el, leaveFromClass)
|
addTransitionClass(el, leaveFromClass)
|
||||||
|
@ -249,9 +258,14 @@ export function resolveTransitionProps(
|
||||||
}
|
}
|
||||||
// add *-leave-active class before reflow so in the case of a cancelled enter transition
|
// add *-leave-active class before reflow so in the case of a cancelled enter transition
|
||||||
// the css will not get the final state (#10677)
|
// the css will not get the final state (#10677)
|
||||||
addTransitionClass(el, leaveActiveClass)
|
if (!el._enterCancelled) {
|
||||||
// force reflow so *-leave-from classes immediately take effect (#2593)
|
// force reflow so *-leave-from classes immediately take effect (#2593)
|
||||||
forceReflow()
|
forceReflow()
|
||||||
|
addTransitionClass(el, leaveActiveClass)
|
||||||
|
} else {
|
||||||
|
addTransitionClass(el, leaveActiveClass)
|
||||||
|
forceReflow()
|
||||||
|
}
|
||||||
nextFrame(() => {
|
nextFrame(() => {
|
||||||
if (!el._isLeaving) {
|
if (!el._isLeaving) {
|
||||||
// cancelled
|
// cancelled
|
||||||
|
@ -269,11 +283,11 @@ export function resolveTransitionProps(
|
||||||
callHook(onLeave, [el, resolve])
|
callHook(onLeave, [el, resolve])
|
||||||
},
|
},
|
||||||
onEnterCancelled(el) {
|
onEnterCancelled(el) {
|
||||||
finishEnter(el, false)
|
finishEnter(el, false, undefined, true)
|
||||||
callHook(onEnterCancelled, [el])
|
callHook(onEnterCancelled, [el])
|
||||||
},
|
},
|
||||||
onAppearCancelled(el) {
|
onAppearCancelled(el) {
|
||||||
finishEnter(el, true)
|
finishEnter(el, true, undefined, true)
|
||||||
callHook(onAppearCancelled, [el])
|
callHook(onAppearCancelled, [el])
|
||||||
},
|
},
|
||||||
onLeaveCancelled(el) {
|
onLeaveCancelled(el) {
|
||||||
|
|
|
@ -160,7 +160,7 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
||||||
|
|
||||||
function setChecked(
|
function setChecked(
|
||||||
el: HTMLInputElement,
|
el: HTMLInputElement,
|
||||||
{ value }: DirectiveBinding,
|
{ value, oldValue }: 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
|
||||||
|
@ -173,6 +173,7 @@ function setChecked(
|
||||||
} else if (isSet(value)) {
|
} else if (isSet(value)) {
|
||||||
checked = value.has(vnode.props!.value)
|
checked = value.has(vnode.props!.value)
|
||||||
} else {
|
} else {
|
||||||
|
if (value === oldValue) return
|
||||||
checked = looseEqual(value, getCheckboxValue(el, true))
|
checked = looseEqual(value, getCheckboxValue(el, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,14 @@ import {
|
||||||
Static,
|
Static,
|
||||||
type VNode,
|
type VNode,
|
||||||
getCurrentInstance,
|
getCurrentInstance,
|
||||||
onBeforeMount,
|
onBeforeUpdate,
|
||||||
onMounted,
|
onMounted,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
|
queuePostFlushCb,
|
||||||
warn,
|
warn,
|
||||||
watchPostEffect,
|
watch,
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { ShapeFlags } from '@vue/shared'
|
import { NOOP, ShapeFlags } from '@vue/shared'
|
||||||
|
|
||||||
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
|
export const CSS_VAR_TEXT: unique symbol = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
|
||||||
/**
|
/**
|
||||||
|
@ -48,11 +49,15 @@ export function useCssVars(getter: (ctx: any) => Record<string, string>): void {
|
||||||
updateTeleports(vars)
|
updateTeleports(vars)
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
// handle cases where child component root is affected
|
||||||
watchPostEffect(setVars)
|
// and triggers reflow in onMounted
|
||||||
|
onBeforeUpdate(() => {
|
||||||
|
queuePostFlushCb(setVars)
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
// run setVars synchronously here, but run as post-effect on changes
|
||||||
|
watch(setVars, NOOP, { flush: 'post' })
|
||||||
const ob = new MutationObserver(setVars)
|
const ob = new MutationObserver(setVars)
|
||||||
ob.observe(instance.subTree.el!.parentNode, { childList: true })
|
ob.observe(instance.subTree.el!.parentNode, { childList: true })
|
||||||
onUnmounted(() => ob.disconnect())
|
onUnmounted(() => ob.disconnect())
|
||||||
|
|
|
@ -8,6 +8,7 @@ export function patchDOMProp(
|
||||||
key: string,
|
key: string,
|
||||||
value: any,
|
value: any,
|
||||||
parentComponent: any,
|
parentComponent: any,
|
||||||
|
attrName?: string,
|
||||||
): void {
|
): void {
|
||||||
// __UNSAFE__
|
// __UNSAFE__
|
||||||
// Reason: potentially setting innerHTML.
|
// Reason: potentially setting innerHTML.
|
||||||
|
@ -106,5 +107,5 @@ export function patchDOMProp(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
needRemove && el.removeAttribute(key)
|
needRemove && el.removeAttribute(attrName || key)
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
||||||
(el as VueElement)._isVueCE &&
|
(el as VueElement)._isVueCE &&
|
||||||
(/[A-Z]/.test(key) || !isString(nextValue))
|
(/[A-Z]/.test(key) || !isString(nextValue))
|
||||||
) {
|
) {
|
||||||
patchDOMProp(el, camelize(key), nextValue, parentComponent)
|
patchDOMProp(el, camelize(key), nextValue, parentComponent, key)
|
||||||
} 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
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
|
|
||||||
import { createBuffer } from '../src/render'
|
import { createBuffer as _createBuffer } from '../src/render'
|
||||||
|
|
||||||
|
// move to local const to avoid import access overhead
|
||||||
|
// https://github.com/vitest-dev/vitest/issues/6903
|
||||||
|
const createBuffer = _createBuffer
|
||||||
|
|
||||||
describe('createBuffer', () => {
|
describe('createBuffer', () => {
|
||||||
let stringBuffer = createBuffer()
|
let stringBuffer = createBuffer()
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
|
|
||||||
import { type SSRBuffer, createBuffer } from '../src/render'
|
import { type SSRBuffer, createBuffer } from '../src/render'
|
||||||
import { unrollBuffer } from '../src/renderToString'
|
import { unrollBuffer as _unrollBuffer } from '../src/renderToString'
|
||||||
|
|
||||||
|
// move to local const to avoid import access overhead
|
||||||
|
// https://github.com/vitest-dev/vitest/issues/6903
|
||||||
|
const unrollBuffer = _unrollBuffer
|
||||||
|
|
||||||
function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
function createSyncBuffer(levels: number, itemsPerLevel: number): SSRBuffer {
|
||||||
const buffer = createBuffer()
|
const buffer = createBuffer()
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue