chore: Merge branch 'minor' into edison/feat/vaporHydration
ci / test (push) Has been cancelled Details
ci / continuous-release (push) Has been cancelled Details

This commit is contained in:
daiwei 2025-07-18 11:48:22 +08:00
commit cb7779b0d9
164 changed files with 5647 additions and 3198 deletions

View File

@ -38,7 +38,6 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
### Pull Request Checklist ### Pull Request Checklist
- Vue core has two primary work branches: `main` and `minor`. - Vue core has two primary work branches: `main` and `minor`.
- If your pull request is a feature that adds new API surface, it should be submitted against the `minor` branch. - If your pull request is a feature that adds new API surface, it should be submitted against the `minor` branch.
- Otherwise, it should be submitted against the `main` branch. - Otherwise, it should be submitted against the `main` branch.
@ -46,12 +45,10 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
- [Make sure to tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). This allows us to directly make minor edits / refactors and saves a lot of time. - [Make sure to tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). This allows us to directly make minor edits / refactors and saves a lot of time.
- If adding a new feature: - If adding a new feature:
- Add accompanying test case. - Add accompanying test case.
- Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it. - Provide a convincing reason to add this feature. Ideally, you should open a suggestion issue first and have it approved before working on it.
- If fixing a bug: - If fixing a bug:
- If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update entities encoding/decoding (fix #3899)`. - If you are resolving a special issue, add `(fix #xxxx[,#xxxx])` (#xxxx is the issue id) in your PR title for a better release log, e.g. `update entities encoding/decoding (fix #3899)`.
- Provide a detailed description of the bug in the PR. Live demo preferred. - Provide a detailed description of the bug in the PR. Live demo preferred.
- Add appropriate test coverage if applicable. You can check the coverage of your code addition by running `nr test-coverage`. - Add appropriate test coverage if applicable. You can check the coverage of your code addition by running `nr test-coverage`.
@ -69,9 +66,7 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
- The PR should fix the intended bug **only** and not introduce unrelated changes. This includes unnecessary refactors - a PR should focus on the fix and not code style, this makes it easier to trace changes in the future. - The PR should fix the intended bug **only** and not introduce unrelated changes. This includes unnecessary refactors - a PR should focus on the fix and not code style, this makes it easier to trace changes in the future.
- Consider the performance / size impact of the changes, and whether the bug being fixes justifies the cost. If the bug being fixed is a very niche edge case, we should try to minimize the size / perf cost to make it worthwhile. - Consider the performance / size impact of the changes, and whether the bug being fixes justifies the cost. If the bug being fixed is a very niche edge case, we should try to minimize the size / perf cost to make it worthwhile.
- Is the code perf-sensitive (e.g. in "hot paths" like component updates or the vdom patch function?) - Is the code perf-sensitive (e.g. in "hot paths" like component updates or the vdom patch function?)
- If the branch is dev-only, performance is less of a concern. - If the branch is dev-only, performance is less of a concern.
- Check how much extra bundle size the change introduces. - Check how much extra bundle size the change introduces.
@ -265,7 +260,6 @@ This repository employs a [monorepo](https://en.wikipedia.org/wiki/Monorepo) set
- `vue`: The public facing "full build" which includes both the runtime AND the compiler. - `vue`: The public facing "full build" which includes both the runtime AND the compiler.
- Private utility packages: - Private utility packages:
- `dts-test`: Contains type-only tests against generated dts files. - `dts-test`: Contains type-only tests against generated dts files.
- `sfc-playground`: The playground continuously deployed at https://play.vuejs.org. To run the playground locally, use [`nr dev-sfc`](#nr-dev-sfc). - `sfc-playground`: The playground continuously deployed at https://play.vuejs.org. To run the playground locally, use [`nr dev-sfc`](#nr-dev-sfc).

View File

@ -48,7 +48,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Performance: if a refactor PR claims to improve performance, there should be benchmarks showcasing said performance unless the improvement is self-explanatory. - Performance: if a refactor PR claims to improve performance, there should be benchmarks showcasing said performance unless the improvement is self-explanatory.
- Code quality / stylistic PRs: we should be conservative on merging this type PRs because (1) they can be subjective in many cases, and (2) they often come with large git diffs, causing merge conflicts with other pending PRs, and leading to unwanted noise when tracing changes through git history. Use your best judgement on this type of PRs on whether they are worth it. - Code quality / stylistic PRs: we should be conservative on merging this type PRs because (1) they can be subjective in many cases, and (2) they often come with large git diffs, causing merge conflicts with other pending PRs, and leading to unwanted noise when tracing changes through git history. Use your best judgement on this type of PRs on whether they are worth it.
- For PRs in this category that are approved, do not merge immediately. Group them before releasing a new minor, after all feature-oriented PRs are merged. - For PRs in this category that are approved, do not merge immediately. Group them before releasing a new minor, after all feature-oriented PRs are merged.
### Reviewing a Feature ### Reviewing a Feature
@ -56,7 +55,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Feature PRs should always have clear context and explanation on why the feature should be added, ideally in the form of an RFC. If the PR doesn't explain what real-world problem it is solving, ask the contributor to clarify. - Feature PRs should always have clear context and explanation on why the feature should be added, ideally in the form of an RFC. If the PR doesn't explain what real-world problem it is solving, ask the contributor to clarify.
- Decide if the feature should require an RFC process. The line isn't always clear, but a rough criteria is whether it is augmenting an existing API vs. adding a new API. Some examples: - Decide if the feature should require an RFC process. The line isn't always clear, but a rough criteria is whether it is augmenting an existing API vs. adding a new API. Some examples:
- Adding a new built-in component or directive is "significant" and definitely requires an RFC. - Adding a new built-in component or directive is "significant" and definitely requires an RFC.
- Template syntax additions like adding a new `v-on` modifier or a new `v-bind` syntax sugar are "substantial". It would be nice to have an RFC for it, but a detailed explanation on the use case and reasoning behind the design directly in the PR itself can be acceptable. - Template syntax additions like adding a new `v-on` modifier or a new `v-bind` syntax sugar are "substantial". It would be nice to have an RFC for it, but a detailed explanation on the use case and reasoning behind the design directly in the PR itself can be acceptable.
- Small, low-impact additions like exposing a new utility type or adding a new app config option can be self-explanatory, but should still provide enough context in the PR. - Small, low-impact additions like exposing a new utility type or adding a new app config option can be self-explanatory, but should still provide enough context in the PR.
@ -70,7 +68,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Implementation: code style should be consistent with the rest of the codebase, follow common best practices. Prefer code that is boring but easy to understand over "clever" code. - Implementation: code style should be consistent with the rest of the codebase, follow common best practices. Prefer code that is boring but easy to understand over "clever" code.
- Size: bundle size matters. We have a GitHub action that compares the size change for every PR. We should always aim to realize the desired changes with the smallest amount of code size increase. - Size: bundle size matters. We have a GitHub action that compares the size change for every PR. We should always aim to realize the desired changes with the smallest amount of code size increase.
- Sometimes we need to compare the size increase vs. perceived benefits to decide whether a change is justifiable. Also take extra care to make sure added code can be tree-shaken if not needed. - Sometimes we need to compare the size increase vs. perceived benefits to decide whether a change is justifiable. Also take extra care to make sure added code can be tree-shaken if not needed.
- Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable. - Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable.
@ -80,7 +77,6 @@ Depending on the type of the PR, different considerations need to be taken into
- Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`. - Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in @vue/shared are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`.
- Performance - Performance
- Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code. - Be careful about code changes in "hot paths", in particular the Virtual DOM renderer (`runtime-core/src/renderer.ts`) and component instantiation code.
- Potential Breakage - Potential Breakage

View File

@ -31,4 +31,4 @@ jobs:
- name: Run prettier - name: Run prettier
run: pnpm run format run: pnpm run format
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef - uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27

View File

@ -130,5 +130,8 @@ jobs:
- name: Run prettier - name: Run prettier
run: pnpm run format-check run: pnpm run format-check
- name: Run tsc
run: pnpm run check
- name: Run type declaration tests - name: Run type declaration tests
run: pnpm run test-dts run: pnpm run test-dts

View File

@ -1,541 +1,128 @@
## [3.5.14](https://github.com/vuejs/core/compare/v3.5.13...v3.5.14) (2025-05-15) # [3.6.0-alpha.1](https://github.com/vuejs/core/compare/v3.5.17...v3.6.0-alpha.1) (2025-07-12)
### Bug Fixes
* **compat:** correct deprecation message for v-bind.sync usage ([#13137](https://github.com/vuejs/core/issues/13137)) ([466b30f](https://github.com/vuejs/core/commit/466b30f4049ec89fb282624ec17d1a93472ab93f)), closes [#13133](https://github.com/vuejs/core/issues/13133)
* **compiler-core:** remove slot cache from parent renderCache during unmounting ([#13215](https://github.com/vuejs/core/issues/13215)) ([5d166f3](https://github.com/vuejs/core/commit/5d166f3796a03a497435fc079c6a83a4e9c6cf52))
* **compiler-sfc:** fix scope handling for props destructure in function parameters and catch clauses ([8e34357](https://github.com/vuejs/core/commit/8e3435779a667de485cf9efd78667d0ca14c5f84)), closes [#12790](https://github.com/vuejs/core/issues/12790)
* **compiler-sfc:** treat the return value of `useTemplateRef` as a definite ref ([#13197](https://github.com/vuejs/core/issues/13197)) ([8ae1122](https://github.com/vuejs/core/commit/8ae11226e8ee938615e17c7b81dc38ae3f7cefb9))
* **compiler:** fix spelling error in domTagConfig ([#13043](https://github.com/vuejs/core/issues/13043)) ([388295b](https://github.com/vuejs/core/commit/388295b27f3cc69eba25d325bbe60a36a3df831a))
* **customFormatter:** properly accessing ref value during debugger ([#12948](https://github.com/vuejs/core/issues/12948)) ([fdbd026](https://github.com/vuejs/core/commit/fdbd02658301dd794fe0c84f0018d080a07fca9f))
* **hmr/teleport:** adjust static children traversal for HMR in dev mode ([#12819](https://github.com/vuejs/core/issues/12819)) ([5e37dd0](https://github.com/vuejs/core/commit/5e37dd009562bcd8080a200c32abde2d6e4f0305)), closes [#12816](https://github.com/vuejs/core/issues/12816)
* **hmr:** avoid hydration for hmr root reload ([#12450](https://github.com/vuejs/core/issues/12450)) ([1f98a9c](https://github.com/vuejs/core/commit/1f98a9c493d01c21befa90107f0593bc92a58932)), closes [vitejs/vite-plugin-vue#146](https://github.com/vitejs/vite-plugin-vue/issues/146) [vitejs/vite-plugin-vue#477](https://github.com/vitejs/vite-plugin-vue/issues/477)
* **hmr:** avoid hydration for hmr updating ([#12262](https://github.com/vuejs/core/issues/12262)) ([9c4dbbc](https://github.com/vuejs/core/commit/9c4dbbc5185125835ad3e49baba303bd54676111)), closes [#7706](https://github.com/vuejs/core/issues/7706) [#8170](https://github.com/vuejs/core/issues/8170)
* **reactivity:** ensure markRaw objects are not reactive ([#12824](https://github.com/vuejs/core/issues/12824)) ([295b5ec](https://github.com/vuejs/core/commit/295b5ec19b6a52c4a56652cc4d6e93a4ea7c14ed)), closes [#12807](https://github.com/vuejs/core/issues/12807)
* **reactivity:** ensure multiple effectScope on() and off() calls maintains correct active scope ([22dcbf3](https://github.com/vuejs/core/commit/22dcbf3e20eb84f69c8952f6f70d9990136a4a68)), closes [#12631](https://github.com/vuejs/core/issues/12631) [#12632](https://github.com/vuejs/core/issues/12632) [#12641](https://github.com/vuejs/core/issues/12641)
* **reactivity:** should not recompute if computed does not track reactive data ([#12341](https://github.com/vuejs/core/issues/12341)) ([0b23fd2](https://github.com/vuejs/core/commit/0b23fd23833cf085e7e112bf4435cfc9b360d072)), closes [#12337](https://github.com/vuejs/core/issues/12337)
* **runtime-core:** stop tracking deps in setRef during unmount ([#13210](https://github.com/vuejs/core/issues/13210)) ([016c472](https://github.com/vuejs/core/commit/016c472bd2e7604b21c69dee1da8545ce26e4d2f))
* **runtime-core:** update __vnode of static nodes when patching along the optimized path ([#13223](https://github.com/vuejs/core/issues/13223)) ([b3ecee3](https://github.com/vuejs/core/commit/b3ecee3da8ed5c55dea89ce6b4b376b2b722b018))
* **runtime-core:** inherit comment nodes during block patch in production build ([#10748](https://github.com/vuejs/core/issues/10748)) ([6264505](https://github.com/vuejs/core/commit/626450590d81f79117b34d2a73073b1dc8f551bd)), closes [#10747](https://github.com/vuejs/core/issues/10747) [#12650](https://github.com/vuejs/core/issues/12650)
* **runtime-core:** prevent unmounted vnode from being inserted during transition leave ([#12862](https://github.com/vuejs/core/issues/12862)) ([d6a6ec1](https://github.com/vuejs/core/commit/d6a6ec13ce521683bfb2a22932778ef7b51f8600)), closes [#12860](https://github.com/vuejs/core/issues/12860)
* **runtime-core:** respect immutability for readonly reactive arrays in `v-for` ([#13091](https://github.com/vuejs/core/issues/13091)) ([3f27c58](https://github.com/vuejs/core/commit/3f27c58ffbd4309df369bc89493fdc284dc540bb)), closes [#13087](https://github.com/vuejs/core/issues/13087)
* **runtime-dom:** always treat autocorrect as attribute ([#13001](https://github.com/vuejs/core/issues/13001)) ([1499135](https://github.com/vuejs/core/commit/1499135c227236e037bb746beeb777941b0b58ff)), closes [#5705](https://github.com/vuejs/core/issues/5705)
* **slots:** properly warn if slot invoked in setup ([#12195](https://github.com/vuejs/core/issues/12195)) ([9196222](https://github.com/vuejs/core/commit/9196222ae1d63b52b35ac5fbf5e71494587ccf05)), closes [#12194](https://github.com/vuejs/core/issues/12194)
* **ssr:** properly init slots during ssr rendering ([#12441](https://github.com/vuejs/core/issues/12441)) ([2206cd2](https://github.com/vuejs/core/commit/2206cd235a1627c540e795e378b7564a55b47313)), closes [#12438](https://github.com/vuejs/core/issues/12438)
* **transition:** fix KeepAlive with transition out-in mode behavior in production ([#12468](https://github.com/vuejs/core/issues/12468)) ([343c891](https://github.com/vuejs/core/commit/343c89122448719bd6ed6bd9de986dfb2721d6bf)), closes [#12465](https://github.com/vuejs/core/issues/12465)
* **TransitionGroup:** reset prevChildren to prevent memory leak ([#13183](https://github.com/vuejs/core/issues/13183)) ([8b848cb](https://github.com/vuejs/core/commit/8b848cbbd2af337d23e19e202f9ab433f8580855)), closes [#13181](https://github.com/vuejs/core/issues/13181)
* **types:** allow return any for Options API lifecycle hooks ([#5914](https://github.com/vuejs/core/issues/5914)) ([06310e8](https://github.com/vuejs/core/commit/06310e82f5bed62d1b9733dcb18cd8d6edc988de))
* **types:** the directive's modifiers should be optional ([#12605](https://github.com/vuejs/core/issues/12605)) ([10e54dc](https://github.com/vuejs/core/commit/10e54dcc86a7967f3196d96200bcbd1d3d42082f))
* **typos:** fix comments referencing transformElement.ts ([#12551](https://github.com/vuejs/core/issues/12551))[ci-skip] ([11c053a](https://github.com/vuejs/core/commit/11c053a5429ad0d27a0e2c78b6b026ea00ace116))
### Features ### Features
* **types:** add type TemplateRef ([#12645](https://github.com/vuejs/core/issues/12645)) ([636a861](https://github.com/vuejs/core/commit/636a8619f06c71dfd79f7f6412fd130c4f84226f)) - **vapor mode** ([#12359](https://github.com/vuejs/core/issues/12359)) ([bfe5ce3](https://github.com/vuejs/core/commit/bfe5ce309c6fc16bb49cca78e141862bc12708ac))
## [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)
* **reactivity:** 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)
Please see [About Vapor Mode](#about-vapor-mode) section below for details.
### Performance Improvements ### 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)) - **reactivity:** refactor reactivity core by porting [alien-signals](https://github.com/stackblitz/alien-signals) ([#12349](https://github.com/vuejs/core/issues/12349)) ([313dc61](https://github.com/vuejs/core/commit/313dc61bef59e6869aaec9b5ea47c0bf9044a3fc))
* **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 ### 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) - **css-vars:** nullish v-bind in style should not lead to unexpected inheritance ([#12461](https://github.com/vuejs/core/issues/12461)) ([c85f1b5](https://github.com/vuejs/core/commit/c85f1b5a132eb8ec25f71b250e25e65a5c20964f)), closes [#12434](https://github.com/vuejs/core/issues/12434) [#12439](https://github.com/vuejs/core/issues/12439) [#7474](https://github.com/vuejs/core/issues/7474) [#7475](https://github.com/vuejs/core/issues/7475)
* **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)) - **reactivity:** ensure multiple effectScope `on()` and `off()` calls maintains correct active scope ([#12641](https://github.com/vuejs/core/issues/12641)) ([679cbdf](https://github.com/vuejs/core/commit/679cbdf4806cf8c325098f2b579abab60fffb1bb))
* **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)) - **reactivity:** queuing effects in an array ([#13078](https://github.com/vuejs/core/issues/13078)) ([826550c](https://github.com/vuejs/core/commit/826550cd629c59dd91aeb5abdbe101a483497358))
* **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) - **reactivity:** toRefs should be allowed on plain objects ([ac43b11](https://github.com/vuejs/core/commit/ac43b118975b17d7ce7d9e6886f8806af11bee55))
* **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) - **scheduler:** improve error handling in job flushing ([#13587](https://github.com/vuejs/core/issues/13587)) ([94b2ddc](https://github.com/vuejs/core/commit/94b2ddc6f97170f4169d9d81b963c6bcaab08be2))
* **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) - **scheduler:** recover nextTick from error in post flush cb ([2bbb6d2](https://github.com/vuejs/core/commit/2bbb6d2fc56896e64a32b4421822d12bde2bb6e8))
* **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)
### About Vapor Mode
### Performance Improvements Vapor Mode is a new compilation mode for Vue Single-File Components (SFC) with the goal of reducing baseline bundle size and improved performance. It is 100% opt-in, and supports a subset of existing Vue APIs with mostly identical behavior.
* **reactivity:** avoid unnecessary recursion in removeSub ([#12135](https://github.com/vuejs/core/issues/12135)) ([ec917cf](https://github.com/vuejs/core/commit/ec917cfdb9d0169cd0835d3a0e28244242657dc9)) Vapor Mode has demonstrated the same level of performance with Solid and Svelte 5 in [3rd party benchmarks](https://github.com/krausest/js-framework-benchmark).
#### General Stability Notes
Vapor Mode is available starting in Vue 3.6 alpha. Please note it is still incomplete and unstable during the alpha phase. The current focus is making it available for wider stability and compatibility testing. For now, we recommend using it for the following cases:
## [3.5.11](https://github.com/vuejs/core/compare/v3.5.10...v3.5.11) (2024-10-03) - Partial usage in existing apps, e.g. implementing a perf-sensitive sub page in Vapor Mode.
- Build small new apps entirely in Vapor Mode.
We do not recommend migrating existing components to Vapor Mode yet.
### Bug Fixes #### Pending Features
* **compiler-sfc:** do not skip `TSSatisfiesExpression` when transforming props destructure ([#12062](https://github.com/vuejs/core/issues/12062)) ([2328b05](https://github.com/vuejs/core/commit/2328b051f4efa1f1394b7d4e73b7c3f76e430e7c)), closes [#12061](https://github.com/vuejs/core/issues/12061) Things that do not work in this version yet:
* **reactivity:** prevent overwriting `next` property during batch processing ([#12075](https://github.com/vuejs/core/issues/12075)) ([d3f5e6e](https://github.com/vuejs/core/commit/d3f5e6e5319b4ffaa55ca9a2ea3d95d78e76fa58)), closes [#12072](https://github.com/vuejs/core/issues/12072)
* **scheduler:** job ordering when the post queue is flushing ([#12090](https://github.com/vuejs/core/issues/12090)) ([577edca](https://github.com/vuejs/core/commit/577edca8e7795436efd710d1c289ea8ea2642b0e))
* **types:** correctly infer `TypeProps` when it is `any` ([#12073](https://github.com/vuejs/core/issues/12073)) ([57315ab](https://github.com/vuejs/core/commit/57315ab9688c9741a271d1075bbd28cbe5f71e2f)), closes [#12058](https://github.com/vuejs/core/issues/12058)
* **types:** should not intersect `PublicProps` with `Props` ([#12077](https://github.com/vuejs/core/issues/12077)) ([6f85894](https://github.com/vuejs/core/commit/6f8589437635706f825ccec51800effba1d2bf5f))
* **types:** infer the first generic type of `Ref` correctly ([#12094](https://github.com/vuejs/core/issues/12094)) ([c97bb84](https://github.com/vuejs/core/commit/c97bb84d0b0a16b012f886b6498e924415ed63e5))
- SSR hydration\* (which means it does not work with Nuxt yet)
- Async Component\*
- Transition\*
- KeepAlive\*
- Suspense
Features marked with \* have pending PRs which will be merged during the alpha phase.
## [3.5.10](https://github.com/vuejs/core/compare/v3.5.9...v3.5.10) (2024-09-27) #### Opting in to Vapor Mode
Vapor Mode only works for Single File Components using `<script setup>`. To opt-in, add the `vapor` attribute to `<script setup>`:
### Bug Fixes ```vue
<script setup vapor>
// ...
</script>
```
* **custom-element:** properly set kebab-case props on Vue custom elements ([ea3efa0](https://github.com/vuejs/core/commit/ea3efa09e008918c1d9ba7226833a8b1a7a57244)), closes [#12030](https://github.com/vuejs/core/issues/12030) [#12032](https://github.com/vuejs/core/issues/12032) Vapor Mode components are usable in two scenarios:
* **reactivity:** fix nested batch edge case ([93c95dd](https://github.com/vuejs/core/commit/93c95dd4cd416503f43a98a1455f62658d22b0b2))
* **reactivity:** only clear notified flags for computed in first batch iteration ([aa9ef23](https://github.com/vuejs/core/commit/aa9ef2386a0cd39a174e5a887ec2b1a3525034fc)), closes [#12045](https://github.com/vuejs/core/issues/12045)
* **types/ref:** handle nested refs in UnwrapRef ([#12049](https://github.com/vuejs/core/issues/12049)) ([e2c19c2](https://github.com/vuejs/core/commit/e2c19c20cfee9788519a80c0e53e216b78505994)), closes [#12044](https://github.com/vuejs/core/issues/12044)
1. Inside a Vapor app instance create via `createVaporApp`. Apps created this way avoids pulling in the Virtual DOM runtime code and allows bundle baseline size to be drastically reduced.
2. To use Vapor components in a VDOM app instance created via `createApp`, the `vaporInteropPlugin` must be installed:
## [3.5.9](https://github.com/vuejs/core/compare/v3.5.8...v3.5.9) (2024-09-26) ```js
import { createApp, vaporInteropPlugin } from 'vue'
import App from './App.vue'
createApp(App)
.use(vaporInteropPlugin) // enable vapor interop
.mount('#app')
```
### Bug Fixes A Vapor app instance can also install `vaporInteropPlugin` to allow vdom components to be used inside, but it will pull in the vdom runtime and offset the benefits of a smaller bundle.
* **reactivity:** fix property dep removal regression ([6001e5c](https://github.com/vuejs/core/commit/6001e5c81a05c894586f9287fbd991677bdd0455)), closes [#12020](https://github.com/vuejs/core/issues/12020) [#12021](https://github.com/vuejs/core/issues/12021) #### VDOM Interop Limitations
* **reactivity:** fix recursive sync watcher on computed edge case ([10ff159](https://github.com/vuejs/core/commit/10ff15924053d9bd95ad706f78ce09e288213fcf)), closes [#12033](https://github.com/vuejs/core/issues/12033) [#12037](https://github.com/vuejs/core/issues/12037)
* **runtime-core:** avoid rendering plain object as VNode ([#12038](https://github.com/vuejs/core/issues/12038)) ([cb34b28](https://github.com/vuejs/core/commit/cb34b28a4a9bf868be4785b001c526163eda342e)), closes [#12035](https://github.com/vuejs/core/issues/12035) [vitejs/vite-plugin-vue#353](https://github.com/vitejs/vite-plugin-vue/issues/353)
* **runtime-core:** make useId() always return a string ([a177092](https://github.com/vuejs/core/commit/a177092754642af2f98c33a4feffe8f198c3c950))
* **types:** correct type inference of union event names ([#12022](https://github.com/vuejs/core/issues/12022)) ([4da6881](https://github.com/vuejs/core/commit/4da688141d9e7c15b622c289deaa81b11845b2c7))
* **vue:** properly cache runtime compilation ([#12019](https://github.com/vuejs/core/issues/12019)) ([fa0ba24](https://github.com/vuejs/core/commit/fa0ba24b3ace02d7ecab65e57c2bea89a2550dcb))
When the interop plugin is installed, Vapor and non-Vapor components can be nested inside each other. This currently covers standard props, events, and slots usage, but does not yet account for all possible edge cases. For example, there will most likely still be rough edges when using a VDOM-based component library in Vapor Mode.
This is expected to improve over time, but in general, we recommend having distinct "regions" in your app where it's one mode or another, and avoid mixed nesting as much as possible.
## [3.5.8](https://github.com/vuejs/core/compare/v3.5.7...v3.5.8) (2024-09-22) In the future, we may provide support tooling to enforce Vapor usage boundaries in codebases.
#### Feature Compatibility
### Bug Fixes By design, Vapor Mode supports a **subset** of existing Vue features. For the supported subset, we aim to deliver the exact same behavior per API specifications. At the same time, this means there are some features that are explicitly not supported in Vapor Mode:
* **reactivity:** do not remove dep from depsMap when cleaning up deps of computed ([#11995](https://github.com/vuejs/core/issues/11995)) ([0267a58](https://github.com/vuejs/core/commit/0267a588017eee4951ac2a877fe1ccae84cad905)) - Options API
- `app.config.globalProperties`
- `getCurrentInstance()` returns `null` in Vapor components
- Implicit instance properties like `$slots` and `$props` are not available in Vapor template expressions
- `@vue:xxx` per-element lifecycle events
Custom directives in Vapor also have a different interface:
```ts
type VaporDirective = (
node: Element | VaporComponentInstance,
value?: () => any,
argument?: string,
modifiers?: DirectiveModifiers,
) => (() => void) | void
```
## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20) `value` is a reactive getter that returns the binding value. The user can set up reactive effects using `watchEffect` (auto released when component unmounts), and can optionally return a cleanup function. Example:
```ts
const MyDirective = (el, source) => {
watchEffect(() => {
el.textContent = source()
})
return () => console.log('cleanup')
}
```
### Bug Fixes #### Behavior Consistency
* **compile-core:** fix v-model with newlines edge case ([#11960](https://github.com/vuejs/core/issues/11960)) ([6224288](https://github.com/vuejs/core/commit/62242886d705ece88dbcad45bb78072ecccad0ca)), closes [#8306](https://github.com/vuejs/core/issues/8306)
* **compiler-sfc:** initialize scope with null prototype object ([#11963](https://github.com/vuejs/core/issues/11963)) ([215e154](https://github.com/vuejs/core/commit/215e15407294bf667261360218f975b88c99c2e5))
* **hydration:** avoid observing non-Element node ([#11954](https://github.com/vuejs/core/issues/11954)) ([7257e6a](https://github.com/vuejs/core/commit/7257e6a34200409b3fc347d3bb807e11e2785974)), closes [#11952](https://github.com/vuejs/core/issues/11952)
* **reactivity:** do not remove dep from depsMap when unsubbed by computed ([960706e](https://github.com/vuejs/core/commit/960706eebf73f08ebc9d5dd853a05def05e2c153))
* **reactivity:** fix dev-only memory leak by updating dep.subsHead on sub removal ([5c8b76e](https://github.com/vuejs/core/commit/5c8b76ed6cfbbcee4cbaac0b72beab7291044e4f)), closes [#11956](https://github.com/vuejs/core/issues/11956)
* **reactivity:** fix memory leak from dep instances of garbage collected objects ([235ea47](https://github.com/vuejs/core/commit/235ea4772ed2972914cf142da8b7ac1fb04f7585)), closes [#11979](https://github.com/vuejs/core/issues/11979) [#11971](https://github.com/vuejs/core/issues/11971)
* **reactivity:** fix triggerRef call on ObjectRefImpl returned by toRef ([#11986](https://github.com/vuejs/core/issues/11986)) ([b030c8b](https://github.com/vuejs/core/commit/b030c8bc7327877efb98aa3d9a58eb287a6ff07a)), closes [#11982](https://github.com/vuejs/core/issues/11982)
* **scheduler:** ensure recursive jobs can't be queued twice ([#11955](https://github.com/vuejs/core/issues/11955)) ([d18d6aa](https://github.com/vuejs/core/commit/d18d6aa1b20dc57a8103c51ec4d61e8e53ed936d))
* **ssr:** don't render comments in TransitionGroup ([#11961](https://github.com/vuejs/core/issues/11961)) ([a2f6ede](https://github.com/vuejs/core/commit/a2f6edeb02faedbb673c4bc5c6a59d9a79a37d07)), closes [#11958](https://github.com/vuejs/core/issues/11958)
* **transition:** respect `duration` setting even when it is `0` ([#11967](https://github.com/vuejs/core/issues/11967)) ([f927a4a](https://github.com/vuejs/core/commit/f927a4ae6f7c453f70ba89498ee0c737dc9866fd))
* **types:** correct type inference of all-optional props ([#11644](https://github.com/vuejs/core/issues/11644)) ([9eca65e](https://github.com/vuejs/core/commit/9eca65ee9871d1ac878755afa9a3eb1b02030350)), closes [#11733](https://github.com/vuejs/core/issues/11733) [vuejs/language-tools#4704](https://github.com/vuejs/language-tools/issues/4704)
### Performance Improvements
* **hydration:** avoid observer if element is in viewport ([#11639](https://github.com/vuejs/core/issues/11639)) ([e075dfa](https://github.com/vuejs/core/commit/e075dfad5c7649c6045e3711687ec888e7aa1a39))
## [3.5.6](https://github.com/vuejs/core/compare/v3.5.5...v3.5.6) (2024-09-16)
### Bug Fixes
* **compile-dom:** should be able to stringify mathML ([#11891](https://github.com/vuejs/core/issues/11891)) ([85c138c](https://github.com/vuejs/core/commit/85c138ced108268f7656b568dfd3036a1e0aae34))
* **compiler-sfc:** preserve old behavior when using withDefaults with desutructure ([8492c3c](https://github.com/vuejs/core/commit/8492c3c49a922363d6c77ef192c133a8fbce6514)), closes [#11930](https://github.com/vuejs/core/issues/11930)
* **reactivity:** avoid exponential perf cost and reduce call stack depth for deeply chained computeds ([#11944](https://github.com/vuejs/core/issues/11944)) ([c74bb8c](https://github.com/vuejs/core/commit/c74bb8c2dd9e82aaabb0a2a2b368e900929b513b)), closes [#11928](https://github.com/vuejs/core/issues/11928)
* **reactivity:** rely on dirty check only when computed has deps ([#11931](https://github.com/vuejs/core/issues/11931)) ([aa5dafd](https://github.com/vuejs/core/commit/aa5dafd2b55d42d6a29316a3bc91aea85c676a0b)), closes [#11929](https://github.com/vuejs/core/issues/11929)
* **watch:** `once` option should be ignored by watchEffect ([#11884](https://github.com/vuejs/core/issues/11884)) ([49fa673](https://github.com/vuejs/core/commit/49fa673493d93b77ddba2165ab6545bae84fd1ae))
* **watch:** unwatch should be callable during SSR ([#11925](https://github.com/vuejs/core/issues/11925)) ([2d6adf7](https://github.com/vuejs/core/commit/2d6adf78a047eed091db277ffbd9df0822fb0bdd)), closes [#11924](https://github.com/vuejs/core/issues/11924)
## [3.5.5](https://github.com/vuejs/core/compare/v3.5.4...v3.5.5) (2024-09-13)
### Bug Fixes
* **compiler-core:** fix handling of delimiterOpen in VPre ([#11915](https://github.com/vuejs/core/issues/11915)) ([706d4ac](https://github.com/vuejs/core/commit/706d4ac1d0210b2d9134b3228280187fe02fc971)), closes [#11913](https://github.com/vuejs/core/issues/11913)
* **compiler-dom:** fix stringify static edge for partially eligible chunks in cached parent ([1d99d61](https://github.com/vuejs/core/commit/1d99d61c1bd77f9ea6743f6214a82add8346a121)), closes [#11879](https://github.com/vuejs/core/issues/11879) [#11890](https://github.com/vuejs/core/issues/11890)
* **compiler-dom:** should ignore leading newline in <textarea> per spec ([3c4bf76](https://github.com/vuejs/core/commit/3c4bf7627649ec1e3220f8c4e4163c20d2afb367))
* **compiler-sfc:** nested css supports atrule and comment ([#11899](https://github.com/vuejs/core/issues/11899)) ([0e7bc71](https://github.com/vuejs/core/commit/0e7bc717e6640644f062957ec5031506f0dab215)), closes [#11896](https://github.com/vuejs/core/issues/11896)
* **custom-element:** handle nested customElement mount w/ shadowRoot false ([#11861](https://github.com/vuejs/core/issues/11861)) ([f2d8019](https://github.com/vuejs/core/commit/f2d801918841e7673ff3f048d0d895592a2f7e23)), closes [#11851](https://github.com/vuejs/core/issues/11851) [#11871](https://github.com/vuejs/core/issues/11871)
* **hmr:** reload async child wrapped in Suspense + KeepAlive ([#11907](https://github.com/vuejs/core/issues/11907)) ([10a2c60](https://github.com/vuejs/core/commit/10a2c6053bd30d160d0214bb3566f540187e6874)), closes [#11868](https://github.com/vuejs/core/issues/11868)
* **hydration:** fix mismatch of leading newline in `<textarea>` and `<pre>` ([a5f3c2e](https://github.com/vuejs/core/commit/a5f3c2eb4d2e7fae93ff93ce865b269f01cc825e)), closes [#11873](https://github.com/vuejs/core/issues/11873) [#11874](https://github.com/vuejs/core/issues/11874)
* **reactivity:** properly clean up deps, fix memory leak ([8ea5d6d](https://github.com/vuejs/core/commit/8ea5d6d6981ab7febda0be43c3c92b18869c3a2a)), closes [#11901](https://github.com/vuejs/core/issues/11901)
* **runtime-core:** properly update async component nested in KeepAlive ([#11917](https://github.com/vuejs/core/issues/11917)) ([7fe6c79](https://github.com/vuejs/core/commit/7fe6c795a1fc7ddcea5ad91a56141561192373ac)), closes [#11916](https://github.com/vuejs/core/issues/11916)
* **TransitionGroup:** not warn unkeyed text children with whitespece preserve ([#11888](https://github.com/vuejs/core/issues/11888)) ([7571f20](https://github.com/vuejs/core/commit/7571f20bc3d1854377a146f41d211e05bb68cd47)), closes [#11885](https://github.com/vuejs/core/issues/11885)
## [3.5.4](https://github.com/vuejs/core/compare/v3.5.3...v3.5.4) (2024-09-10)
### Bug Fixes
* **compiler-sfc:** correct scoped injection for nesting selector ([#11854](https://github.com/vuejs/core/issues/11854)) ([b1de75e](https://github.com/vuejs/core/commit/b1de75ed04626b6423085dfde91fb0cb481a25e8)), closes [#10567](https://github.com/vuejs/core/issues/10567)
* **reactivity:** fix markRaw error on already marked object ([#11864](https://github.com/vuejs/core/issues/11864)) ([67d6596](https://github.com/vuejs/core/commit/67d6596d40b1807b9cd8eb0d9282932ea77be3c0)), closes [#11862](https://github.com/vuejs/core/issues/11862)
* Revert "fix: Revert "fix(reactivity): self-referencing computed should refresh"" ([e596378](https://github.com/vuejs/core/commit/e596378e0be728dad7d60938449f3fa557ca2ec9))
* **runtime-core:** handle shallow reactive arrays in renderList correctly ([#11870](https://github.com/vuejs/core/issues/11870)) ([ced59ab](https://github.com/vuejs/core/commit/ced59ab8f2f2e89c13119bab3a0c25a1a1f1c3d6)), closes [#11869](https://github.com/vuejs/core/issues/11869)
* **types:** correctly infer `TypeEmits` with both tuple and function syntax ([#11840](https://github.com/vuejs/core/issues/11840)) ([dad6738](https://github.com/vuejs/core/commit/dad673809929c084dcb8e42640eb7daa675d4ea4)), closes [#11836](https://github.com/vuejs/core/issues/11836)
### Performance Improvements
* **reactivity:** trigger deps directly instead of storing in an array first ([#11695](https://github.com/vuejs/core/issues/11695)) ([f80d447](https://github.com/vuejs/core/commit/f80d447c17662556e9e3f99f6d199967f4c8cf3d))
## [3.5.3](https://github.com/vuejs/core/compare/v3.5.2...v3.5.3) (2024-09-06)
### Bug Fixes
* **hydration:** check __asyncHydrate presence for vue3-lazy-hydration compat ([#11825](https://github.com/vuejs/core/issues/11825)) ([8e6c337](https://github.com/vuejs/core/commit/8e6c3378676be05cea7f53664442acdfb86784f9)), closes [#11793](https://github.com/vuejs/core/issues/11793)
* Revert "fix(reactivity): self-referencing computed should refresh" ([35c760f](https://github.com/vuejs/core/commit/35c760f82f749f7c6e3f9bfead8221ce498e892f))
* **ssr:** respect app.config.warnHandler during ssr ([bf3d9a2](https://github.com/vuejs/core/commit/bf3d9a2af41659a743706306fc798b3d215df5af)), closes [#11830](https://github.com/vuejs/core/issues/11830)
* **Transition:** handle KeepAlive child unmount in Transition out-in mode ([#11833](https://github.com/vuejs/core/issues/11833)) ([6b7901d](https://github.com/vuejs/core/commit/6b7901d28ed3a6a9242c666cc1b8e3c0b0b0fe62)), closes [#11775](https://github.com/vuejs/core/issues/11775)
* **useId:** make generated IDs selector compatible ([babfb4c](https://github.com/vuejs/core/commit/babfb4cbcbf98601d76c1d7653eae8d250ce2710)), closes [#11828](https://github.com/vuejs/core/issues/11828)
## [3.5.2](https://github.com/vuejs/core/compare/v3.5.1...v3.5.2) (2024-09-05)
### Bug Fixes
* **reactivity:** make toRaw work on proxies created by proxyRef ([46c3ab1](https://github.com/vuejs/core/commit/46c3ab1d714024894fa1d33e495d5d35c7817d4d))
* **reactivity:** pass oldValue to computed getter ([#11813](https://github.com/vuejs/core/issues/11813)) ([98864a7](https://github.com/vuejs/core/commit/98864a7ef5c8080c407166c8221488a4eacbbc81)), closes [#11812](https://github.com/vuejs/core/issues/11812)
* **reactivity:** prevent endless recursion in computed getters ([#11797](https://github.com/vuejs/core/issues/11797)) ([716275d](https://github.com/vuejs/core/commit/716275d1b1d2383d8ef0306fcd94558d4d9170f2))
* **reactivity:** self-referencing computed should refresh ([e84c4a6](https://github.com/vuejs/core/commit/e84c4a608e9dc96fb2a4a29d538bcc64f26103a2)), closes [/github.com/vuejs/core/pull/11797#issuecomment-2330738633](https://github.com//github.com/vuejs/core/pull/11797/issues/issuecomment-2330738633)
* **scheduler:** prevent duplicate jobs being queued ([#11826](https://github.com/vuejs/core/issues/11826)) ([df56cc5](https://github.com/vuejs/core/commit/df56cc528793b1d6131a1e64095dd5cb95c56bee)), closes [#11712](https://github.com/vuejs/core/issues/11712) [#11807](https://github.com/vuejs/core/issues/11807)
* **suspense:** avoid updating anchor if activeBranch has not been rendered to the actual container ([#11818](https://github.com/vuejs/core/issues/11818)) ([3c0d531](https://github.com/vuejs/core/commit/3c0d531fa7fe762bfe46fbe63f318adc95221795)), closes [#11806](https://github.com/vuejs/core/issues/11806)
* **Transition:** handle KeepAlive child unmount in Transition out-in mode ([#11778](https://github.com/vuejs/core/issues/11778)) ([3116553](https://github.com/vuejs/core/commit/311655352931863dfcf520b8cf29cebc5b7e1e00)), closes [#11775](https://github.com/vuejs/core/issues/11775)
* **types:** add HTMLDialogElement missing close event ([#11811](https://github.com/vuejs/core/issues/11811)) ([3634f7a](https://github.com/vuejs/core/commit/3634f7a4c1649ad2e7e969eb4512512868c61d01))
* **types:** added name attribute support to details tag ([#11823](https://github.com/vuejs/core/issues/11823)) ([c74176e](https://github.com/vuejs/core/commit/c74176ec7b4d1d34159ce21d600c04b157ac5549)), closes [#11821](https://github.com/vuejs/core/issues/11821)
* **types:** fix defineComponent props inference when setup() has explicit annotation ([fca20a3](https://github.com/vuejs/core/commit/fca20a39aa4a6f98c8f972bd435ebb7dc535648a)), closes [#11803](https://github.com/vuejs/core/issues/11803)
* **useTemplateRef:** properly fix readonly warning in dev and ensure prod behavior consistency ([9b7797d](https://github.com/vuejs/core/commit/9b7797d0d1fc773e979e042673d5b9b3151c40fc)), closes [#11808](https://github.com/vuejs/core/issues/11808) [#11816](https://github.com/vuejs/core/issues/11816) [#11810](https://github.com/vuejs/core/issues/11810)
### Features
* **compiler-core:** parse modifiers as expression to provide location data ([#11819](https://github.com/vuejs/core/issues/11819)) ([3f13203](https://github.com/vuejs/core/commit/3f13203564164eeb2945bdc0b9ef755c37477d75))
## [3.5.1](https://github.com/vuejs/core/compare/v3.5.0...v3.5.1) (2024-09-04)
### Bug Fixes
* **build:** improve built-in components treeshakability ([4eee630](https://github.com/vuejs/core/commit/4eee630b3122a10d0baf9b91358cfffa92d6fd81))
* **reactivity:** handle non-array arguments in reactive `concat` method ([#11794](https://github.com/vuejs/core/issues/11794)) ([475977a](https://github.com/vuejs/core/commit/475977a6f76b77392610e0a3ec2b0e076d1e1d59)), closes [#11792](https://github.com/vuejs/core/issues/11792)
* **Transition:** avoid applying transition hooks on comment vnode ([#11788](https://github.com/vuejs/core/issues/11788)) ([51912f8](https://github.com/vuejs/core/commit/51912f8a02e35f172f6d30ed7a2f3a92c1407cf9)), closes [#11782](https://github.com/vuejs/core/issues/11782)
* **types:** avoid using intersection type in `Readonly<...>` to fix JSDoc emit ([#11799](https://github.com/vuejs/core/issues/11799)) ([7518bc1](https://github.com/vuejs/core/commit/7518bc19dc73ba46dcf1eef6e23f9e6e75552675))
* **useTemplateRef:** fix readonly warning when useTemplateRef has same variable name as template ref ([bc63df0](https://github.com/vuejs/core/commit/bc63df01992fdbf0b6749ad234153725697ed896)), closes [#11795](https://github.com/vuejs/core/issues/11795) [#11802](https://github.com/vuejs/core/issues/11802) [#11804](https://github.com/vuejs/core/issues/11804)
# [3.5.0](https://github.com/vuejs/core/compare/v3.5.0-rc.1...v3.5.0) (2024-09-03)
## Aggregated Features List for 3.5 (alpha to stable)
### Reactivity
- **reactivity**: Refactor reactivity system to use version counting and doubly-linked list tracking ([#10397](https://github.com/vuejs/core/pull/10397)) ([05eb4e0](https://github.com/vuejs/core/commit/05eb4e0fefd585125dd60b7f8fe9c36928d921aa))
- **reactivity**: Optimize array tracking ([#9511](https://github.com/vuejs/core/pull/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e))
- **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
- **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
- **reactivity:** add `failSilently` argument for `onScopeDispose` ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
- **reactivity/watch:** base `watch`, `getCurrentWatcher`, and `onWatcherCleanup` ([#9927](https://github.com/vuejs/core/issues/9927)) ([205e5b5](https://github.com/vuejs/core/commit/205e5b5e277243c3af2c937d9bd46cf671296b72))
- **reactivity/watch:** add pause/resume for ReactiveEffect, EffectScope, and WatchHandle ([#9651](https://github.com/vuejs/core/issues/9651)) ([267093c](https://github.com/vuejs/core/commit/267093c31490050bfcf3ff2b30a2aefee2dad582))
- **watch:** support passing number to `deep` option to control the watch depth ([#9572](https://github.com/vuejs/core/issues/9572)) ([22f7d96](https://github.com/vuejs/core/commit/22f7d96757956ebe0baafe52256aa327908cc51c))
- **types:** export `MultiWatchSources` type ([#9563](https://github.com/vuejs/core/issues/9563)) ([998dca5](https://github.com/vuejs/core/commit/998dca59f140420280803233f41707580688562c))
- **types:** allow computed getter and setter types to be unrelated ([#11472](https://github.com/vuejs/core/issues/11472)) ([a01675e](https://github.com/vuejs/core/commit/a01675ef8f99b5acd6832c53051f4415b18609f2)), closes [#7271](https://github.com/vuejs/core/issues/7271)
### SSR
- **runtime-core:** `useId()` and `app.config.idPrefix` ([#11404](https://github.com/vuejs/core/issues/11404)) ([73ef156](https://github.com/vuejs/core/commit/73ef1561f6905d69f968c094d0180c61824f1247))
- **hydration:** lazy hydration strategies for async components ([#11458](https://github.com/vuejs/core/issues/11458)) ([d14a11c](https://github.com/vuejs/core/commit/d14a11c1cdcee88452f17ce97758743c863958f4))
- **hydration:** support suppressing hydration mismatch via data-allow-mismatch ([94fb2b8](https://github.com/vuejs/core/commit/94fb2b8106a66bcca1a3f922a246a29fdd1274b1))
### Custom Element
- **custom-element:** `useHost()` helper ([775103a](https://github.com/vuejs/core/commit/775103af37df69d34c79f12c4c1776c47d07f0a0))
- **custom-element:** `useShadowRoot()` helper ([5a1a89b](https://github.com/vuejs/core/commit/5a1a89bd6178cc2f84ba91da7d72aee4c6ec1282)), closes [#6113](https://github.com/vuejs/core/issues/6113) [#8195](https://github.com/vuejs/core/issues/8195)
- **custom-element:** expose `this.$host` in Options API ([1ef8f46](https://github.com/vuejs/core/commit/1ef8f46af0cfdec2fed66376772409e0aa25ad50))
- **custom-element:** inject child components styles to custom element shadow root ([#11517](https://github.com/vuejs/core/issues/11517)) ([56c76a8](https://github.com/vuejs/core/commit/56c76a8b05c45f782ed3a16ec77c6292b71a17f1)), closes [#4662](https://github.com/vuejs/core/issues/4662) [#7941](https://github.com/vuejs/core/issues/7941) [#7942](https://github.com/vuejs/core/issues/7942)
- **custom-element:** support configurable app instance in defineCustomElement ([6758c3c](https://github.com/vuejs/core/commit/6758c3cd0427f97394d95168c655dae3b7fa62cd)), closes [#4356](https://github.com/vuejs/core/issues/4356) [#4635](https://github.com/vuejs/core/issues/4635)
- **custom-element:** support css `:host` selector by applying css vars on host element ([#8830](https://github.com/vuejs/core/issues/8830)) ([03a9ea2](https://github.com/vuejs/core/commit/03a9ea2b88df0842a820e09f7445c4b9189e3fcb)), closes [#8826](https://github.com/vuejs/core/issues/8826)
- **custom-element:** support emit with options ([e181bff](https://github.com/vuejs/core/commit/e181bff6dc39d5cef92000c10291243c7d6e4d08)), closes [#7605](https://github.com/vuejs/core/issues/7605)
- **custom-element:** support expose on customElement ([#6256](https://github.com/vuejs/core/issues/6256)) ([af838c1](https://github.com/vuejs/core/commit/af838c1b5ec23552e52e64ffa7db0eb0246c3624)), closes [#5540](https://github.com/vuejs/core/issues/5540)
- **custom-element:** support `nonce` option for injected style tags ([bb4a02a](https://github.com/vuejs/core/commit/bb4a02a70c30e739a3c705b3d96d09258d7d7ded)), closes [#6530](https://github.com/vuejs/core/issues/6530)
- **custom-element:** support passing custom-element-specific options via 2nd argument of defineCustomElement ([60a88a2](https://github.com/vuejs/core/commit/60a88a2b129714186cf6ba66f30f31d733d0311e))
- **custom-element:** support `shadowRoot: false` in `defineCustomElement()` ([37d2ce5](https://github.com/vuejs/core/commit/37d2ce5d8e0fac4a00064f02b05f91f69b2d5d5e)), closes [#4314](https://github.com/vuejs/core/issues/4314) [#4404](https://github.com/vuejs/core/issues/4404)
### Teleport
- **teleport:** support deferred Teleport ([#11387](https://github.com/vuejs/core/issues/11387)) ([59a3e88](https://github.com/vuejs/core/commit/59a3e88903b10ac2278170a44d5a03f24fef23ef)), closes [#2015](https://github.com/vuejs/core/issues/2015) [#11386](https://github.com/vuejs/core/issues/11386)
- **teleport/transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
### Misc
- **runtime-core:** `useTemplateRef()` ([3ba70e4](https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df))
- **runtime-core:** add `app.onUnmount()` for registering cleanup functions ([#4619](https://github.com/vuejs/core/issues/4619)) ([582a3a3](https://github.com/vuejs/core/commit/582a3a382b1adda565bac576b913a88d9e8d7a9e)), closes [#4516](https://github.com/vuejs/core/issues/4516)
- **runtime-core:** add `app.config.throwUnhandledErrorInProduction` ([f476b7f](https://github.com/vuejs/core/commit/f476b7f030f2dd427ca655fcea36f4933a4b4da0)), closes [#7876](https://github.com/vuejs/core/issues/7876)
- **runtime-dom:** Trusted Types compatibility ([#10844](https://github.com/vuejs/core/issues/10844)) ([6d4eb94](https://github.com/vuejs/core/commit/6d4eb94853ed1b2b1675bdd7d5ba9c75cc6daed5))
- **compiler-core:** support `Symbol` global in template expressions ([#9069](https://github.com/vuejs/core/issues/9069)) ([a501a85](https://github.com/vuejs/core/commit/a501a85a7c910868e01a5c70a2abea4e9d9e87f3))
- **types:** export more emit related types ([#11017](https://github.com/vuejs/core/issues/11017)) ([189573d](https://github.com/vuejs/core/commit/189573dcee2a16bd3ed36ff5589d43f535e5e733))
* **types:** add loading prop to iframe ([#11767](https://github.com/vuejs/core/issues/11767)) ([d86fe0e](https://github.com/vuejs/core/commit/d86fe0ec002901dc359a0e85f3a421b4a8538d68))
### Internals
- **reactivity:** store value cache on CustomRefs impls ([#11539](https://github.com/vuejs/core/issues/11539)) ([e044b6e](https://github.com/vuejs/core/commit/e044b6e737efc9433d1d84590036b82280da6292))
- **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
- **types:** provide internal options for using refs type in language tools ([#11492](https://github.com/vuejs/core/issues/11492)) ([5ffd1a8](https://github.com/vuejs/core/commit/5ffd1a89455807d5069eb2c28eba0379641dca76))
## Bug Fixes
* **compiler-sfc:** fix import usage check for kebab-case same name shorthand binding ([0f7c0e5](https://github.com/vuejs/core/commit/0f7c0e5dc0eedada7a5194db87fd0a7dbd1d3354)), closes [#11745](https://github.com/vuejs/core/issues/11745) [#11754](https://github.com/vuejs/core/issues/11754)
* **cssVars:** correctly escape double quotes in SSR ([#11784](https://github.com/vuejs/core/issues/11784)) ([7b5b6e0](https://github.com/vuejs/core/commit/7b5b6e0275f35748dca6d7eb842f8ab2364c6b9a)), closes [#11779](https://github.com/vuejs/core/issues/11779)
* **deps:** update dependency postcss to ^8.4.44 ([#11774](https://github.com/vuejs/core/issues/11774)) ([cb843e0](https://github.com/vuejs/core/commit/cb843e0be31f9e563ccfc30eca0c06f2a224b505))
* **hydration:** escape css var name to avoid mismatch ([#11739](https://github.com/vuejs/core/issues/11739)) ([ca12e77](https://github.com/vuejs/core/commit/ca12e776bc53aaa31f2df6bb6edc6be1b2f10c37)), closes [#11735](https://github.com/vuejs/core/issues/11735)
* **hydration:** handle text nodes with 0 during hydration ([#11772](https://github.com/vuejs/core/issues/11772)) ([c756da2](https://github.com/vuejs/core/commit/c756da24b2d8635cf52b4c7d3abf5bf938852cc5)), closes [#11771](https://github.com/vuejs/core/issues/11771)
* **reactivity:** correctly handle method calls on user-extended arrays ([#11760](https://github.com/vuejs/core/issues/11760)) ([9817c80](https://github.com/vuejs/core/commit/9817c80187bec6a3344c74d65fac92262de0fcdd)), closes [#11759](https://github.com/vuejs/core/issues/11759)
* **runtime-dom:** avoid unnecessary prop patch for checkbox ([#11657](https://github.com/vuejs/core/issues/11657)) ([c3ce9fe](https://github.com/vuejs/core/commit/c3ce9fe3d8fc27d864ce7148cd36da882cfc21ab)), closes [#11647](https://github.com/vuejs/core/issues/11647)
* **runtime-dom:** prevent unnecessary DOM update from v-model ([#11656](https://github.com/vuejs/core/issues/11656)) ([b1be9bd](https://github.com/vuejs/core/commit/b1be9bd64f2c7c4286fecb25bad5d5edd49efce9)), closes [#11647](https://github.com/vuejs/core/issues/11647)
* **server-renderer:** Fix call to serverPrefetch in server renderer with an async setup ([#10893](https://github.com/vuejs/core/issues/10893)) ([6039e25](https://github.com/vuejs/core/commit/6039e25e04a8c1db5821955f011d57f1615807ab))
* **server-renderer:** render `className` during SSR ([#11722](https://github.com/vuejs/core/issues/11722)) ([52cdb0f](https://github.com/vuejs/core/commit/52cdb0f991dc154ae32a2900874d5dbc4e078565))
* **types/defineModel:** allow getter and setter types to be unrelated ([#11699](https://github.com/vuejs/core/issues/11699)) ([fe07f70](https://github.com/vuejs/core/commit/fe07f7073617df358c2f8cbc3de433359e873c96)), closes [#11697](https://github.com/vuejs/core/issues/11697)
# [3.5.0-rc.1](https://github.com/vuejs/core/compare/v3.5.0-beta.3...v3.5.0-rc.1) (2024-08-29)
### Bug Fixes
* **compiler-sfc:** skip circular tsconfig project reference ([#11680](https://github.com/vuejs/core/issues/11680)) ([9c4c2e5](https://github.com/vuejs/core/commit/9c4c2e51b045218d0c5ca64b4fb58b17d5d580cc)), closes [#11382](https://github.com/vuejs/core/issues/11382)
* **custom-element:** handle keys set on custom elements ([#11655](https://github.com/vuejs/core/issues/11655)) ([f1d1831](https://github.com/vuejs/core/commit/f1d1831f07fe52d5681a5ec9ec310572463abf26)), closes [#11641](https://github.com/vuejs/core/issues/11641)
* **deps:** update dependency monaco-editor to ^0.51.0 ([#11713](https://github.com/vuejs/core/issues/11713)) ([434f8a9](https://github.com/vuejs/core/commit/434f8a97c77f68aeae050e9e4e1f54f63bc4bd26))
* **keep-alive:** reset keep alive flag when the component is removed from include ([#11718](https://github.com/vuejs/core/issues/11718)) ([29c321b](https://github.com/vuejs/core/commit/29c321bfd33f9197244dec3d027077e63b2cdf2f)), closes [#11717](https://github.com/vuejs/core/issues/11717)
* **reactivity:** avoid infinite recursion when mutating ref wrapped in reactive ([313e4bf](https://github.com/vuejs/core/commit/313e4bf55214ac1e334a99c329a3ba5daca4f156)), closes [#11696](https://github.com/vuejs/core/issues/11696)
* **reactivity:** ensure watcher with once: true are properly removed from effect scope ([#11665](https://github.com/vuejs/core/issues/11665)) ([fbc0c42](https://github.com/vuejs/core/commit/fbc0c42bcf6dea5a6ae664223fa19d4375ca39f0))
* **runtime-dom:** setting innerHTML when patching props should go through trusted types ([d875de5](https://github.com/vuejs/core/commit/d875de54e9e03e0768fe550aa4c4886a4baf3bd7))
* **types:** GlobalDirective / GlobalComponents should not be records ([42e8df6](https://github.com/vuejs/core/commit/42e8df62030e7f2c287d9103f045e67b34a63e3b))
# [3.5.0-beta.3](https://github.com/vuejs/core/compare/v3.5.0-beta.2...v3.5.0-beta.3) (2024-08-20)
### Bug Fixes
* **reactivity:** extended methods respect reactive ([#11629](https://github.com/vuejs/core/issues/11629)) ([9de1d10](https://github.com/vuejs/core/commit/9de1d101f98bf6081f41038f6974826f190330a0)), closes [#11628](https://github.com/vuejs/core/issues/11628)
* **runtime-core:** correct type inference for PascalCase emits ([#11579](https://github.com/vuejs/core/issues/11579)) ([d7d0371](https://github.com/vuejs/core/commit/d7d0371e74707ee601020f67de88e091cdae2673)), closes [vuejs/language-tools#4269](https://github.com/vuejs/language-tools/issues/4269)
* **runtime-core:** ensure suspense content inherit scopeId ([#10652](https://github.com/vuejs/core/issues/10652)) ([ac2a410](https://github.com/vuejs/core/commit/ac2a410e46392db63ca4ed2db3c0fa71ebe1e855)), closes [#5148](https://github.com/vuejs/core/issues/5148)
* **runtime-core:** pre jobs without an id should run first ([#7746](https://github.com/vuejs/core/issues/7746)) ([b332f80](https://github.com/vuejs/core/commit/b332f80f0edb018229a23b43b93bb402b6368a3c))
* **ssr:** apply ssr props to the the fallback vnode-based branch in ssr ([#7247](https://github.com/vuejs/core/issues/7247)) ([98b83e8](https://github.com/vuejs/core/commit/98b83e86d16c635547a1e735e5fb675aea2f0f1b)), closes [#6123](https://github.com/vuejs/core/issues/6123)
* **types/custom-element:** `defineCustomElement` with required props ([#11578](https://github.com/vuejs/core/issues/11578)) ([5e0f6d5](https://github.com/vuejs/core/commit/5e0f6d5f8fe7c4eb8f247357c3e2e281726f36db))
* **types:** strip non-prop default values from return type of withDefaults ([#9998](https://github.com/vuejs/core/issues/9998)) ([44973bb](https://github.com/vuejs/core/commit/44973bb3e790db7d8aa7af4eda21c80cac73a8de)), closes [#9899](https://github.com/vuejs/core/issues/9899)
* **watch:** handle errors in computed used as watch source ([#11626](https://github.com/vuejs/core/issues/11626)) ([8bcaad4](https://github.com/vuejs/core/commit/8bcaad4a32cf0f1f89e0259f6a53036620b7fe9f)), closes [#11624](https://github.com/vuejs/core/issues/11624)
### Features
* **reactivity:** base `watch`, `getCurrentWatcher`, and `onWatcherCleanup` ([#9927](https://github.com/vuejs/core/issues/9927)) ([205e5b5](https://github.com/vuejs/core/commit/205e5b5e277243c3af2c937d9bd46cf671296b72))
### Performance Improvements
* **runtime-core:** use `apply` to avoid spreading. ([#5985](https://github.com/vuejs/core/issues/5985)) ([bb6babc](https://github.com/vuejs/core/commit/bb6babca8f206615d4e246457cd54d21bb3bc5a4))
# [3.5.0-beta.2](https://github.com/vuejs/core/compare/v3.5.0-beta.1...v3.5.0-beta.2) (2024-08-15)
### Bug Fixes
* **build:** revert entities to 4.5 to avoid runtime resolution errors ([e9e0815](https://github.com/vuejs/core/commit/e9e08155bf8d00c3327ed7371330eb2ae467e560)), closes [#11603](https://github.com/vuejs/core/issues/11603)
* **compiler-core:** use ast-based check for function expressions when possible ([5861229](https://github.com/vuejs/core/commit/58612294757480974e667652ede5bbcf72b1089d)), closes [#11615](https://github.com/vuejs/core/issues/11615)
* **compiler-sfc:** fix prefixIdentifier default value ([3d6f015](https://github.com/vuejs/core/commit/3d6f01571b3fb61b32da599d0419eff4e3ebb231))
* **compiler-sfc:** handle keyof operator with index object ([#11581](https://github.com/vuejs/core/issues/11581)) ([fe00815](https://github.com/vuejs/core/commit/fe008152c0612ff3ecc7ad88e7e66a06b1b2bc3f))
* **custom-element:** keep instance.isCE for backwards compat ([e19fc27](https://github.com/vuejs/core/commit/e19fc270428b59456fee43224990138c4d6ccb2d))
* **deps:** update dependency postcss to ^8.4.41 ([#11585](https://github.com/vuejs/core/issues/11585)) ([4c4e12a](https://github.com/vuejs/core/commit/4c4e12ae28d67d616924b0601e68adc551959971))
* **keep-alive:** ensure include/exclude regexp work with global flag ([#11595](https://github.com/vuejs/core/issues/11595)) ([3653bc0](https://github.com/vuejs/core/commit/3653bc0f45d6fedf84e29b64ca52584359c383c0))
* **reactivity:** ensure extended method arguments are not lost ([#11574](https://github.com/vuejs/core/issues/11574)) ([4085def](https://github.com/vuejs/core/commit/4085def1bae42d01ee3c22c731cc4a02096464ee)), closes [#11570](https://github.com/vuejs/core/issues/11570)
* **reactivity:** sync watch should be executed correctly ([#11589](https://github.com/vuejs/core/issues/11589)) ([3bda3e8](https://github.com/vuejs/core/commit/3bda3e83fd9e2fbe451a1c79dae82ff6a7467683)), closes [#11577](https://github.com/vuejs/core/issues/11577)
* **types/computed:** ensure type safety for `WritableComputedRef` ([#11608](https://github.com/vuejs/core/issues/11608)) ([5cf5a16](https://github.com/vuejs/core/commit/5cf5a1620d9a97382d386c277265d9dd051fe484))
* **types:** add fallback stub for DOM types when DOM lib is absent ([#11598](https://github.com/vuejs/core/issues/11598)) ([fee6697](https://github.com/vuejs/core/commit/fee669764fbf475adce9e47a7a73b4937ab31ffc))
### Features
* **deprecated:** remove deprecated parseExpressions option ([#11597](https://github.com/vuejs/core/issues/11597)) ([4e7d5db](https://github.com/vuejs/core/commit/4e7d5db4d276a5d4aaf3af7d43cfd28c171db307))
# [3.5.0-beta.1](https://github.com/vuejs/core/compare/v3.4.37...v3.5.0-beta.1) (2024-08-08)
### Bug Fixes
* **custom-element:** delay mounting of custom elements with async parent ([37ccb9b](https://github.com/vuejs/core/commit/37ccb9b9a0e4381f9465e0fc6459609003030da4)), closes [#8127](https://github.com/vuejs/core/issues/8127) [#9341](https://github.com/vuejs/core/issues/9341) [#9351](https://github.com/vuejs/core/issues/9351) [#9351](https://github.com/vuejs/core/issues/9351)
* **custom-element:** delete prop on attribute removal ([506c4c5](https://github.com/vuejs/core/commit/506c4c53fdf9766c2ce9517ad58d501ef6b1b9de)), closes [#11276](https://github.com/vuejs/core/issues/11276)
* **custom-element:** ignore scoped id ([7f2c505](https://github.com/vuejs/core/commit/7f2c505f92026408a8262ba9b5104a465be19446))
* **custom-element:** reflect prop default value on custom element ([63689ed](https://github.com/vuejs/core/commit/63689ed77601d5f9b78540f810612806c3a5de15)), closes [#9006](https://github.com/vuejs/core/issues/9006) [#10537](https://github.com/vuejs/core/issues/10537)
* **custom-element:** support early-set domProps for async custom elements ([a07e7bf](https://github.com/vuejs/core/commit/a07e7bf5536a6b3db70ba9bb1c3f366dac1bf5a0)), closes [#11081](https://github.com/vuejs/core/issues/11081) [#11082](https://github.com/vuejs/core/issues/11082)
* **types/custome-element:** `defineCustomElement` props inference with array emits ([#11384](https://github.com/vuejs/core/issues/11384)) ([e94b01b](https://github.com/vuejs/core/commit/e94b01bd8a1ec740eddc823839ab2627b307c1b0)), closes [#11353](https://github.com/vuejs/core/issues/11353)
* **types:** allow using InjectionKey as valid property key ([321d807](https://github.com/vuejs/core/commit/321d80758c42fccbd39ecbb63f1a4f6632a1580a)), closes [#5089](https://github.com/vuejs/core/issues/5089)
### Features
* **custom-element:** expose this.$host in Options API ([1ef8f46](https://github.com/vuejs/core/commit/1ef8f46af0cfdec2fed66376772409e0aa25ad50))
* **custom-element:** inject child components styles to custom element shadow root ([#11517](https://github.com/vuejs/core/issues/11517)) ([56c76a8](https://github.com/vuejs/core/commit/56c76a8b05c45f782ed3a16ec77c6292b71a17f1)), closes [#4662](https://github.com/vuejs/core/issues/4662) [#7941](https://github.com/vuejs/core/issues/7941) [#7942](https://github.com/vuejs/core/issues/7942)
* **custom-element:** support configurable app instance in defineCustomElement ([6758c3c](https://github.com/vuejs/core/commit/6758c3cd0427f97394d95168c655dae3b7fa62cd)), closes [#4356](https://github.com/vuejs/core/issues/4356) [#4635](https://github.com/vuejs/core/issues/4635)
* **custom-element:** support css `:host` selector by applying css vars on host element ([#8830](https://github.com/vuejs/core/issues/8830)) ([03a9ea2](https://github.com/vuejs/core/commit/03a9ea2b88df0842a820e09f7445c4b9189e3fcb)), closes [#8826](https://github.com/vuejs/core/issues/8826)
* **custom-element:** support emit with options ([e181bff](https://github.com/vuejs/core/commit/e181bff6dc39d5cef92000c10291243c7d6e4d08)), closes [#7605](https://github.com/vuejs/core/issues/7605)
* **custom-element:** support for expose on customElement ([#6256](https://github.com/vuejs/core/issues/6256)) ([af838c1](https://github.com/vuejs/core/commit/af838c1b5ec23552e52e64ffa7db0eb0246c3624)), closes [#5540](https://github.com/vuejs/core/issues/5540)
* **custom-element:** support nonce option for injected style tags ([bb4a02a](https://github.com/vuejs/core/commit/bb4a02a70c30e739a3c705b3d96d09258d7d7ded)), closes [#6530](https://github.com/vuejs/core/issues/6530)
* **custom-element:** support passing custom-element-specific options via 2nd argument of defineCustomElement ([60a88a2](https://github.com/vuejs/core/commit/60a88a2b129714186cf6ba66f30f31d733d0311e))
* **custom-element:** support shadowRoot: false in defineCustomElement() ([37d2ce5](https://github.com/vuejs/core/commit/37d2ce5d8e0fac4a00064f02b05f91f69b2d5d5e)), closes [#4314](https://github.com/vuejs/core/issues/4314) [#4404](https://github.com/vuejs/core/issues/4404)
* **custom-element:** useHost() helper ([775103a](https://github.com/vuejs/core/commit/775103af37df69d34c79f12c4c1776c47d07f0a0))
* **custom-element:** useShadowRoot() helper ([5a1a89b](https://github.com/vuejs/core/commit/5a1a89bd6178cc2f84ba91da7d72aee4c6ec1282)), closes [#6113](https://github.com/vuejs/core/issues/6113) [#8195](https://github.com/vuejs/core/issues/8195)
* **hydration:** allow fine tuning of lazy hydration strategy triggers ([#11530](https://github.com/vuejs/core/issues/11530)) ([261c8b1](https://github.com/vuejs/core/commit/261c8b111d046204bd22392a8b920e3c3d4def48))
* **reactivity/watch:** add pause/resume for ReactiveEffect, EffectScope, and WatchHandle ([#9651](https://github.com/vuejs/core/issues/9651)) ([267093c](https://github.com/vuejs/core/commit/267093c31490050bfcf3ff2b30a2aefee2dad582))
* **reactivity:** store value cache on CustomRefs impls ([#11539](https://github.com/vuejs/core/issues/11539)) ([e044b6e](https://github.com/vuejs/core/commit/e044b6e737efc9433d1d84590036b82280da6292))
* **runtime-dom:** Trusted Types compatibility ([#10844](https://github.com/vuejs/core/issues/10844)) ([6d4eb94](https://github.com/vuejs/core/commit/6d4eb94853ed1b2b1675bdd7d5ba9c75cc6daed5))
* support specifying allowed keys via generic argument in useTemplateRef() ([1fbfa69](https://github.com/vuejs/core/commit/1fbfa6962b48634ff60837084b82dd57f215c109))
* **types:** allow computed getter and setter types to be unrelated ([#11472](https://github.com/vuejs/core/issues/11472)) ([a01675e](https://github.com/vuejs/core/commit/a01675ef8f99b5acd6832c53051f4415b18609f2)), closes [#7271](https://github.com/vuejs/core/issues/7271)
* **types:** export `MultiWatchSources` type ([#9563](https://github.com/vuejs/core/issues/9563)) ([998dca5](https://github.com/vuejs/core/commit/998dca59f140420280803233f41707580688562c))
* **types:** provide internal options for using refs type in language tools ([#11492](https://github.com/vuejs/core/issues/11492)) ([5ffd1a8](https://github.com/vuejs/core/commit/5ffd1a89455807d5069eb2c28eba0379641dca76))
* **watch:** support passing number to `deep` option to control the watch depth ([#9572](https://github.com/vuejs/core/issues/9572)) ([22f7d96](https://github.com/vuejs/core/commit/22f7d96757956ebe0baafe52256aa327908cc51c))
# [3.5.0-alpha.5](https://github.com/vuejs/core/compare/v3.4.35...v3.5.0-alpha.5) (2024-07-31)
### Features
* **hydration:** support suppressing hydration mismatch via data-allow-mismatch ([94fb2b8](https://github.com/vuejs/core/commit/94fb2b8106a66bcca1a3f922a246a29fdd1274b1))
* lazy hydration strategies for async components ([#11458](https://github.com/vuejs/core/issues/11458)) ([d14a11c](https://github.com/vuejs/core/commit/d14a11c1cdcee88452f17ce97758743c863958f4))
# [3.5.0-alpha.4](https://github.com/vuejs/core/compare/v3.4.34...v3.5.0-alpha.4) (2024-07-24)
### Bug Fixes
* **suspense/hydration:** fix hydration timing of async component inside suspense ([1b8e197](https://github.com/vuejs/core/commit/1b8e197a5b65d67a9703b8511786fb81df9aa7cc)), closes [#6638](https://github.com/vuejs/core/issues/6638)
* **useId:** properly mark async boundary for already resolved async component ([cd28172](https://github.com/vuejs/core/commit/cd281725781ada2ab279e919031ae307e146a9d9))
# [3.5.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.33...v3.5.0-alpha.3) (2024-07-19)
### Bug Fixes
* **build:** enable SSR branches in esm-browser builds ([b14cd9a](https://github.com/vuejs/core/commit/b14cd9a68bab082332b0169be075be357be076ca))
* **compiler-core:** change node hoisting to caching per instance ([#11067](https://github.com/vuejs/core/issues/11067)) ([cd0ea0d](https://github.com/vuejs/core/commit/cd0ea0d479a276583fa181d8ecbc97fb0e4a9dce)), closes [#5256](https://github.com/vuejs/core/issues/5256) [#9219](https://github.com/vuejs/core/issues/9219) [#10959](https://github.com/vuejs/core/issues/10959)
* **compiler-sfc:** should properly walk desutructured props when reactive destructure is not enabled ([0fd6193](https://github.com/vuejs/core/commit/0fd6193def2380916eb51a118f37f2d9ec2ace23)), closes [#11325](https://github.com/vuejs/core/issues/11325)
* **types:** respect props with default on instance type when using __typeProps ([96e4738](https://github.com/vuejs/core/commit/96e473833422342c5ca371ae1aeb186dec9a55e3))
### Features
* **runtime-core:** useTemplateRef() ([3ba70e4](https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df))
* **runtime-core:** useId() and app.config.idPrefix ([#11404](https://github.com/vuejs/core/issues/11404)) ([73ef156](https://github.com/vuejs/core/commit/73ef1561f6905d69f968c094d0180c61824f1247))
* **runtime-core:** add app.config.throwUnhandledErrorInProduction ([f476b7f](https://github.com/vuejs/core/commit/f476b7f030f2dd427ca655fcea36f4933a4b4da0)), closes [#7876](https://github.com/vuejs/core/issues/7876)
* **teleport:** support deferred Teleport ([#11387](https://github.com/vuejs/core/issues/11387)) ([59a3e88](https://github.com/vuejs/core/commit/59a3e88903b10ac2278170a44d5a03f24fef23ef)), closes [#2015](https://github.com/vuejs/core/issues/2015) [#11386](https://github.com/vuejs/core/issues/11386)
* **compiler-core:** support `Symbol` global in template expressions ([#9069](https://github.com/vuejs/core/issues/9069)) ([a501a85](https://github.com/vuejs/core/commit/a501a85a7c910868e01a5c70a2abea4e9d9e87f3))
* **types:** export more emit related types ([#11017](https://github.com/vuejs/core/issues/11017)) ([189573d](https://github.com/vuejs/core/commit/189573dcee2a16bd3ed36ff5589d43f535e5e733))
# [3.5.0-alpha.2](https://github.com/vuejs/core/compare/v3.4.26...v3.5.0-alpha.2) (2024-05-04)
### Bug Fixes
* **types:** fix app.component() typing with inline defineComponent ([908f70a](https://github.com/vuejs/core/commit/908f70adc06038d1ea253d96f4024367f4a7545d)), closes [#10843](https://github.com/vuejs/core/issues/10843)
* **types:** fix compat with generated types that rely on CreateComponentPublicInstance ([c146186](https://github.com/vuejs/core/commit/c146186396d0c1a65423b8c9a21251c5a6467336)), closes [#10842](https://github.com/vuejs/core/issues/10842)
* **types:** props in defineOptions type should be optional ([124c4ca](https://github.com/vuejs/core/commit/124c4cac833a28ae9bc8edc576c1d0c7c41f5985)), closes [#10841](https://github.com/vuejs/core/issues/10841)
### Features
* **runtime-core:** add app.onUnmount() for registering cleanup functions ([#4619](https://github.com/vuejs/core/issues/4619)) ([582a3a3](https://github.com/vuejs/core/commit/582a3a382b1adda565bac576b913a88d9e8d7a9e)), closes [#4516](https://github.com/vuejs/core/issues/4516)
# [3.5.0-alpha.1](https://github.com/vuejs/core/compare/v3.4.25...v3.5.0-alpha.1) (2024-04-29)
### Bug Fixes
* **reactivity:** fix call sequence of ontrigger in effect ([#10501](https://github.com/vuejs/core/issues/10501)) ([28841fe](https://github.com/vuejs/core/commit/28841fee43a45c37905c2c1ed9ace23067539045))
### Features
* **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
* **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
* **reactivity:** add failSilently argument for onScopeDispose ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
* **transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
* **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
### Performance Improvements
* **reactivity:** optimize array tracking ([#9511](https://github.com/vuejs/core/issues/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e)), closes [#4318](https://github.com/vuejs/core/issues/4318)
Vapor Mode attempts to match VDOM Mode behavior as much as possible, but there could still be minor behavior inconsistencies in edge cases due to how fundamentally different the two rendering modes are. In general, we do not consider a minor inconsistency to be breaking change unless the behavior has previously been documented.
## Previous Changelogs ## Previous Changelogs
### 3.5.x (2024-04-29 - 2025-06-18)
See [3.5 changelog](./changelogs/CHANGELOG-3.5.md)
### 3.4.x (2023-10-28 - 2024-08-15) ### 3.4.x (2023-10-28 - 2024-08-15)
See [3.4 changelog](./changelogs/CHANGELOG-3.4.md) See [3.4 changelog](./changelogs/CHANGELOG-3.4.md)

View File

@ -56,13 +56,13 @@
- **hydration:** handle camel-case tag name when performing match assertion ([#3247](https://github.com/vuejs/core/issues/3247)) ([9036f88](https://github.com/vuejs/core/commit/9036f88d8304a3455265f1ecd86ec8f4a5ea4715)), closes [#3243](https://github.com/vuejs/core/issues/3243) - **hydration:** handle camel-case tag name when performing match assertion ([#3247](https://github.com/vuejs/core/issues/3247)) ([9036f88](https://github.com/vuejs/core/commit/9036f88d8304a3455265f1ecd86ec8f4a5ea4715)), closes [#3243](https://github.com/vuejs/core/issues/3243)
- **KeepAlive:** adapt keepalive for ssr ([#3259](https://github.com/vuejs/core/issues/3259)) ([e8e9b00](https://github.com/vuejs/core/commit/e8e9b00f81ed42434afd92f84101e7a14d70a23c)), closes [#3255](https://github.com/vuejs/core/issues/3255) - **KeepAlive:** adapt keepalive for ssr ([#3259](https://github.com/vuejs/core/issues/3259)) ([e8e9b00](https://github.com/vuejs/core/commit/e8e9b00f81ed42434afd92f84101e7a14d70a23c)), closes [#3255](https://github.com/vuejs/core/issues/3255)
- **reactivity:** ensure computed can be wrapped by readonly ([41e02f0](https://github.com/vuejs/core/commit/41e02f0fac069c93c94438741517e713f3c94215)), closes [#3376](https://github.com/vuejs/core/issues/3376) - **reactivity:** ensure computed can be wrapped by readonly ([41e02f0](https://github.com/vuejs/core/commit/41e02f0fac069c93c94438741517e713f3c94215)), closes [#3376](https://github.com/vuejs/core/issues/3376)
- **reactivity:** ensure that shallow and normal proxies are tracked seperately (close [#2843](https://github.com/vuejs/core/issues/2843)) ([#2851](https://github.com/vuejs/core/issues/2851)) ([22cc4a7](https://github.com/vuejs/core/commit/22cc4a76592cfe336e75e2fa0c05232ae1f0f149)) - **reactivity:** ensure that shallow and normal proxies are tracked separately (close [#2843](https://github.com/vuejs/core/issues/2843)) ([#2851](https://github.com/vuejs/core/issues/2851)) ([22cc4a7](https://github.com/vuejs/core/commit/22cc4a76592cfe336e75e2fa0c05232ae1f0f149))
- **reactivity:** fix shallow readonly behavior for collections ([#3003](https://github.com/vuejs/core/issues/3003)) ([68de9f4](https://github.com/vuejs/core/commit/68de9f408a2e61a5726a4a0d03b026cba451c5bd)), closes [#3007](https://github.com/vuejs/core/issues/3007) - **reactivity:** fix shallow readonly behavior for collections ([#3003](https://github.com/vuejs/core/issues/3003)) ([68de9f4](https://github.com/vuejs/core/commit/68de9f408a2e61a5726a4a0d03b026cba451c5bd)), closes [#3007](https://github.com/vuejs/core/issues/3007)
- **rumtime-core:** custom dom props should be cloned when cloning a hoisted DOM ([#3080](https://github.com/vuejs/core/issues/3080)) ([5dbe834](https://github.com/vuejs/core/commit/5dbe8348581dacd7a3594a9b0055ce350ce8e5bf)), closes [#3072](https://github.com/vuejs/core/issues/3072) - **rumtime-core:** custom dom props should be cloned when cloning a hoisted DOM ([#3080](https://github.com/vuejs/core/issues/3080)) ([5dbe834](https://github.com/vuejs/core/commit/5dbe8348581dacd7a3594a9b0055ce350ce8e5bf)), closes [#3072](https://github.com/vuejs/core/issues/3072)
- **runtime-core:** cache props default values to avoid unnecessary watcher trigger ([#3474](https://github.com/vuejs/core/issues/3474)) ([44166b4](https://github.com/vuejs/core/commit/44166b43d9be1062f79612880f71284049bcab0b)), closes [#3471](https://github.com/vuejs/core/issues/3471) - **runtime-core:** cache props default values to avoid unnecessary watcher trigger ([#3474](https://github.com/vuejs/core/issues/3474)) ([44166b4](https://github.com/vuejs/core/commit/44166b43d9be1062f79612880f71284049bcab0b)), closes [#3471](https://github.com/vuejs/core/issues/3471)
- **runtime-core:** ensure only skip unflushed job ([#3406](https://github.com/vuejs/core/issues/3406)) ([bf34e33](https://github.com/vuejs/core/commit/bf34e33c909da89681b9c5004cdf04ab198ec5a7)) - **runtime-core:** ensure only skip unflushed job ([#3406](https://github.com/vuejs/core/issues/3406)) ([bf34e33](https://github.com/vuejs/core/commit/bf34e33c909da89681b9c5004cdf04ab198ec5a7))
- **runtime-core:** fix async component ref handling ([#3191](https://github.com/vuejs/core/issues/3191)) ([7562e72](https://github.com/vuejs/core/commit/7562e72c2b58a5646bd4fbd9adea11eb884fe140)), closes [#3188](https://github.com/vuejs/core/issues/3188) - **runtime-core:** fix async component ref handling ([#3191](https://github.com/vuejs/core/issues/3191)) ([7562e72](https://github.com/vuejs/core/commit/7562e72c2b58a5646bd4fbd9adea11eb884fe140)), closes [#3188](https://github.com/vuejs/core/issues/3188)
- **runtime-core:** fix erraneous emits warnings w/ mixins ([60d777d](https://github.com/vuejs/core/commit/60d777d228414515cc32526ad72a53ef070501be)), closes [#2651](https://github.com/vuejs/core/issues/2651) - **runtime-core:** fix erroneous emits warnings w/ mixins ([60d777d](https://github.com/vuejs/core/commit/60d777d228414515cc32526ad72a53ef070501be)), closes [#2651](https://github.com/vuejs/core/issues/2651)
- **runtime-core:** fix warning for absent props ([#3363](https://github.com/vuejs/core/issues/3363)) ([86ceef4](https://github.com/vuejs/core/commit/86ceef43523bfbbb0a24731d3802ca6849cbefd6)), closes [#3362](https://github.com/vuejs/core/issues/3362) - **runtime-core:** fix warning for absent props ([#3363](https://github.com/vuejs/core/issues/3363)) ([86ceef4](https://github.com/vuejs/core/commit/86ceef43523bfbbb0a24731d3802ca6849cbefd6)), closes [#3362](https://github.com/vuejs/core/issues/3362)
- **runtime-core:** handle error in async setup ([#2881](https://github.com/vuejs/core/issues/2881)) ([d668d48](https://github.com/vuejs/core/commit/d668d48e9e5211a49ee53361ea5b4d67ba16e0a3)) - **runtime-core:** handle error in async setup ([#2881](https://github.com/vuejs/core/issues/2881)) ([d668d48](https://github.com/vuejs/core/commit/d668d48e9e5211a49ee53361ea5b4d67ba16e0a3))
- **runtime-core:** handle error in async watchEffect ([#3129](https://github.com/vuejs/core/issues/3129)) ([eb1fae6](https://github.com/vuejs/core/commit/eb1fae63f926435fb0eef890663d24e09d4c79e1)) - **runtime-core:** handle error in async watchEffect ([#3129](https://github.com/vuejs/core/issues/3129)) ([eb1fae6](https://github.com/vuejs/core/commit/eb1fae63f926435fb0eef890663d24e09d4c79e1))
@ -202,7 +202,7 @@ may cause build issues in projects still using TS 3.x.
- **script-setup:** ensure useContext() return valid context ([73cdb9d](https://github.com/vuejs/core/commit/73cdb9d4208f887fe08349657122e39175d7166c)) - **script-setup:** ensure useContext() return valid context ([73cdb9d](https://github.com/vuejs/core/commit/73cdb9d4208f887fe08349657122e39175d7166c))
- **slots:** dynamically named slots should be keyed by name ([2ab8c41](https://github.com/vuejs/core/commit/2ab8c41a1a43952fb229587a9da48d9a1214ab9e)), closes [#2535](https://github.com/vuejs/core/issues/2535) - **slots:** dynamically named slots should be keyed by name ([2ab8c41](https://github.com/vuejs/core/commit/2ab8c41a1a43952fb229587a9da48d9a1214ab9e)), closes [#2535](https://github.com/vuejs/core/issues/2535)
- **slots:** should render fallback content when slot content contains no valid nodes ([#2485](https://github.com/vuejs/core/issues/2485)) ([ce4915d](https://github.com/vuejs/core/commit/ce4915d8bed12f4cdb5fa8ca39bda98d0d3aabb7)), closes [#2347](https://github.com/vuejs/core/issues/2347) [#2461](https://github.com/vuejs/core/issues/2461) - **slots:** should render fallback content when slot content contains no valid nodes ([#2485](https://github.com/vuejs/core/issues/2485)) ([ce4915d](https://github.com/vuejs/core/commit/ce4915d8bed12f4cdb5fa8ca39bda98d0d3aabb7)), closes [#2347](https://github.com/vuejs/core/issues/2347) [#2461](https://github.com/vuejs/core/issues/2461)
- **suspense:** fix nested async child toggle inside already resovled suspense ([cf7f1db](https://github.com/vuejs/core/commit/cf7f1dbc9be8d50ad220e3630c38f5a9a217d693)), closes [#2215](https://github.com/vuejs/core/issues/2215) - **suspense:** fix nested async child toggle inside already resolved suspense ([cf7f1db](https://github.com/vuejs/core/commit/cf7f1dbc9be8d50ad220e3630c38f5a9a217d693)), closes [#2215](https://github.com/vuejs/core/issues/2215)
- **teleport:** Teleport into SVG elements ([#2648](https://github.com/vuejs/core/issues/2648)) ([cd92836](https://github.com/vuejs/core/commit/cd928362232747a51d1fd4790bb20adcdd59d187)), closes [#2652](https://github.com/vuejs/core/issues/2652) - **teleport:** Teleport into SVG elements ([#2648](https://github.com/vuejs/core/issues/2648)) ([cd92836](https://github.com/vuejs/core/commit/cd928362232747a51d1fd4790bb20adcdd59d187)), closes [#2652](https://github.com/vuejs/core/issues/2652)
- **transition:** avoid invoking stale transition end callbacks ([eaf8a67](https://github.com/vuejs/core/commit/eaf8a67c7219e1b79d6abca44a1d7f1b341b58b0)), closes [#2482](https://github.com/vuejs/core/issues/2482) - **transition:** avoid invoking stale transition end callbacks ([eaf8a67](https://github.com/vuejs/core/commit/eaf8a67c7219e1b79d6abca44a1d7f1b341b58b0)), closes [#2482](https://github.com/vuejs/core/issues/2482)
- **transition:** respect rules in \*-leave-from transition class ([#2597](https://github.com/vuejs/core/issues/2597)) ([e2618a6](https://github.com/vuejs/core/commit/e2618a632d4add2819ffb8b575af0da189dc3204)), closes [#2593](https://github.com/vuejs/core/issues/2593) - **transition:** respect rules in \*-leave-from transition class ([#2597](https://github.com/vuejs/core/issues/2597)) ([e2618a6](https://github.com/vuejs/core/commit/e2618a632d4add2819ffb8b575af0da189dc3204)), closes [#2593](https://github.com/vuejs/core/issues/2593)
@ -236,7 +236,7 @@ may cause build issues in projects still using TS 3.x.
- **compiler-sfc:** compileScript inline render function mode ([886ed76](https://github.com/vuejs/core/commit/886ed7681dd203c07ff3b504538328f43e14d9b0)) - **compiler-sfc:** compileScript inline render function mode ([886ed76](https://github.com/vuejs/core/commit/886ed7681dd203c07ff3b504538328f43e14d9b0))
- **compiler-sfc:** new script setup implementation ([556560f](https://github.com/vuejs/core/commit/556560fae31d9e406cfae656089657b6332686c1)) - **compiler-sfc:** new script setup implementation ([556560f](https://github.com/vuejs/core/commit/556560fae31d9e406cfae656089657b6332686c1))
- **compiler-sfc:** new SFC css varaible injection implementation ([41bb7fa](https://github.com/vuejs/core/commit/41bb7fa330e78c4a354a2e67742bd13bee2f4293)) - **compiler-sfc:** new SFC css variable injection implementation ([41bb7fa](https://github.com/vuejs/core/commit/41bb7fa330e78c4a354a2e67742bd13bee2f4293))
- **compiler-sfc:** support kebab-case components in `<script setup>` sfc template ([3f99e23](https://github.com/vuejs/core/commit/3f99e239e03a8861c462d4ee91feb82066ab3e28)) - **compiler-sfc:** support kebab-case components in `<script setup>` sfc template ([3f99e23](https://github.com/vuejs/core/commit/3f99e239e03a8861c462d4ee91feb82066ab3e28))
- **runtime-core:** explicit expose API ([0e59770](https://github.com/vuejs/core/commit/0e59770b9282992f6a5af4d8fef33dafb948fc8b)) - **runtime-core:** explicit expose API ([0e59770](https://github.com/vuejs/core/commit/0e59770b9282992f6a5af4d8fef33dafb948fc8b))
@ -282,7 +282,7 @@ may cause build issues in projects still using TS 3.x.
- **runtime-core:** fix directive merging on component root ([4d1ebb5](https://github.com/vuejs/core/commit/4d1ebb5deb4c1cb2a02e8482bf8f9cc87197b088)), closes [#2298](https://github.com/vuejs/core/issues/2298) - **runtime-core:** fix directive merging on component root ([4d1ebb5](https://github.com/vuejs/core/commit/4d1ebb5deb4c1cb2a02e8482bf8f9cc87197b088)), closes [#2298](https://github.com/vuejs/core/issues/2298)
- **runtime-core:** fix duplicated unmount traversal in optimized mode ([376883d](https://github.com/vuejs/core/commit/376883d1cfea6ed92807cce1f1209f943a04b625)), closes [#2169](https://github.com/vuejs/core/issues/2169) - **runtime-core:** fix duplicated unmount traversal in optimized mode ([376883d](https://github.com/vuejs/core/commit/376883d1cfea6ed92807cce1f1209f943a04b625)), closes [#2169](https://github.com/vuejs/core/issues/2169)
- **runtime-core:** fix provide function data access in extends/mixins ([f06518a](https://github.com/vuejs/core/commit/f06518a8c9201b4fa2a956595aa9d89a192fcd20)), closes [#2300](https://github.com/vuejs/core/issues/2300) - **runtime-core:** fix provide function data access in extends/mixins ([f06518a](https://github.com/vuejs/core/commit/f06518a8c9201b4fa2a956595aa9d89a192fcd20)), closes [#2300](https://github.com/vuejs/core/issues/2300)
- **runtime-core:** fix SSR memoery leak due to props normalization cache ([a66e53a](https://github.com/vuejs/core/commit/a66e53a24f445b688eef6812ecb872dc53cf2702)), closes [#2225](https://github.com/vuejs/core/issues/2225) - **runtime-core:** fix SSR memory leak due to props normalization cache ([a66e53a](https://github.com/vuejs/core/commit/a66e53a24f445b688eef6812ecb872dc53cf2702)), closes [#2225](https://github.com/vuejs/core/issues/2225)
- **runtime-core:** make errorCaptured return value handling consistent with Vue 2 ([#2289](https://github.com/vuejs/core/issues/2289)) ([4d20ac8](https://github.com/vuejs/core/commit/4d20ac8173f84c87288255dcc03c62a6ee862a23)), closes [#2267](https://github.com/vuejs/core/issues/2267) - **runtime-core:** make errorCaptured return value handling consistent with Vue 2 ([#2289](https://github.com/vuejs/core/issues/2289)) ([4d20ac8](https://github.com/vuejs/core/commit/4d20ac8173f84c87288255dcc03c62a6ee862a23)), closes [#2267](https://github.com/vuejs/core/issues/2267)
- **runtime-core:** use consistent camelCase event casing for render functions ([#2278](https://github.com/vuejs/core/issues/2278)) ([62f2617](https://github.com/vuejs/core/commit/62f26173ba715fd8bf2b131e19d94275106e830d)), closes [#2249](https://github.com/vuejs/core/issues/2249) - **runtime-core:** use consistent camelCase event casing for render functions ([#2278](https://github.com/vuejs/core/issues/2278)) ([62f2617](https://github.com/vuejs/core/commit/62f26173ba715fd8bf2b131e19d94275106e830d)), closes [#2249](https://github.com/vuejs/core/issues/2249)
- **runtime-core:** vnode.el is null in watcher after rerendering ([#2295](https://github.com/vuejs/core/issues/2295)) ([28d5fd7](https://github.com/vuejs/core/commit/28d5fd7a2871c10df3427dfbbe0e203c2a976cb4)), closes [#2170](https://github.com/vuejs/core/issues/2170) - **runtime-core:** vnode.el is null in watcher after rerendering ([#2295](https://github.com/vuejs/core/issues/2295)) ([28d5fd7](https://github.com/vuejs/core/commit/28d5fd7a2871c10df3427dfbbe0e203c2a976cb4)), closes [#2170](https://github.com/vuejs/core/issues/2170)
@ -450,7 +450,7 @@ may cause build issues in projects still using TS 3.x.
- **compiler-core:** should attach key to single element child of `<template v-for>` ([#1910](https://github.com/vuejs/core/issues/1910)) ([69cfed6](https://github.com/vuejs/core/commit/69cfed6b313821d1ae7ecb02b63b0aaccb5599c6)) - **compiler-core:** should attach key to single element child of `<template v-for>` ([#1910](https://github.com/vuejs/core/issues/1910)) ([69cfed6](https://github.com/vuejs/core/commit/69cfed6b313821d1ae7ecb02b63b0aaccb5599c6))
- **reactivity:** unwrap non-index accessed refs on reactive arrays ([#1859](https://github.com/vuejs/core/issues/1859)) ([3c05f8b](https://github.com/vuejs/core/commit/3c05f8bbd6cd0e01bbc5830730852f9a93d8de8a)), closes [#1846](https://github.com/vuejs/core/issues/1846) - **reactivity:** unwrap non-index accessed refs on reactive arrays ([#1859](https://github.com/vuejs/core/issues/1859)) ([3c05f8b](https://github.com/vuejs/core/commit/3c05f8bbd6cd0e01bbc5830730852f9a93d8de8a)), closes [#1846](https://github.com/vuejs/core/issues/1846)
- **runtime-core:** correctly track dynamic nodes in renderSlot ([#1911](https://github.com/vuejs/core/issues/1911)) ([7ffb79c](https://github.com/vuejs/core/commit/7ffb79c56318861075a47bd2357e34cde8a6dad9)) - **runtime-core:** correctly track dynamic nodes in renderSlot ([#1911](https://github.com/vuejs/core/issues/1911)) ([7ffb79c](https://github.com/vuejs/core/commit/7ffb79c56318861075a47bd2357e34cde8a6dad9))
- **runtime-core:** disable block tracking when calling compiled slot function in tempalte expressions ([f02e2f9](https://github.com/vuejs/core/commit/f02e2f99d9c2ca95f4fd984d7bd62178eceaa214)), closes [#1745](https://github.com/vuejs/core/issues/1745) [#1918](https://github.com/vuejs/core/issues/1918) - **runtime-core:** disable block tracking when calling compiled slot function in template expressions ([f02e2f9](https://github.com/vuejs/core/commit/f02e2f99d9c2ca95f4fd984d7bd62178eceaa214)), closes [#1745](https://github.com/vuejs/core/issues/1745) [#1918](https://github.com/vuejs/core/issues/1918)
- **teleport:** only inherit el for non-patched nodes ([d4cc7b2](https://github.com/vuejs/core/commit/d4cc7b2496f9ed21ef6cac426697eac058da76bb)), closes [#1903](https://github.com/vuejs/core/issues/1903) - **teleport:** only inherit el for non-patched nodes ([d4cc7b2](https://github.com/vuejs/core/commit/d4cc7b2496f9ed21ef6cac426697eac058da76bb)), closes [#1903](https://github.com/vuejs/core/issues/1903)
### Performance Improvements ### Performance Improvements
@ -631,7 +631,7 @@ may cause build issues in projects still using TS 3.x.
- **runtime-dom/v-on:** only block event handlers based on attach timestamp ([8b320cc](https://github.com/vuejs/core/commit/8b320cc12f74aafea9ec69f7ce70231d4f0d08fd)), closes [#1565](https://github.com/vuejs/core/issues/1565) - **runtime-dom/v-on:** only block event handlers based on attach timestamp ([8b320cc](https://github.com/vuejs/core/commit/8b320cc12f74aafea9ec69f7ce70231d4f0d08fd)), closes [#1565](https://github.com/vuejs/core/issues/1565)
- **slots:** differentiate dynamic/static compiled slots ([65beba9](https://github.com/vuejs/core/commit/65beba98fe5793133d3218945218b9e3f8d136eb)), closes [#1557](https://github.com/vuejs/core/issues/1557) - **slots:** differentiate dynamic/static compiled slots ([65beba9](https://github.com/vuejs/core/commit/65beba98fe5793133d3218945218b9e3f8d136eb)), closes [#1557](https://github.com/vuejs/core/issues/1557)
- **v-on:** capitalize dynamic event names ([9152a89](https://github.com/vuejs/core/commit/9152a8901653d7cef864a52a3c618afcc70d827d)) - **v-on:** capitalize dynamic event names ([9152a89](https://github.com/vuejs/core/commit/9152a8901653d7cef864a52a3c618afcc70d827d))
- **v-on:** refactor DOM event options modifer handling ([380c679](https://github.com/vuejs/core/commit/380c6792d8899f1a43a9e6400c5df483c63290b6)), closes [#1567](https://github.com/vuejs/core/issues/1567) - **v-on:** refactor DOM event options modifier handling ([380c679](https://github.com/vuejs/core/commit/380c6792d8899f1a43a9e6400c5df483c63290b6)), closes [#1567](https://github.com/vuejs/core/issues/1567)
### Features ### Features
@ -743,7 +743,7 @@ may cause build issues in projects still using TS 3.x.
- **compiler-core:** fix parsing for directive with dynamic argument containing dots ([0d26413](https://github.com/vuejs/core/commit/0d26413433d41389f5525a0ef2c2dd7cfbb454d4)) - **compiler-core:** fix parsing for directive with dynamic argument containing dots ([0d26413](https://github.com/vuejs/core/commit/0d26413433d41389f5525a0ef2c2dd7cfbb454d4))
- **compiler-core:** support static slot names containing dots for 2.x compat ([825ec15](https://github.com/vuejs/core/commit/825ec1500feda8b0c43245e7e92074af7f9dcca2)), closes [#1241](https://github.com/vuejs/core/issues/1241) - **compiler-core:** support static slot names containing dots for 2.x compat ([825ec15](https://github.com/vuejs/core/commit/825ec1500feda8b0c43245e7e92074af7f9dcca2)), closes [#1241](https://github.com/vuejs/core/issues/1241)
- **hmr:** force full update on nested child components ([#1312](https://github.com/vuejs/core/issues/1312)) ([8f2a748](https://github.com/vuejs/core/commit/8f2a7489b7c74f5cfc1844697c60287c37fc0eb8)) - **hmr:** force full update on nested child components ([#1312](https://github.com/vuejs/core/issues/1312)) ([8f2a748](https://github.com/vuejs/core/commit/8f2a7489b7c74f5cfc1844697c60287c37fc0eb8))
- **reactivity:** fix toRaw for objects prototype inherting reactive ([10bb34b](https://github.com/vuejs/core/commit/10bb34bb869a47c37d945f8c80abf723fac9fc1a)), closes [#1246](https://github.com/vuejs/core/issues/1246) - **reactivity:** fix toRaw for objects prototype inheriting reactive ([10bb34b](https://github.com/vuejs/core/commit/10bb34bb869a47c37d945f8c80abf723fac9fc1a)), closes [#1246](https://github.com/vuejs/core/issues/1246)
- **runtime-core:** should pass instance to patchProp on mount for event error handling ([#1337](https://github.com/vuejs/core/issues/1337)) ([aac9b03](https://github.com/vuejs/core/commit/aac9b03c11c9be0c67b924004364a42d04d78195)), closes [#1336](https://github.com/vuejs/core/issues/1336) - **runtime-core:** should pass instance to patchProp on mount for event error handling ([#1337](https://github.com/vuejs/core/issues/1337)) ([aac9b03](https://github.com/vuejs/core/commit/aac9b03c11c9be0c67b924004364a42d04d78195)), closes [#1336](https://github.com/vuejs/core/issues/1336)
- **runtime-core:** track access to $attrs ([6abac87](https://github.com/vuejs/core/commit/6abac87b3d1b7a22df80b7a70a10101a7f3d3732)), closes [#1346](https://github.com/vuejs/core/issues/1346) - **runtime-core:** track access to $attrs ([6abac87](https://github.com/vuejs/core/commit/6abac87b3d1b7a22df80b7a70a10101a7f3d3732)), closes [#1346](https://github.com/vuejs/core/issues/1346)
- always treat spellcheck and draggable as attributes ([4492b88](https://github.com/vuejs/core/commit/4492b88938922a7f1bcc36a608375ad99f16b22e)), closes [#1350](https://github.com/vuejs/core/issues/1350) - always treat spellcheck and draggable as attributes ([4492b88](https://github.com/vuejs/core/commit/4492b88938922a7f1bcc36a608375ad99f16b22e)), closes [#1350](https://github.com/vuejs/core/issues/1350)
@ -863,7 +863,7 @@ may cause build issues in projects still using TS 3.x.
### Bug Fixes ### Bug Fixes
- **compiler:** bail strigification on runtime constant expressions ([f9a3766](https://github.com/vuejs/core/commit/f9a3766fd68dc6996cdbda6475287c4005f55243)) - **compiler:** bail stringification on runtime constant expressions ([f9a3766](https://github.com/vuejs/core/commit/f9a3766fd68dc6996cdbda6475287c4005f55243))
- **transitionGroup:** fix transition children resolving condition ([f05aeea](https://github.com/vuejs/core/commit/f05aeea7aec2e6cd859f40edc6236afd0ce2ea7d)) - **transitionGroup:** fix transition children resolving condition ([f05aeea](https://github.com/vuejs/core/commit/f05aeea7aec2e6cd859f40edc6236afd0ce2ea7d))
### Features ### Features

View File

@ -28,7 +28,7 @@
- **build:** avoid using async/await syntax ([438754a](https://github.com/vuejs/core/commit/438754a0d1428d10e27d1a290beb4b81da5fdaeb)) - **build:** avoid using async/await syntax ([438754a](https://github.com/vuejs/core/commit/438754a0d1428d10e27d1a290beb4b81da5fdaeb))
- **build:** fix generated code containing unprocessed class field syntax ([2788154](https://github.com/vuejs/core/commit/2788154f7707928f1dd3e4d9bd144f758a8c0478)), closes [#4052](https://github.com/vuejs/core/issues/4052) [vuejs/vue-cli#6562](https://github.com/vuejs/vue-cli/issues/6562) - **build:** fix generated code containing unprocessed class field syntax ([2788154](https://github.com/vuejs/core/commit/2788154f7707928f1dd3e4d9bd144f758a8c0478)), closes [#4052](https://github.com/vuejs/core/issues/4052) [vuejs/vue-cli#6562](https://github.com/vuejs/vue-cli/issues/6562)
- **codegen:** ensure valid types in genreated code when using global directives ([a44d528](https://github.com/vuejs/core/commit/a44d528af1227c05dedf610b6ec45504d8e58276)), closes [#4054](https://github.com/vuejs/core/issues/4054) - **codegen:** ensure valid types in generated code when using global directives ([a44d528](https://github.com/vuejs/core/commit/a44d528af1227c05dedf610b6ec45504d8e58276)), closes [#4054](https://github.com/vuejs/core/issues/4054)
- **compiler-sfc:** fix parse-only mode when there is no script setup block ([253ca27](https://github.com/vuejs/core/commit/253ca2729d808fc051215876aa4af986e4caa43c)) - **compiler-sfc:** fix parse-only mode when there is no script setup block ([253ca27](https://github.com/vuejs/core/commit/253ca2729d808fc051215876aa4af986e4caa43c))
- **runtime-core:** add useAttrs and useSlots export ([#4053](https://github.com/vuejs/core/issues/4053)) ([735ada1](https://github.com/vuejs/core/commit/735ada1507623b8d36e80b30a4f67a8af4a45c99)) - **runtime-core:** add useAttrs and useSlots export ([#4053](https://github.com/vuejs/core/issues/4053)) ([735ada1](https://github.com/vuejs/core/commit/735ada1507623b8d36e80b30a4f67a8af4a45c99))
- **runtime-core:** fix instance accessed via $parent chain when using expose() ([#4048](https://github.com/vuejs/core/issues/4048)) ([12cf9f4](https://github.com/vuejs/core/commit/12cf9f4ea148a59fd9002ecf9ea9d365829ce37c)) - **runtime-core:** fix instance accessed via $parent chain when using expose() ([#4048](https://github.com/vuejs/core/issues/4048)) ([12cf9f4](https://github.com/vuejs/core/commit/12cf9f4ea148a59fd9002ecf9ea9d365829ce37c))
@ -114,7 +114,7 @@
### Performance Improvements ### Performance Improvements
- only trigger `$attrs` update when it has actually changed ([5566d39](https://github.com/vuejs/core/commit/5566d39d467ebdd4e4234bc97d62600ff01ea28e)) - only trigger `$attrs` update when it has actually changed ([5566d39](https://github.com/vuejs/core/commit/5566d39d467ebdd4e4234bc97d62600ff01ea28e))
- **compiler:** skip unncessary checks when parsing end tag ([048ac29](https://github.com/vuejs/core/commit/048ac299f35709b25ae1bc1efa67d2abc53dbc3b)) - **compiler:** skip unnecessary checks when parsing end tag ([048ac29](https://github.com/vuejs/core/commit/048ac299f35709b25ae1bc1efa67d2abc53dbc3b))
- avoid deopt for props/emits normalization when global mixins are used ([51d2be2](https://github.com/vuejs/core/commit/51d2be20386d4dc59006d31a1cc96676871027ce)) - avoid deopt for props/emits normalization when global mixins are used ([51d2be2](https://github.com/vuejs/core/commit/51d2be20386d4dc59006d31a1cc96676871027ce))
### Deprecations ### Deprecations
@ -181,7 +181,7 @@
* **compat:** avoid accidentally delete the modelValue prop ([#3772](https://github.com/vuejs/core/issues/3772)) ([4f17be7](https://github.com/vuejs/core/commit/4f17be7b1ce4872ded085a36b95c1897d8c1f299)) * **compat:** avoid accidentally delete the modelValue prop ([#3772](https://github.com/vuejs/core/issues/3772)) ([4f17be7](https://github.com/vuejs/core/commit/4f17be7b1ce4872ded085a36b95c1897d8c1f299))
* **compat:** enum coercion warning ([#3755](https://github.com/vuejs/core/issues/3755)) ([f01aadf](https://github.com/vuejs/core/commit/f01aadf2a16a7bef422eb039d7b157bef9ad32fc)) * **compat:** enum coercion warning ([#3755](https://github.com/vuejs/core/issues/3755)) ([f01aadf](https://github.com/vuejs/core/commit/f01aadf2a16a7bef422eb039d7b157bef9ad32fc))
* **compiler-core:** fix whitespace management for slots with whitespace: 'preserve' ([#3767](https://github.com/vuejs/core/issues/3767)) ([47da921](https://github.com/vuejs/core/commit/47da92146c9fb3fa6b1e250e064ca49b74d815e4)), closes [#3766](https://github.com/vuejs/core/issues/3766) * **compiler-core:** fix whitespace management for slots with whitespace: 'preserve' ([#3767](https://github.com/vuejs/core/issues/3767)) ([47da921](https://github.com/vuejs/core/commit/47da92146c9fb3fa6b1e250e064ca49b74d815e4)), closes [#3766](https://github.com/vuejs/core/issues/3766)
* **compiler-dom:** comments in the v-if branchs should be ignored when used in Transition ([#3622](https://github.com/vuejs/core/issues/3622)) ([7c74feb](https://github.com/vuejs/core/commit/7c74feb3dc6beae7ff3ad22193be3b5a0f4d8aac)), closes [#3619](https://github.com/vuejs/core/issues/3619) * **compiler-dom:** comments in the v-if branches should be ignored when used in Transition ([#3622](https://github.com/vuejs/core/issues/3622)) ([7c74feb](https://github.com/vuejs/core/commit/7c74feb3dc6beae7ff3ad22193be3b5a0f4d8aac)), closes [#3619](https://github.com/vuejs/core/issues/3619)
* **compiler-sfc:** support tsx in setup script ([#3825](https://github.com/vuejs/core/issues/3825)) ([01e8ba8](https://github.com/vuejs/core/commit/01e8ba8f873afe3857a23fb68b44fdc057e31781)), closes [#3808](https://github.com/vuejs/core/issues/3808) * **compiler-sfc:** support tsx in setup script ([#3825](https://github.com/vuejs/core/issues/3825)) ([01e8ba8](https://github.com/vuejs/core/commit/01e8ba8f873afe3857a23fb68b44fdc057e31781)), closes [#3808](https://github.com/vuejs/core/issues/3808)
* **compiler-ssr:** disable hoisting in compiler-ssr ([3ef1fcc](https://github.com/vuejs/core/commit/3ef1fcc8590da186664197a0a82e7856011c1693)), closes [#3536](https://github.com/vuejs/core/issues/3536) * **compiler-ssr:** disable hoisting in compiler-ssr ([3ef1fcc](https://github.com/vuejs/core/commit/3ef1fcc8590da186664197a0a82e7856011c1693)), closes [#3536](https://github.com/vuejs/core/issues/3536)
* **devtools:** send update to component owning the slot ([1355ee2](https://github.com/vuejs/core/commit/1355ee27a65d466bfe8f3a7ba99aa2213e25bc50)) * **devtools:** send update to component owning the slot ([1355ee2](https://github.com/vuejs/core/commit/1355ee27a65d466bfe8f3a7ba99aa2213e25bc50))
@ -265,7 +265,7 @@
- **compat:** avoid accidentally delete the modelValue prop ([#3772](https://github.com/vuejs/core/issues/3772)) ([4f17be7](https://github.com/vuejs/core/commit/4f17be7b1ce4872ded085a36b95c1897d8c1f299)) - **compat:** avoid accidentally delete the modelValue prop ([#3772](https://github.com/vuejs/core/issues/3772)) ([4f17be7](https://github.com/vuejs/core/commit/4f17be7b1ce4872ded085a36b95c1897d8c1f299))
- **compat:** enum coercion warning ([#3755](https://github.com/vuejs/core/issues/3755)) ([f01aadf](https://github.com/vuejs/core/commit/f01aadf2a16a7bef422eb039d7b157bef9ad32fc)) - **compat:** enum coercion warning ([#3755](https://github.com/vuejs/core/issues/3755)) ([f01aadf](https://github.com/vuejs/core/commit/f01aadf2a16a7bef422eb039d7b157bef9ad32fc))
- **compiler-core:** fix whitespace management for slots with whitespace: 'preserve' ([#3767](https://github.com/vuejs/core/issues/3767)) ([47da921](https://github.com/vuejs/core/commit/47da92146c9fb3fa6b1e250e064ca49b74d815e4)), closes [#3766](https://github.com/vuejs/core/issues/3766) - **compiler-core:** fix whitespace management for slots with whitespace: 'preserve' ([#3767](https://github.com/vuejs/core/issues/3767)) ([47da921](https://github.com/vuejs/core/commit/47da92146c9fb3fa6b1e250e064ca49b74d815e4)), closes [#3766](https://github.com/vuejs/core/issues/3766)
- **compiler-dom:** comments in the v-if branchs should be ignored when used in Transition ([#3622](https://github.com/vuejs/core/issues/3622)) ([7c74feb](https://github.com/vuejs/core/commit/7c74feb3dc6beae7ff3ad22193be3b5a0f4d8aac)), closes [#3619](https://github.com/vuejs/core/issues/3619) - **compiler-dom:** comments in the v-if branches should be ignored when used in Transition ([#3622](https://github.com/vuejs/core/issues/3622)) ([7c74feb](https://github.com/vuejs/core/commit/7c74feb3dc6beae7ff3ad22193be3b5a0f4d8aac)), closes [#3619](https://github.com/vuejs/core/issues/3619)
- **compiler-sfc:** support tsx in setup script ([#3825](https://github.com/vuejs/core/issues/3825)) ([01e8ba8](https://github.com/vuejs/core/commit/01e8ba8f873afe3857a23fb68b44fdc057e31781)), closes [#3808](https://github.com/vuejs/core/issues/3808) - **compiler-sfc:** support tsx in setup script ([#3825](https://github.com/vuejs/core/issues/3825)) ([01e8ba8](https://github.com/vuejs/core/commit/01e8ba8f873afe3857a23fb68b44fdc057e31781)), closes [#3808](https://github.com/vuejs/core/issues/3808)
- **compiler-ssr:** disable hoisting in compiler-ssr ([3ef1fcc](https://github.com/vuejs/core/commit/3ef1fcc8590da186664197a0a82e7856011c1693)), closes [#3536](https://github.com/vuejs/core/issues/3536) - **compiler-ssr:** disable hoisting in compiler-ssr ([3ef1fcc](https://github.com/vuejs/core/commit/3ef1fcc8590da186664197a0a82e7856011c1693)), closes [#3536](https://github.com/vuejs/core/issues/3536)
- **devtools:** send update to component owning the slot ([1355ee2](https://github.com/vuejs/core/commit/1355ee27a65d466bfe8f3a7ba99aa2213e25bc50)) - **devtools:** send update to component owning the slot ([1355ee2](https://github.com/vuejs/core/commit/1355ee27a65d466bfe8f3a7ba99aa2213e25bc50))
@ -317,4 +317,4 @@
### Performance Improvements ### Performance Improvements
- only trigger $attrs update when it has actually changed ([5566d39](https://github.com/vuejs/core/commit/5566d39d467ebdd4e4234bc97d62600ff01ea28e)) - only trigger $attrs update when it has actually changed ([5566d39](https://github.com/vuejs/core/commit/5566d39d467ebdd4e4234bc97d62600ff01ea28e))
- **compiler:** skip unncessary checks when parsing end tag ([048ac29](https://github.com/vuejs/core/commit/048ac299f35709b25ae1bc1efa67d2abc53dbc3b)) - **compiler:** skip unnecessary checks when parsing end tag ([048ac29](https://github.com/vuejs/core/commit/048ac299f35709b25ae1bc1efa67d2abc53dbc3b))

View File

@ -26,7 +26,7 @@
* **reactivity-transform:** fix $$ escape edge cases ([e06d3b6](https://github.com/vuejs/core/commit/e06d3b614ea518e9cdf83fca9200fc816eb4e5a1)), closes [#6312](https://github.com/vuejs/core/issues/6312) [#6944](https://github.com/vuejs/core/issues/6944) * **reactivity-transform:** fix $$ escape edge cases ([e06d3b6](https://github.com/vuejs/core/commit/e06d3b614ea518e9cdf83fca9200fc816eb4e5a1)), closes [#6312](https://github.com/vuejs/core/issues/6312) [#6944](https://github.com/vuejs/core/issues/6944)
* **reactivity-transform:** prohibit const assignment at compile time ([#6993](https://github.com/vuejs/core/issues/6993)) ([3427052](https://github.com/vuejs/core/commit/3427052229db3448252d938292a40e960a0f4b9c)), closes [#6992](https://github.com/vuejs/core/issues/6992) * **reactivity-transform:** prohibit const assignment at compile time ([#6993](https://github.com/vuejs/core/issues/6993)) ([3427052](https://github.com/vuejs/core/commit/3427052229db3448252d938292a40e960a0f4b9c)), closes [#6992](https://github.com/vuejs/core/issues/6992)
* **reactivity:** `triggerRef` working with `toRef` from reactive ([#7507](https://github.com/vuejs/core/issues/7507)) ([e64c9ae](https://github.com/vuejs/core/commit/e64c9ae957aa2606b55e8652bbde30a6ada59fb0)) * **reactivity:** `triggerRef` working with `toRef` from reactive ([#7507](https://github.com/vuejs/core/issues/7507)) ([e64c9ae](https://github.com/vuejs/core/commit/e64c9ae957aa2606b55e8652bbde30a6ada59fb0))
* **reactivity:** ensure watch(Effect) can run independent of unmounted instance if created in a detatched effectScope (fix [#7319](https://github.com/vuejs/core/issues/7319)) ([#7330](https://github.com/vuejs/core/issues/7330)) ([cd7c887](https://github.com/vuejs/core/commit/cd7c887b755810aedf83f3d458cb956d5b147f6f)) * **reactivity:** ensure watch(Effect) can run independent of unmounted instance if created in a detached effectScope (fix [#7319](https://github.com/vuejs/core/issues/7319)) ([#7330](https://github.com/vuejs/core/issues/7330)) ([cd7c887](https://github.com/vuejs/core/commit/cd7c887b755810aedf83f3d458cb956d5b147f6f))
* **reactivity:** track hasOwnProperty ([588bd44](https://github.com/vuejs/core/commit/588bd44f036b79d7dee5d23661aa7244f70e6beb)), closes [#2619](https://github.com/vuejs/core/issues/2619) [#2621](https://github.com/vuejs/core/issues/2621) * **reactivity:** track hasOwnProperty ([588bd44](https://github.com/vuejs/core/commit/588bd44f036b79d7dee5d23661aa7244f70e6beb)), closes [#2619](https://github.com/vuejs/core/issues/2619) [#2621](https://github.com/vuejs/core/issues/2621)
* **runtime-core:** ensure prop type validation warning shows custom class names ([#7198](https://github.com/vuejs/core/issues/7198)) ([620327d](https://github.com/vuejs/core/commit/620327d527593c6263a21500baddbae1ebc30db8)) * **runtime-core:** ensure prop type validation warning shows custom class names ([#7198](https://github.com/vuejs/core/issues/7198)) ([620327d](https://github.com/vuejs/core/commit/620327d527593c6263a21500baddbae1ebc30db8))
* **runtime-core:** fix keep-alive cache prune logic on vnodes with same type but different keys ([#7510](https://github.com/vuejs/core/issues/7510)) ([1fde49c](https://github.com/vuejs/core/commit/1fde49c0f57cc50fedf91366a274c9759d1d9a39)), closes [#7355](https://github.com/vuejs/core/issues/7355) * **runtime-core:** fix keep-alive cache prune logic on vnodes with same type but different keys ([#7510](https://github.com/vuejs/core/issues/7510)) ([1fde49c](https://github.com/vuejs/core/commit/1fde49c0f57cc50fedf91366a274c9759d1d9a39)), closes [#7355](https://github.com/vuejs/core/issues/7355)
@ -126,7 +126,7 @@
* **transition/keep-alive:** fix unmount bug for component with out-in transition ([#6839](https://github.com/vuejs/core/issues/6839)) ([64e6d92](https://github.com/vuejs/core/commit/64e6d9221d353598b5f61c158c978d80e3b4628c)), closes [#6835](https://github.com/vuejs/core/issues/6835) * **transition/keep-alive:** fix unmount bug for component with out-in transition ([#6839](https://github.com/vuejs/core/issues/6839)) ([64e6d92](https://github.com/vuejs/core/commit/64e6d9221d353598b5f61c158c978d80e3b4628c)), closes [#6835](https://github.com/vuejs/core/issues/6835)
* **types/reactivity-transform:** fix type when initial value is not used ([#6821](https://github.com/vuejs/core/issues/6821)) ([fdc5902](https://github.com/vuejs/core/commit/fdc5902cce0d077c722dfd422850ca69fd51be8e)), closes [#6820](https://github.com/vuejs/core/issues/6820) * **types/reactivity-transform:** fix type when initial value is not used ([#6821](https://github.com/vuejs/core/issues/6821)) ([fdc5902](https://github.com/vuejs/core/commit/fdc5902cce0d077c722dfd422850ca69fd51be8e)), closes [#6820](https://github.com/vuejs/core/issues/6820)
* **types:** `$watch` callback parameters type ([#6136](https://github.com/vuejs/core/issues/6136)) ([41d9c47](https://github.com/vuejs/core/commit/41d9c47300888fce9d4ff6a02f69d8a912cded8f)), closes [#6135](https://github.com/vuejs/core/issues/6135) * **types:** `$watch` callback parameters type ([#6136](https://github.com/vuejs/core/issues/6136)) ([41d9c47](https://github.com/vuejs/core/commit/41d9c47300888fce9d4ff6a02f69d8a912cded8f)), closes [#6135](https://github.com/vuejs/core/issues/6135)
* **types:** ensure createBlock() helper accepts Teleport and Supsense types (fix: [#2855](https://github.com/vuejs/core/issues/2855)) ([#5458](https://github.com/vuejs/core/issues/5458)) ([e5fc7dc](https://github.com/vuejs/core/commit/e5fc7dcc02f2dd3fa8172958259049031626375f)) * **types:** ensure createBlock() helper accepts Teleport and Suspense types (fix: [#2855](https://github.com/vuejs/core/issues/2855)) ([#5458](https://github.com/vuejs/core/issues/5458)) ([e5fc7dc](https://github.com/vuejs/core/commit/e5fc7dcc02f2dd3fa8172958259049031626375f))
* **types:** export `Raw` type ([#6380](https://github.com/vuejs/core/issues/6380)) ([e9172db](https://github.com/vuejs/core/commit/e9172db68b86fad2e0bb1de9e5d0dddbe3c2a25e)), closes [#7048](https://github.com/vuejs/core/issues/7048) * **types:** export `Raw` type ([#6380](https://github.com/vuejs/core/issues/6380)) ([e9172db](https://github.com/vuejs/core/commit/e9172db68b86fad2e0bb1de9e5d0dddbe3c2a25e)), closes [#7048](https://github.com/vuejs/core/issues/7048)
* **types:** should unwrap tuple correctly ([#3820](https://github.com/vuejs/core/issues/3820)) ([e816812](https://github.com/vuejs/core/commit/e816812f10b9e3a375eef8dffd617d7f08b23c00)), closes [#3819](https://github.com/vuejs/core/issues/3819) * **types:** should unwrap tuple correctly ([#3820](https://github.com/vuejs/core/issues/3820)) ([e816812](https://github.com/vuejs/core/commit/e816812f10b9e3a375eef8dffd617d7f08b23c00)), closes [#3819](https://github.com/vuejs/core/issues/3819)
* **types:** stricter type condition for `EventHandlers` ([#6855](https://github.com/vuejs/core/issues/6855)) ([bad3f3c](https://github.com/vuejs/core/commit/bad3f3ce46aad1f5fec47d1d02aee26af393bcff)), closes [#6899](https://github.com/vuejs/core/issues/6899) * **types:** stricter type condition for `EventHandlers` ([#6855](https://github.com/vuejs/core/issues/6855)) ([bad3f3c](https://github.com/vuejs/core/commit/bad3f3ce46aad1f5fec47d1d02aee26af393bcff)), closes [#6899](https://github.com/vuejs/core/issues/6899)
@ -714,7 +714,7 @@
* **compiler-core:** avoid runtime dependency on @babel/types ([1045590](https://github.com/vuejs/core/commit/1045590d4bbaf4a2b05311f11b22a0b3d22cf609)), closes [#4531](https://github.com/vuejs/core/issues/4531) * **compiler-core:** avoid runtime dependency on @babel/types ([1045590](https://github.com/vuejs/core/commit/1045590d4bbaf4a2b05311f11b22a0b3d22cf609)), closes [#4531](https://github.com/vuejs/core/issues/4531)
* **compiler-core:** pick last char when dynamic directive doesn't close ([#4507](https://github.com/vuejs/core/issues/4507)) ([5d262e0](https://github.com/vuejs/core/commit/5d262e08d5d5fb29f48ba5fa5b97a9a3e34b9d4b)) * **compiler-core:** pick last char when dynamic directive doesn't close ([#4507](https://github.com/vuejs/core/issues/4507)) ([5d262e0](https://github.com/vuejs/core/commit/5d262e08d5d5fb29f48ba5fa5b97a9a3e34b9d4b))
* **compiler:** condense whitespaces in static class attributes ([#4432](https://github.com/vuejs/core/issues/4432)) ([b8653d3](https://github.com/vuejs/core/commit/b8653d390a555e1ee3f92a1c49cfd8800c67e46a)), closes [#4251](https://github.com/vuejs/core/issues/4251) * **compiler:** condense whitespaces in static class attributes ([#4432](https://github.com/vuejs/core/issues/4432)) ([b8653d3](https://github.com/vuejs/core/commit/b8653d390a555e1ee3f92a1c49cfd8800c67e46a)), closes [#4251](https://github.com/vuejs/core/issues/4251)
* **runtime-dom:** style patching shoud always preserve v-show display property ([d534515](https://github.com/vuejs/core/commit/d53451583684c37bda7d30bff912216e1a58126f)), closes [#4424](https://github.com/vuejs/core/issues/4424) * **runtime-dom:** style patching should always preserve v-show display property ([d534515](https://github.com/vuejs/core/commit/d53451583684c37bda7d30bff912216e1a58126f)), closes [#4424](https://github.com/vuejs/core/issues/4424)
* **type:** fix prop type infer ([#4530](https://github.com/vuejs/core/issues/4530)) ([4178d5d](https://github.com/vuejs/core/commit/4178d5d7d9549a0a1d19663bc2f92c8ac6a731b2)), closes [#4525](https://github.com/vuejs/core/issues/4525) * **type:** fix prop type infer ([#4530](https://github.com/vuejs/core/issues/4530)) ([4178d5d](https://github.com/vuejs/core/commit/4178d5d7d9549a0a1d19663bc2f92c8ac6a731b2)), closes [#4525](https://github.com/vuejs/core/issues/4525)
@ -741,7 +741,7 @@
* **compiler-sfc:** ensure script setup generates type-valid ts output ([bacb201](https://github.com/vuejs/core/commit/bacb2012acb4045a2db6988ba4545a7655d6ca14)), closes [#4455](https://github.com/vuejs/core/issues/4455) * **compiler-sfc:** ensure script setup generates type-valid ts output ([bacb201](https://github.com/vuejs/core/commit/bacb2012acb4045a2db6988ba4545a7655d6ca14)), closes [#4455](https://github.com/vuejs/core/issues/4455)
* **compiler-sfc:** generate matching prop types when withDefaults is used ([#4466](https://github.com/vuejs/core/issues/4466)) ([8580796](https://github.com/vuejs/core/commit/85807967dc874e6ea6b20f341875beda938e3058)), closes [#4455](https://github.com/vuejs/core/issues/4455) * **compiler-sfc:** generate matching prop types when withDefaults is used ([#4466](https://github.com/vuejs/core/issues/4466)) ([8580796](https://github.com/vuejs/core/commit/85807967dc874e6ea6b20f341875beda938e3058)), closes [#4455](https://github.com/vuejs/core/issues/4455)
* **compiler:** generate function ref for script setup if inline is ture. ([#4492](https://github.com/vuejs/core/issues/4492)) ([4cd282b](https://github.com/vuejs/core/commit/4cd282b0a17589ef9ca2649e7beb0bdee4a73c57)) * **compiler:** generate function ref for script setup if inline is true. ([#4492](https://github.com/vuejs/core/issues/4492)) ([4cd282b](https://github.com/vuejs/core/commit/4cd282b0a17589ef9ca2649e7beb0bdee4a73c57))
* **compiler:** report invalid directive name error ([#4494](https://github.com/vuejs/core/issues/4494)) ([#4495](https://github.com/vuejs/core/issues/4495)) ([c00925e](https://github.com/vuejs/core/commit/c00925ed5c409b57a1540b79c595b7f8117e2d4c)) * **compiler:** report invalid directive name error ([#4494](https://github.com/vuejs/core/issues/4494)) ([#4495](https://github.com/vuejs/core/issues/4495)) ([c00925e](https://github.com/vuejs/core/commit/c00925ed5c409b57a1540b79c595b7f8117e2d4c))
* **types:** include ref-macros.d.ts in npm dist files ([d7f1b77](https://github.com/vuejs/core/commit/d7f1b771f80ab9014a4701913b50458fd251a117)), closes [#4433](https://github.com/vuejs/core/issues/4433) * **types:** include ref-macros.d.ts in npm dist files ([d7f1b77](https://github.com/vuejs/core/commit/d7f1b771f80ab9014a4701913b50458fd251a117)), closes [#4433](https://github.com/vuejs/core/issues/4433)
@ -798,7 +798,7 @@
### Bug Fixes ### Bug Fixes
* **compiler-sfc:** fix import usage check for lowercase imported components ([57f1081](https://github.com/vuejs/core/commit/57f10812cc7f1e9f6c92736c36aba577943996fd)), closes [#4358](https://github.com/vuejs/core/issues/4358) * **compiler-sfc:** fix import usage check for lowercase imported components ([57f1081](https://github.com/vuejs/core/commit/57f10812cc7f1e9f6c92736c36aba577943996fd)), closes [#4358](https://github.com/vuejs/core/issues/4358)
* **runtime-core:** ensure consistent arguments for tempalte and render funtion slot usage ([644971e](https://github.com/vuejs/core/commit/644971ec06642817cf7e720ad4980182d2140f53)), closes [#4367](https://github.com/vuejs/core/issues/4367) * **runtime-core:** ensure consistent arguments for template and render function slot usage ([644971e](https://github.com/vuejs/core/commit/644971ec06642817cf7e720ad4980182d2140f53)), closes [#4367](https://github.com/vuejs/core/issues/4367)
* **runtime-core:** fix child component double update on props change ([c1f564e](https://github.com/vuejs/core/commit/c1f564e1dc40eda9af657c30cd787a8d770dde0f)), closes [#4365](https://github.com/vuejs/core/issues/4365) * **runtime-core:** fix child component double update on props change ([c1f564e](https://github.com/vuejs/core/commit/c1f564e1dc40eda9af657c30cd787a8d770dde0f)), closes [#4365](https://github.com/vuejs/core/issues/4365)

View File

@ -259,7 +259,7 @@
* **sfc:** support imported types in SFC macros ([#8083](https://github.com/vuejs/core/pull/8083)) * **sfc:** support imported types in SFC macros ([#8083](https://github.com/vuejs/core/pull/8083))
* **types/slots:** support slot presence / props type checks via `defineSlots` macro and `slots` option ([#7982](https://github.com/vuejs/core/issues/7982)) ([5a2f5d5](https://github.com/vuejs/core/commit/5a2f5d59cffa36a99e6f2feab6b3ba7958b7362f)) * **types/slots:** support slot presence / props type checks via `defineSlots` macro and `slots` option ([#7982](https://github.com/vuejs/core/issues/7982)) ([5a2f5d5](https://github.com/vuejs/core/commit/5a2f5d59cffa36a99e6f2feab6b3ba7958b7362f))
* **sfc:** support more ergnomic defineEmits type syntax ([#7992](https://github.com/vuejs/core/issues/7992)) ([8876dcc](https://github.com/vuejs/core/commit/8876dccf42a7f05375d97cb18c1afdfd0fc51c94)) * **sfc:** support more ergonomic defineEmits type syntax ([#7992](https://github.com/vuejs/core/issues/7992)) ([8876dcc](https://github.com/vuejs/core/commit/8876dccf42a7f05375d97cb18c1afdfd0fc51c94))
* **sfc:** introduce `defineModel` macro and `useModel` helper ([#8018](https://github.com/vuejs/core/issues/8018)) ([14f3d74](https://github.com/vuejs/core/commit/14f3d747a34d45415b0036b274517d70a27ec0d3)) * **sfc:** introduce `defineModel` macro and `useModel` helper ([#8018](https://github.com/vuejs/core/issues/8018)) ([14f3d74](https://github.com/vuejs/core/commit/14f3d747a34d45415b0036b274517d70a27ec0d3))
* **reactivity:** improve support of getter usage in reactivity APIs ([#7997](https://github.com/vuejs/core/issues/7997)) ([59e8284](https://github.com/vuejs/core/commit/59e828448e7f37643cd0eaea924a764e9d314448)) * **reactivity:** improve support of getter usage in reactivity APIs ([#7997](https://github.com/vuejs/core/issues/7997)) ([59e8284](https://github.com/vuejs/core/commit/59e828448e7f37643cd0eaea924a764e9d314448))
* **compiler-sfc:** add defineOptions macro ([#5738](https://github.com/vuejs/core/issues/5738)) ([bcf5841](https://github.com/vuejs/core/commit/bcf5841ddecc64d0bdbd56ce1463eb8ebf01bb9d)) * **compiler-sfc:** add defineOptions macro ([#5738](https://github.com/vuejs/core/issues/5738)) ([bcf5841](https://github.com/vuejs/core/commit/bcf5841ddecc64d0bdbd56ce1463eb8ebf01bb9d))
@ -483,7 +483,7 @@
* **compiler-sfc:** support arbitrary expression as withDefaults argument ([fe61944](https://github.com/vuejs/core/commit/fe619443d2e99301975de120685dbae8d66c03a6)), closes [#6459](https://github.com/vuejs/core/issues/6459) * **compiler-sfc:** support arbitrary expression as withDefaults argument ([fe61944](https://github.com/vuejs/core/commit/fe619443d2e99301975de120685dbae8d66c03a6)), closes [#6459](https://github.com/vuejs/core/issues/6459)
* **reactivity:** improve support of getter usage in reactivity APIs ([#7997](https://github.com/vuejs/core/issues/7997)) ([59e8284](https://github.com/vuejs/core/commit/59e828448e7f37643cd0eaea924a764e9d314448)) * **reactivity:** improve support of getter usage in reactivity APIs ([#7997](https://github.com/vuejs/core/issues/7997)) ([59e8284](https://github.com/vuejs/core/commit/59e828448e7f37643cd0eaea924a764e9d314448))
* **sfc:** revert withDefaults() deprecation ([4af5d1b](https://github.com/vuejs/core/commit/4af5d1b0754035058436f9e4e5c12aedef199177)) * **sfc:** revert withDefaults() deprecation ([4af5d1b](https://github.com/vuejs/core/commit/4af5d1b0754035058436f9e4e5c12aedef199177))
* **sfc:** support more ergnomic defineEmits type syntax ([#7992](https://github.com/vuejs/core/issues/7992)) ([8876dcc](https://github.com/vuejs/core/commit/8876dccf42a7f05375d97cb18c1afdfd0fc51c94)) * **sfc:** support more ergonomic defineEmits type syntax ([#7992](https://github.com/vuejs/core/issues/7992)) ([8876dcc](https://github.com/vuejs/core/commit/8876dccf42a7f05375d97cb18c1afdfd0fc51c94))
* **types/slots:** support slot presence / props type checks via `defineSlots` macro and `slots` option ([#7982](https://github.com/vuejs/core/issues/7982)) ([5a2f5d5](https://github.com/vuejs/core/commit/5a2f5d59cffa36a99e6f2feab6b3ba7958b7362f)) * **types/slots:** support slot presence / props type checks via `defineSlots` macro and `slots` option ([#7982](https://github.com/vuejs/core/issues/7982)) ([5a2f5d5](https://github.com/vuejs/core/commit/5a2f5d59cffa36a99e6f2feab6b3ba7958b7362f))
@ -544,7 +544,7 @@
### Bug Fixes ### Bug Fixes
* **runtime-core:** support `getCurrentInstance` across mutiple builds of Vue ([8d2d5bf](https://github.com/vuejs/core/commit/8d2d5bf48a24dab44e5b03cb8fa0c5faa4b696e3)) * **runtime-core:** support `getCurrentInstance` across multiple builds of Vue ([8d2d5bf](https://github.com/vuejs/core/commit/8d2d5bf48a24dab44e5b03cb8fa0c5faa4b696e3))
* **types:** ensure defineProps with generics return correct types ([c288c7b](https://github.com/vuejs/core/commit/c288c7b0bd6077d690f42153c3fc49a45454a66a)) * **types:** ensure defineProps with generics return correct types ([c288c7b](https://github.com/vuejs/core/commit/c288c7b0bd6077d690f42153c3fc49a45454a66a))

View File

@ -167,7 +167,7 @@
### Bug Fixes ### Bug Fixes
* **compat:** correctly transform non-identifier expressions in legacy filter syntax ([#10896](https://github.com/vuejs/core/issues/10896)) ([07b3c4b](https://github.com/vuejs/core/commit/07b3c4b7860009e19446f3d78571556c5737d82a)), closes [#10852](https://github.com/vuejs/core/issues/10852) * **compat:** correctly transform non-identifier expressions in legacy filter syntax ([#10896](https://github.com/vuejs/core/issues/10896)) ([07b3c4b](https://github.com/vuejs/core/commit/07b3c4b7860009e19446f3d78571556c5737d82a)), closes [#10852](https://github.com/vuejs/core/issues/10852)
* **compat:** ensure proper handling of render fuction from SFC using Vue.extend ([#7781](https://github.com/vuejs/core/issues/7781)) ([c73847f](https://github.com/vuejs/core/commit/c73847f2becc20f03cb9c68748eea92455e688ee)), closes [#7766](https://github.com/vuejs/core/issues/7766) * **compat:** ensure proper handling of render function from SFC using Vue.extend ([#7781](https://github.com/vuejs/core/issues/7781)) ([c73847f](https://github.com/vuejs/core/commit/c73847f2becc20f03cb9c68748eea92455e688ee)), closes [#7766](https://github.com/vuejs/core/issues/7766)
* **compat:** only warn ATTR_FALSE_VALUE when enabled ([04729ba](https://github.com/vuejs/core/commit/04729ba2163d840f0ca7866bc964696eb5557804)), closes [#11126](https://github.com/vuejs/core/issues/11126) * **compat:** only warn ATTR_FALSE_VALUE when enabled ([04729ba](https://github.com/vuejs/core/commit/04729ba2163d840f0ca7866bc964696eb5557804)), closes [#11126](https://github.com/vuejs/core/issues/11126)
* **compile-sfc:** register props destructure rest id as setup bindings ([#10888](https://github.com/vuejs/core/issues/10888)) ([b2b5f57](https://github.com/vuejs/core/commit/b2b5f57c2c945edd0eebc1b545ec1b7568e51484)), closes [#10885](https://github.com/vuejs/core/issues/10885) * **compile-sfc:** register props destructure rest id as setup bindings ([#10888](https://github.com/vuejs/core/issues/10888)) ([b2b5f57](https://github.com/vuejs/core/commit/b2b5f57c2c945edd0eebc1b545ec1b7568e51484)), closes [#10885](https://github.com/vuejs/core/issues/10885)
* **compile-sfc:** Support project reference with folder, ([#10908](https://github.com/vuejs/core/issues/10908)) ([bdeac37](https://github.com/vuejs/core/commit/bdeac377c7b85888193b49ac187e927636cc40bc)), closes [#10907](https://github.com/vuejs/core/issues/10907) * **compile-sfc:** Support project reference with folder, ([#10908](https://github.com/vuejs/core/issues/10908)) ([bdeac37](https://github.com/vuejs/core/commit/bdeac377c7b85888193b49ac187e927636cc40bc)), closes [#10907](https://github.com/vuejs/core/issues/10907)
@ -218,7 +218,7 @@
### Bug Fixes ### Bug Fixes
* **compat:** include legacy scoped slots ([#10868](https://github.com/vuejs/core/issues/10868)) ([8366126](https://github.com/vuejs/core/commit/83661264a4ced3cb2ff6800904a86dd9e82bbfe2)), closes [#8869](https://github.com/vuejs/core/issues/8869) * **compat:** include legacy scoped slots ([#10868](https://github.com/vuejs/core/issues/10868)) ([8366126](https://github.com/vuejs/core/commit/83661264a4ced3cb2ff6800904a86dd9e82bbfe2)), closes [#8869](https://github.com/vuejs/core/issues/8869)
* **compiler-core:** add support for arrow aysnc function with unbracketed ([#5789](https://github.com/vuejs/core/issues/5789)) ([ca7d421](https://github.com/vuejs/core/commit/ca7d421e8775f6813f8943d32ab485e0c542f98b)), closes [#5788](https://github.com/vuejs/core/issues/5788) * **compiler-core:** add support for arrow async function with unbracketed ([#5789](https://github.com/vuejs/core/issues/5789)) ([ca7d421](https://github.com/vuejs/core/commit/ca7d421e8775f6813f8943d32ab485e0c542f98b)), closes [#5788](https://github.com/vuejs/core/issues/5788)
* **compiler-dom:** restrict createStaticVNode usage with option elements ([#10846](https://github.com/vuejs/core/issues/10846)) ([0e3d617](https://github.com/vuejs/core/commit/0e3d6178b02d0386d779720ae2cc4eac1d1ec990)), closes [#6568](https://github.com/vuejs/core/issues/6568) [#7434](https://github.com/vuejs/core/issues/7434) * **compiler-dom:** restrict createStaticVNode usage with option elements ([#10846](https://github.com/vuejs/core/issues/10846)) ([0e3d617](https://github.com/vuejs/core/commit/0e3d6178b02d0386d779720ae2cc4eac1d1ec990)), closes [#6568](https://github.com/vuejs/core/issues/6568) [#7434](https://github.com/vuejs/core/issues/7434)
* **compiler-sfc:** handle keyof operator ([#10874](https://github.com/vuejs/core/issues/10874)) ([10d34a5](https://github.com/vuejs/core/commit/10d34a5624775f20437ccad074a97270ef74c3fb)), closes [#10871](https://github.com/vuejs/core/issues/10871) * **compiler-sfc:** handle keyof operator ([#10874](https://github.com/vuejs/core/issues/10874)) ([10d34a5](https://github.com/vuejs/core/commit/10d34a5624775f20437ccad074a97270ef74c3fb)), closes [#10871](https://github.com/vuejs/core/issues/10871)
* **hydration:** handle edge case of style mismatch without style attribute ([f2c1412](https://github.com/vuejs/core/commit/f2c1412e46a8fad3e13403bfa78335c4f704f21c)), closes [#10786](https://github.com/vuejs/core/issues/10786) * **hydration:** handle edge case of style mismatch without style attribute ([f2c1412](https://github.com/vuejs/core/commit/f2c1412e46a8fad3e13403bfa78335c4f704f21c)), closes [#10786](https://github.com/vuejs/core/issues/10786)
@ -417,7 +417,7 @@
* **compiler-sfc:** fix type resolution for symlinked node_modules structure w/ pnpm ([75e866b](https://github.com/vuejs/core/commit/75e866bd4ef368b4e037a4933dbaf188920dc683)), closes [#10121](https://github.com/vuejs/core/issues/10121) * **compiler-sfc:** fix type resolution for symlinked node_modules structure w/ pnpm ([75e866b](https://github.com/vuejs/core/commit/75e866bd4ef368b4e037a4933dbaf188920dc683)), closes [#10121](https://github.com/vuejs/core/issues/10121)
* correct url for production error reference links ([c3087ff](https://github.com/vuejs/core/commit/c3087ff2cce7d96c60a870f8233441311ab4dfb4)) * correct url for production error reference links ([c3087ff](https://github.com/vuejs/core/commit/c3087ff2cce7d96c60a870f8233441311ab4dfb4))
* **hydration:** fix incorect mismatch warning for option with non-string value and inner text ([d16a213](https://github.com/vuejs/core/commit/d16a2138a33b106b9e1499bbb9e1c67790370c97)) * **hydration:** fix incorrect mismatch warning for option with non-string value and inner text ([d16a213](https://github.com/vuejs/core/commit/d16a2138a33b106b9e1499bbb9e1c67790370c97))
* **reactivity:** re-fix [#10114](https://github.com/vuejs/core/issues/10114) ([#10123](https://github.com/vuejs/core/issues/10123)) ([c2b274a](https://github.com/vuejs/core/commit/c2b274a887f61deb7e0185d1bef3b77d31e991cc)) * **reactivity:** re-fix [#10114](https://github.com/vuejs/core/issues/10114) ([#10123](https://github.com/vuejs/core/issues/10123)) ([c2b274a](https://github.com/vuejs/core/commit/c2b274a887f61deb7e0185d1bef3b77d31e991cc))
* **runtime-core:** should not warn out-of-render slot fn usage when mounting another app in setup ([#10125](https://github.com/vuejs/core/issues/10125)) ([6fa33e6](https://github.com/vuejs/core/commit/6fa33e67ec42af140a86fbdb86939032c3a1f345)), closes [#10124](https://github.com/vuejs/core/issues/10124) * **runtime-core:** should not warn out-of-render slot fn usage when mounting another app in setup ([#10125](https://github.com/vuejs/core/issues/10125)) ([6fa33e6](https://github.com/vuejs/core/commit/6fa33e67ec42af140a86fbdb86939032c3a1f345)), closes [#10124](https://github.com/vuejs/core/issues/10124)

592
changelogs/CHANGELOG-3.5.md Normal file
View File

@ -0,0 +1,592 @@
## [3.5.17](https://github.com/vuejs/core/compare/v3.5.16...v3.5.17) (2025-06-18)
### Bug Fixes
* **compat:** allow v-model built in modifiers on component ([#12654](https://github.com/vuejs/core/issues/12654)) ([cb14b86](https://github.com/vuejs/core/commit/cb14b860f150c4a83bcd52cd26096b7a5aa3a2bf)), closes [#12652](https://github.com/vuejs/core/issues/12652)
* **compile-sfc:** handle mapped types work with omit and pick ([#12648](https://github.com/vuejs/core/issues/12648)) ([4eb46e4](https://github.com/vuejs/core/commit/4eb46e443f1878199755cb73d481d318a9714392)), closes [#12647](https://github.com/vuejs/core/issues/12647)
* **compiler-core:** do not increase newlines in `InEntity` state ([#13362](https://github.com/vuejs/core/issues/13362)) ([f05a8d6](https://github.com/vuejs/core/commit/f05a8d613bd873b811cfdb9979ccac8382dba322))
* **compiler-core:** ignore whitespace when matching adjacent v-if ([#12321](https://github.com/vuejs/core/issues/12321)) ([10ebcef](https://github.com/vuejs/core/commit/10ebcef8c870dbc042b0ea49b1424b2e8f692145)), closes [#9173](https://github.com/vuejs/core/issues/9173)
* **compiler-core:** prevent comments from blocking static node hoisting ([#13345](https://github.com/vuejs/core/issues/13345)) ([55dad62](https://github.com/vuejs/core/commit/55dad625acd9e9ddd5a933d5e323ecfdec1a612f)), closes [#13344](https://github.com/vuejs/core/issues/13344)
* **compiler-sfc:** improved type resolution for function type aliases ([#13452](https://github.com/vuejs/core/issues/13452)) ([f3479aa](https://github.com/vuejs/core/commit/f3479aac9625f4459e650d1c0a70e73863147903)), closes [#13444](https://github.com/vuejs/core/issues/13444)
* **custom-element:** ensure configureApp is applied to async component ([#12607](https://github.com/vuejs/core/issues/12607)) ([5ba1afb](https://github.com/vuejs/core/commit/5ba1afba09c3ea56c1c17484f5d8aeae210ce52a)), closes [#12448](https://github.com/vuejs/core/issues/12448)
* **custom-element:** prevent injecting child styles if shadowRoot is false ([#12769](https://github.com/vuejs/core/issues/12769)) ([73055d8](https://github.com/vuejs/core/commit/73055d8d9578d485e3fe846726b50666e1aa56f5)), closes [#12630](https://github.com/vuejs/core/issues/12630)
* **reactivity:** add `__v_skip` flag to `Dep` to prevent reactive conversion ([#12804](https://github.com/vuejs/core/issues/12804)) ([e8d8f5f](https://github.com/vuejs/core/commit/e8d8f5f604e821acc46b4200d5b06979c05af1c2)), closes [#12803](https://github.com/vuejs/core/issues/12803)
* **runtime-core:** unset old ref during patching when new ref is absent ([#12900](https://github.com/vuejs/core/issues/12900)) ([47ddf98](https://github.com/vuejs/core/commit/47ddf986021dff8de68b0da72787e53a6c19de4c)), closes [#12898](https://github.com/vuejs/core/issues/12898)
* **slots:** make cache indexes marker non-enumerable ([#13469](https://github.com/vuejs/core/issues/13469)) ([919c447](https://github.com/vuejs/core/commit/919c44744bba1f0c661c87d2059c3b429611aa7e)), closes [#13468](https://github.com/vuejs/core/issues/13468)
* **ssr:** handle initial selected state for select with v-model + v-for/v-if option ([#13487](https://github.com/vuejs/core/issues/13487)) ([1552095](https://github.com/vuejs/core/commit/15520954f9f1c7f834175938a50dba5d4be0e6c4)), closes [#13486](https://github.com/vuejs/core/issues/13486)
* **types:** typo of `vOnce` and `vSlot` ([#13343](https://github.com/vuejs/core/issues/13343)) ([762fae4](https://github.com/vuejs/core/commit/762fae4b57ad60602e5c84465a3bff562785b314))
## [3.5.16](https://github.com/vuejs/core/compare/v3.5.15...v3.5.16) (2025-05-29)
### Reverts
* Revert "fix(compiler-sfc): add scoping tag to trailing universal selector" (#13406) ([19f23b1](https://github.com/vuejs/core/commit/19f23b180bb679e38db95d6a10a420abeedc8e1c)), closes [#13406](https://github.com/vuejs/core/issues/13406)
* Revert "fix(compiler-sfc): add error handling for defineModel() without variable" (#13390) ([42f879f](https://github.com/vuejs/core/commit/42f879fcab48e0e1011967a771b4ad9e8838d760)), closes [#13390](https://github.com/vuejs/core/issues/13390)
## [3.5.15](https://github.com/vuejs/core/compare/v3.5.14...v3.5.15) (2025-05-26)
### Bug Fixes
* **compat:** ensure false value on input retains value attribute ([#13216](https://github.com/vuejs/core/issues/13216)) ([1a66474](https://github.com/vuejs/core/commit/1a664749d4d65a345589a6d78106ede7574cb2e1)), closes [#13205](https://github.com/vuejs/core/issues/13205)
* **compat:** should not warn COMPILER_V_BIND_OBJECT_ORDER when using v-bind together with v-for ([#12993](https://github.com/vuejs/core/issues/12993)) ([93949e6](https://github.com/vuejs/core/commit/93949e6587ee019bccd5b8b9d76f0e1ed6ea16fc)), closes [#12992](https://github.com/vuejs/core/issues/12992)
* **compile-sfc:** handle inline template source map in prod build ([#12701](https://github.com/vuejs/core/issues/12701)) ([89edc6c](https://github.com/vuejs/core/commit/89edc6cdcbd34ea6394927ecbfaa61dc4f871de7)), closes [#12682](https://github.com/vuejs/core/issues/12682) [vitejs/vite-plugin-vue#500](https://github.com/vitejs/vite-plugin-vue/issues/500)
* **compiler-core:** ensure mapping is added only if node source is available ([#13285](https://github.com/vuejs/core/issues/13285)) ([d37a2ac](https://github.com/vuejs/core/commit/d37a2ac59d904ac0e3257ba552b6c04920a363f0)), closes [#13261](https://github.com/vuejs/core/issues/13261) [vitejs/vite-plugin-vue#368](https://github.com/vitejs/vite-plugin-vue/issues/368)
* **compiler-dom:** improve HTML nesting validation to allow any child element within template tag ([#13320](https://github.com/vuejs/core/issues/13320)) ([163b365](https://github.com/vuejs/core/commit/163b3651d174321911648a164052effa9249a2aa)), closes [#13318](https://github.com/vuejs/core/issues/13318)
* **compiler-sfc:** add error handling for defineModel() without variable assignment ([#13352](https://github.com/vuejs/core/issues/13352)) ([00734af](https://github.com/vuejs/core/commit/00734afef5f7bddbdaee52aa5359a6ef989f32d3)), closes [#13280](https://github.com/vuejs/core/issues/13280)
* **compiler-sfc:** add scoping tag to trailing universal selector ([#12918](https://github.com/vuejs/core/issues/12918)) ([949df80](https://github.com/vuejs/core/commit/949df808809fd7cccf7718797beab0654aa68302)), closes [#12906](https://github.com/vuejs/core/issues/12906)
* **compiler-sfc:** improve type inference for TSTypeAliasDeclaration with better runtime type detection ([#13245](https://github.com/vuejs/core/issues/13245)) ([cf5a5e0](https://github.com/vuejs/core/commit/cf5a5e0edf0efcab25c27aa2d13eba91f7372d39)), closes [#13240](https://github.com/vuejs/core/issues/13240)
* **compiler-sfc:** simulate `allowArbitraryExtensions` on resolving type ([#13301](https://github.com/vuejs/core/issues/13301)) ([f7ce5ae](https://github.com/vuejs/core/commit/f7ce5ae666129339c006b339437c2dff6bceffe0)), closes [#13295](https://github.com/vuejs/core/issues/13295)
* **custom-element:** allow injecting values from app context in nested elements ([#13219](https://github.com/vuejs/core/issues/13219)) ([b991075](https://github.com/vuejs/core/commit/b9910755a50c7d6c52b28c3aef20cf97810295c9)), closes [#13212](https://github.com/vuejs/core/issues/13212)
* **custom-element:** ensure proper remount and prevent redundant slot parsing with shadowRoot false ([#13201](https://github.com/vuejs/core/issues/13201)) ([1d41d4d](https://github.com/vuejs/core/commit/1d41d4de7f64a37160c8171d0137fd8d35c346c9)), closes [#13199](https://github.com/vuejs/core/issues/13199)
* **custom-element:** preserve appContext during update ([#12455](https://github.com/vuejs/core/issues/12455)) ([013749e](https://github.com/vuejs/core/commit/013749e75ef3b51762a86da379ea4ba4501b54ae)), closes [#12453](https://github.com/vuejs/core/issues/12453)
* **custom-element:** properly resolve props for sync component defs ([#12855](https://github.com/vuejs/core/issues/12855)) ([a683c80](https://github.com/vuejs/core/commit/a683c80cf44ecc482f8ac9c76bf2381443c1b0bb)), closes [#12854](https://github.com/vuejs/core/issues/12854)
* **hydration:** handle transition appear hydration edge case ([#13339](https://github.com/vuejs/core/issues/13339)) ([35aeae7](https://github.com/vuejs/core/commit/35aeae7fa3168adcf9ed95fd35495d17c8b93eeb)), closes [#13335](https://github.com/vuejs/core/issues/13335)
* **hydration:** skip lazy hydration for patched components ([#13283](https://github.com/vuejs/core/issues/13283)) ([80055fd](https://github.com/vuejs/core/commit/80055fddfb3ca1e2a44f19c7f0ffaeba00de5140)), closes [#13255](https://github.com/vuejs/core/issues/13255)
* **suspense:** handle edge case in patching list nodes within Suspense ([#13306](https://github.com/vuejs/core/issues/13306)) ([772b008](https://github.com/vuejs/core/commit/772b0087cb7be151c514a1d30365fb0f61a652ba)), closes [#13305](https://github.com/vuejs/core/issues/13305)
* **teleport:** handle deferred teleport updates before and after mount ([#13350](https://github.com/vuejs/core/issues/13350)) ([d15dce3](https://github.com/vuejs/core/commit/d15dce3142474f2ef9fffed38383acdadcb26c4c)), closes [#13349](https://github.com/vuejs/core/issues/13349)
* **types:** avoid merging component instance into `$props` in `ComponentInstance` ([#12870](https://github.com/vuejs/core/issues/12870)) ([f44feed](https://github.com/vuejs/core/commit/f44feed6fa461a9c4c724e9631c19e9e214c0a20)), closes [#12751](https://github.com/vuejs/core/issues/12751)
* **types:** exclude `undefined` from inferred prop types with default values ([#13007](https://github.com/vuejs/core/issues/13007)) ([5179d32](https://github.com/vuejs/core/commit/5179d328d950015e7fb2a74fe1a8518fd8d2c94e)), closes [#13006](https://github.com/vuejs/core/issues/13006)
* **watch:** update `oldValue` before running `cb` to prevent stale value ([#12296](https://github.com/vuejs/core/issues/12296)) ([c69c4bb](https://github.com/vuejs/core/commit/c69c4bb59c114f2b5e03733b55ef9ace3087b5c3)), closes [#12294](https://github.com/vuejs/core/issues/12294)
## [3.5.14](https://github.com/vuejs/core/compare/v3.5.13...v3.5.14) (2025-05-15)
### Bug Fixes
* **compat:** correct deprecation message for v-bind.sync usage ([#13137](https://github.com/vuejs/core/issues/13137)) ([466b30f](https://github.com/vuejs/core/commit/466b30f4049ec89fb282624ec17d1a93472ab93f)), closes [#13133](https://github.com/vuejs/core/issues/13133)
* **compiler-core:** remove slot cache from parent renderCache during unmounting ([#13215](https://github.com/vuejs/core/issues/13215)) ([5d166f3](https://github.com/vuejs/core/commit/5d166f3796a03a497435fc079c6a83a4e9c6cf52))
* **compiler-sfc:** fix scope handling for props destructure in function parameters and catch clauses ([8e34357](https://github.com/vuejs/core/commit/8e3435779a667de485cf9efd78667d0ca14c5f84)), closes [#12790](https://github.com/vuejs/core/issues/12790)
* **compiler-sfc:** treat the return value of `useTemplateRef` as a definite ref ([#13197](https://github.com/vuejs/core/issues/13197)) ([8ae1122](https://github.com/vuejs/core/commit/8ae11226e8ee938615e17c7b81dc38ae3f7cefb9))
* **compiler:** fix spelling error in domTagConfig ([#13043](https://github.com/vuejs/core/issues/13043)) ([388295b](https://github.com/vuejs/core/commit/388295b27f3cc69eba25d325bbe60a36a3df831a))
* **customFormatter:** properly accessing ref value during debugger ([#12948](https://github.com/vuejs/core/issues/12948)) ([fdbd026](https://github.com/vuejs/core/commit/fdbd02658301dd794fe0c84f0018d080a07fca9f))
* **hmr/teleport:** adjust static children traversal for HMR in dev mode ([#12819](https://github.com/vuejs/core/issues/12819)) ([5e37dd0](https://github.com/vuejs/core/commit/5e37dd009562bcd8080a200c32abde2d6e4f0305)), closes [#12816](https://github.com/vuejs/core/issues/12816)
* **hmr:** avoid hydration for hmr root reload ([#12450](https://github.com/vuejs/core/issues/12450)) ([1f98a9c](https://github.com/vuejs/core/commit/1f98a9c493d01c21befa90107f0593bc92a58932)), closes [vitejs/vite-plugin-vue#146](https://github.com/vitejs/vite-plugin-vue/issues/146) [vitejs/vite-plugin-vue#477](https://github.com/vitejs/vite-plugin-vue/issues/477)
* **hmr:** avoid hydration for hmr updating ([#12262](https://github.com/vuejs/core/issues/12262)) ([9c4dbbc](https://github.com/vuejs/core/commit/9c4dbbc5185125835ad3e49baba303bd54676111)), closes [#7706](https://github.com/vuejs/core/issues/7706) [#8170](https://github.com/vuejs/core/issues/8170)
* **reactivity:** ensure markRaw objects are not reactive ([#12824](https://github.com/vuejs/core/issues/12824)) ([295b5ec](https://github.com/vuejs/core/commit/295b5ec19b6a52c4a56652cc4d6e93a4ea7c14ed)), closes [#12807](https://github.com/vuejs/core/issues/12807)
* **reactivity:** ensure multiple effectScope on() and off() calls maintains correct active scope ([22dcbf3](https://github.com/vuejs/core/commit/22dcbf3e20eb84f69c8952f6f70d9990136a4a68)), closes [#12631](https://github.com/vuejs/core/issues/12631) [#12632](https://github.com/vuejs/core/issues/12632) [#12641](https://github.com/vuejs/core/issues/12641)
* **reactivity:** should not recompute if computed does not track reactive data ([#12341](https://github.com/vuejs/core/issues/12341)) ([0b23fd2](https://github.com/vuejs/core/commit/0b23fd23833cf085e7e112bf4435cfc9b360d072)), closes [#12337](https://github.com/vuejs/core/issues/12337)
* **runtime-core:** stop tracking deps in setRef during unmount ([#13210](https://github.com/vuejs/core/issues/13210)) ([016c472](https://github.com/vuejs/core/commit/016c472bd2e7604b21c69dee1da8545ce26e4d2f))
* **runtime-core:** update __vnode of static nodes when patching along the optimized path ([#13223](https://github.com/vuejs/core/issues/13223)) ([b3ecee3](https://github.com/vuejs/core/commit/b3ecee3da8ed5c55dea89ce6b4b376b2b722b018))
* **runtime-core:** inherit comment nodes during block patch in production build ([#10748](https://github.com/vuejs/core/issues/10748)) ([6264505](https://github.com/vuejs/core/commit/626450590d81f79117b34d2a73073b1dc8f551bd)), closes [#10747](https://github.com/vuejs/core/issues/10747) [#12650](https://github.com/vuejs/core/issues/12650)
* **runtime-core:** prevent unmounted vnode from being inserted during transition leave ([#12862](https://github.com/vuejs/core/issues/12862)) ([d6a6ec1](https://github.com/vuejs/core/commit/d6a6ec13ce521683bfb2a22932778ef7b51f8600)), closes [#12860](https://github.com/vuejs/core/issues/12860)
* **runtime-core:** respect immutability for readonly reactive arrays in `v-for` ([#13091](https://github.com/vuejs/core/issues/13091)) ([3f27c58](https://github.com/vuejs/core/commit/3f27c58ffbd4309df369bc89493fdc284dc540bb)), closes [#13087](https://github.com/vuejs/core/issues/13087)
* **runtime-dom:** always treat autocorrect as attribute ([#13001](https://github.com/vuejs/core/issues/13001)) ([1499135](https://github.com/vuejs/core/commit/1499135c227236e037bb746beeb777941b0b58ff)), closes [#5705](https://github.com/vuejs/core/issues/5705)
* **slots:** properly warn if slot invoked in setup ([#12195](https://github.com/vuejs/core/issues/12195)) ([9196222](https://github.com/vuejs/core/commit/9196222ae1d63b52b35ac5fbf5e71494587ccf05)), closes [#12194](https://github.com/vuejs/core/issues/12194)
* **ssr:** properly init slots during ssr rendering ([#12441](https://github.com/vuejs/core/issues/12441)) ([2206cd2](https://github.com/vuejs/core/commit/2206cd235a1627c540e795e378b7564a55b47313)), closes [#12438](https://github.com/vuejs/core/issues/12438)
* **transition:** fix KeepAlive with transition out-in mode behavior in production ([#12468](https://github.com/vuejs/core/issues/12468)) ([343c891](https://github.com/vuejs/core/commit/343c89122448719bd6ed6bd9de986dfb2721d6bf)), closes [#12465](https://github.com/vuejs/core/issues/12465)
* **TransitionGroup:** reset prevChildren to prevent memory leak ([#13183](https://github.com/vuejs/core/issues/13183)) ([8b848cb](https://github.com/vuejs/core/commit/8b848cbbd2af337d23e19e202f9ab433f8580855)), closes [#13181](https://github.com/vuejs/core/issues/13181)
* **types:** allow return any for Options API lifecycle hooks ([#5914](https://github.com/vuejs/core/issues/5914)) ([06310e8](https://github.com/vuejs/core/commit/06310e82f5bed62d1b9733dcb18cd8d6edc988de))
* **types:** the directive's modifiers should be optional ([#12605](https://github.com/vuejs/core/issues/12605)) ([10e54dc](https://github.com/vuejs/core/commit/10e54dcc86a7967f3196d96200bcbd1d3d42082f))
* **typos:** fix comments referencing transformElement.ts ([#12551](https://github.com/vuejs/core/issues/12551))[ci-skip] ([11c053a](https://github.com/vuejs/core/commit/11c053a5429ad0d27a0e2c78b6b026ea00ace116))
### Features
* **types:** add type TemplateRef ([#12645](https://github.com/vuejs/core/issues/12645)) ([636a861](https://github.com/vuejs/core/commit/636a8619f06c71dfd79f7f6412fd130c4f84226f))
## [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)
* **reactivity:** 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)
### Bug Fixes
* **compiler-sfc:** do not skip `TSSatisfiesExpression` when transforming props destructure ([#12062](https://github.com/vuejs/core/issues/12062)) ([2328b05](https://github.com/vuejs/core/commit/2328b051f4efa1f1394b7d4e73b7c3f76e430e7c)), closes [#12061](https://github.com/vuejs/core/issues/12061)
* **reactivity:** prevent overwriting `next` property during batch processing ([#12075](https://github.com/vuejs/core/issues/12075)) ([d3f5e6e](https://github.com/vuejs/core/commit/d3f5e6e5319b4ffaa55ca9a2ea3d95d78e76fa58)), closes [#12072](https://github.com/vuejs/core/issues/12072)
* **scheduler:** job ordering when the post queue is flushing ([#12090](https://github.com/vuejs/core/issues/12090)) ([577edca](https://github.com/vuejs/core/commit/577edca8e7795436efd710d1c289ea8ea2642b0e))
* **types:** correctly infer `TypeProps` when it is `any` ([#12073](https://github.com/vuejs/core/issues/12073)) ([57315ab](https://github.com/vuejs/core/commit/57315ab9688c9741a271d1075bbd28cbe5f71e2f)), closes [#12058](https://github.com/vuejs/core/issues/12058)
* **types:** should not intersect `PublicProps` with `Props` ([#12077](https://github.com/vuejs/core/issues/12077)) ([6f85894](https://github.com/vuejs/core/commit/6f8589437635706f825ccec51800effba1d2bf5f))
* **types:** infer the first generic type of `Ref` correctly ([#12094](https://github.com/vuejs/core/issues/12094)) ([c97bb84](https://github.com/vuejs/core/commit/c97bb84d0b0a16b012f886b6498e924415ed63e5))
## [3.5.10](https://github.com/vuejs/core/compare/v3.5.9...v3.5.10) (2024-09-27)
### Bug Fixes
* **custom-element:** properly set kebab-case props on Vue custom elements ([ea3efa0](https://github.com/vuejs/core/commit/ea3efa09e008918c1d9ba7226833a8b1a7a57244)), closes [#12030](https://github.com/vuejs/core/issues/12030) [#12032](https://github.com/vuejs/core/issues/12032)
* **reactivity:** fix nested batch edge case ([93c95dd](https://github.com/vuejs/core/commit/93c95dd4cd416503f43a98a1455f62658d22b0b2))
* **reactivity:** only clear notified flags for computed in first batch iteration ([aa9ef23](https://github.com/vuejs/core/commit/aa9ef2386a0cd39a174e5a887ec2b1a3525034fc)), closes [#12045](https://github.com/vuejs/core/issues/12045)
* **types/ref:** handle nested refs in UnwrapRef ([#12049](https://github.com/vuejs/core/issues/12049)) ([e2c19c2](https://github.com/vuejs/core/commit/e2c19c20cfee9788519a80c0e53e216b78505994)), closes [#12044](https://github.com/vuejs/core/issues/12044)
## [3.5.9](https://github.com/vuejs/core/compare/v3.5.8...v3.5.9) (2024-09-26)
### Bug Fixes
* **reactivity:** fix property dep removal regression ([6001e5c](https://github.com/vuejs/core/commit/6001e5c81a05c894586f9287fbd991677bdd0455)), closes [#12020](https://github.com/vuejs/core/issues/12020) [#12021](https://github.com/vuejs/core/issues/12021)
* **reactivity:** fix recursive sync watcher on computed edge case ([10ff159](https://github.com/vuejs/core/commit/10ff15924053d9bd95ad706f78ce09e288213fcf)), closes [#12033](https://github.com/vuejs/core/issues/12033) [#12037](https://github.com/vuejs/core/issues/12037)
* **runtime-core:** avoid rendering plain object as VNode ([#12038](https://github.com/vuejs/core/issues/12038)) ([cb34b28](https://github.com/vuejs/core/commit/cb34b28a4a9bf868be4785b001c526163eda342e)), closes [#12035](https://github.com/vuejs/core/issues/12035) [vitejs/vite-plugin-vue#353](https://github.com/vitejs/vite-plugin-vue/issues/353)
* **runtime-core:** make useId() always return a string ([a177092](https://github.com/vuejs/core/commit/a177092754642af2f98c33a4feffe8f198c3c950))
* **types:** correct type inference of union event names ([#12022](https://github.com/vuejs/core/issues/12022)) ([4da6881](https://github.com/vuejs/core/commit/4da688141d9e7c15b622c289deaa81b11845b2c7))
* **vue:** properly cache runtime compilation ([#12019](https://github.com/vuejs/core/issues/12019)) ([fa0ba24](https://github.com/vuejs/core/commit/fa0ba24b3ace02d7ecab65e57c2bea89a2550dcb))
## [3.5.8](https://github.com/vuejs/core/compare/v3.5.7...v3.5.8) (2024-09-22)
### Bug Fixes
* **reactivity:** do not remove dep from depsMap when cleaning up deps of computed ([#11995](https://github.com/vuejs/core/issues/11995)) ([0267a58](https://github.com/vuejs/core/commit/0267a588017eee4951ac2a877fe1ccae84cad905))
## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20)
### Bug Fixes
* **compile-core:** fix v-model with newlines edge case ([#11960](https://github.com/vuejs/core/issues/11960)) ([6224288](https://github.com/vuejs/core/commit/62242886d705ece88dbcad45bb78072ecccad0ca)), closes [#8306](https://github.com/vuejs/core/issues/8306)
* **compiler-sfc:** initialize scope with null prototype object ([#11963](https://github.com/vuejs/core/issues/11963)) ([215e154](https://github.com/vuejs/core/commit/215e15407294bf667261360218f975b88c99c2e5))
* **hydration:** avoid observing non-Element node ([#11954](https://github.com/vuejs/core/issues/11954)) ([7257e6a](https://github.com/vuejs/core/commit/7257e6a34200409b3fc347d3bb807e11e2785974)), closes [#11952](https://github.com/vuejs/core/issues/11952)
* **reactivity:** do not remove dep from depsMap when unsubbed by computed ([960706e](https://github.com/vuejs/core/commit/960706eebf73f08ebc9d5dd853a05def05e2c153))
* **reactivity:** fix dev-only memory leak by updating dep.subsHead on sub removal ([5c8b76e](https://github.com/vuejs/core/commit/5c8b76ed6cfbbcee4cbaac0b72beab7291044e4f)), closes [#11956](https://github.com/vuejs/core/issues/11956)
* **reactivity:** fix memory leak from dep instances of garbage collected objects ([235ea47](https://github.com/vuejs/core/commit/235ea4772ed2972914cf142da8b7ac1fb04f7585)), closes [#11979](https://github.com/vuejs/core/issues/11979) [#11971](https://github.com/vuejs/core/issues/11971)
* **reactivity:** fix triggerRef call on ObjectRefImpl returned by toRef ([#11986](https://github.com/vuejs/core/issues/11986)) ([b030c8b](https://github.com/vuejs/core/commit/b030c8bc7327877efb98aa3d9a58eb287a6ff07a)), closes [#11982](https://github.com/vuejs/core/issues/11982)
* **scheduler:** ensure recursive jobs can't be queued twice ([#11955](https://github.com/vuejs/core/issues/11955)) ([d18d6aa](https://github.com/vuejs/core/commit/d18d6aa1b20dc57a8103c51ec4d61e8e53ed936d))
* **ssr:** don't render comments in TransitionGroup ([#11961](https://github.com/vuejs/core/issues/11961)) ([a2f6ede](https://github.com/vuejs/core/commit/a2f6edeb02faedbb673c4bc5c6a59d9a79a37d07)), closes [#11958](https://github.com/vuejs/core/issues/11958)
* **transition:** respect `duration` setting even when it is `0` ([#11967](https://github.com/vuejs/core/issues/11967)) ([f927a4a](https://github.com/vuejs/core/commit/f927a4ae6f7c453f70ba89498ee0c737dc9866fd))
* **types:** correct type inference of all-optional props ([#11644](https://github.com/vuejs/core/issues/11644)) ([9eca65e](https://github.com/vuejs/core/commit/9eca65ee9871d1ac878755afa9a3eb1b02030350)), closes [#11733](https://github.com/vuejs/core/issues/11733) [vuejs/language-tools#4704](https://github.com/vuejs/language-tools/issues/4704)
### Performance Improvements
* **hydration:** avoid observer if element is in viewport ([#11639](https://github.com/vuejs/core/issues/11639)) ([e075dfa](https://github.com/vuejs/core/commit/e075dfad5c7649c6045e3711687ec888e7aa1a39))
## [3.5.6](https://github.com/vuejs/core/compare/v3.5.5...v3.5.6) (2024-09-16)
### Bug Fixes
* **compile-dom:** should be able to stringify mathML ([#11891](https://github.com/vuejs/core/issues/11891)) ([85c138c](https://github.com/vuejs/core/commit/85c138ced108268f7656b568dfd3036a1e0aae34))
* **compiler-sfc:** preserve old behavior when using withDefaults with desutructure ([8492c3c](https://github.com/vuejs/core/commit/8492c3c49a922363d6c77ef192c133a8fbce6514)), closes [#11930](https://github.com/vuejs/core/issues/11930)
* **reactivity:** avoid exponential perf cost and reduce call stack depth for deeply chained computeds ([#11944](https://github.com/vuejs/core/issues/11944)) ([c74bb8c](https://github.com/vuejs/core/commit/c74bb8c2dd9e82aaabb0a2a2b368e900929b513b)), closes [#11928](https://github.com/vuejs/core/issues/11928)
* **reactivity:** rely on dirty check only when computed has deps ([#11931](https://github.com/vuejs/core/issues/11931)) ([aa5dafd](https://github.com/vuejs/core/commit/aa5dafd2b55d42d6a29316a3bc91aea85c676a0b)), closes [#11929](https://github.com/vuejs/core/issues/11929)
* **watch:** `once` option should be ignored by watchEffect ([#11884](https://github.com/vuejs/core/issues/11884)) ([49fa673](https://github.com/vuejs/core/commit/49fa673493d93b77ddba2165ab6545bae84fd1ae))
* **watch:** unwatch should be callable during SSR ([#11925](https://github.com/vuejs/core/issues/11925)) ([2d6adf7](https://github.com/vuejs/core/commit/2d6adf78a047eed091db277ffbd9df0822fb0bdd)), closes [#11924](https://github.com/vuejs/core/issues/11924)
## [3.5.5](https://github.com/vuejs/core/compare/v3.5.4...v3.5.5) (2024-09-13)
### Bug Fixes
* **compiler-core:** fix handling of delimiterOpen in VPre ([#11915](https://github.com/vuejs/core/issues/11915)) ([706d4ac](https://github.com/vuejs/core/commit/706d4ac1d0210b2d9134b3228280187fe02fc971)), closes [#11913](https://github.com/vuejs/core/issues/11913)
* **compiler-dom:** fix stringify static edge for partially eligible chunks in cached parent ([1d99d61](https://github.com/vuejs/core/commit/1d99d61c1bd77f9ea6743f6214a82add8346a121)), closes [#11879](https://github.com/vuejs/core/issues/11879) [#11890](https://github.com/vuejs/core/issues/11890)
* **compiler-dom:** should ignore leading newline in <textarea> per spec ([3c4bf76](https://github.com/vuejs/core/commit/3c4bf7627649ec1e3220f8c4e4163c20d2afb367))
* **compiler-sfc:** nested css supports atrule and comment ([#11899](https://github.com/vuejs/core/issues/11899)) ([0e7bc71](https://github.com/vuejs/core/commit/0e7bc717e6640644f062957ec5031506f0dab215)), closes [#11896](https://github.com/vuejs/core/issues/11896)
* **custom-element:** handle nested customElement mount w/ shadowRoot false ([#11861](https://github.com/vuejs/core/issues/11861)) ([f2d8019](https://github.com/vuejs/core/commit/f2d801918841e7673ff3f048d0d895592a2f7e23)), closes [#11851](https://github.com/vuejs/core/issues/11851) [#11871](https://github.com/vuejs/core/issues/11871)
* **hmr:** reload async child wrapped in Suspense + KeepAlive ([#11907](https://github.com/vuejs/core/issues/11907)) ([10a2c60](https://github.com/vuejs/core/commit/10a2c6053bd30d160d0214bb3566f540187e6874)), closes [#11868](https://github.com/vuejs/core/issues/11868)
* **hydration:** fix mismatch of leading newline in `<textarea>` and `<pre>` ([a5f3c2e](https://github.com/vuejs/core/commit/a5f3c2eb4d2e7fae93ff93ce865b269f01cc825e)), closes [#11873](https://github.com/vuejs/core/issues/11873) [#11874](https://github.com/vuejs/core/issues/11874)
* **reactivity:** properly clean up deps, fix memory leak ([8ea5d6d](https://github.com/vuejs/core/commit/8ea5d6d6981ab7febda0be43c3c92b18869c3a2a)), closes [#11901](https://github.com/vuejs/core/issues/11901)
* **runtime-core:** properly update async component nested in KeepAlive ([#11917](https://github.com/vuejs/core/issues/11917)) ([7fe6c79](https://github.com/vuejs/core/commit/7fe6c795a1fc7ddcea5ad91a56141561192373ac)), closes [#11916](https://github.com/vuejs/core/issues/11916)
* **TransitionGroup:** not warn unkeyed text children with whitespece preserve ([#11888](https://github.com/vuejs/core/issues/11888)) ([7571f20](https://github.com/vuejs/core/commit/7571f20bc3d1854377a146f41d211e05bb68cd47)), closes [#11885](https://github.com/vuejs/core/issues/11885)
## [3.5.4](https://github.com/vuejs/core/compare/v3.5.3...v3.5.4) (2024-09-10)
### Bug Fixes
* **compiler-sfc:** correct scoped injection for nesting selector ([#11854](https://github.com/vuejs/core/issues/11854)) ([b1de75e](https://github.com/vuejs/core/commit/b1de75ed04626b6423085dfde91fb0cb481a25e8)), closes [#10567](https://github.com/vuejs/core/issues/10567)
* **reactivity:** fix markRaw error on already marked object ([#11864](https://github.com/vuejs/core/issues/11864)) ([67d6596](https://github.com/vuejs/core/commit/67d6596d40b1807b9cd8eb0d9282932ea77be3c0)), closes [#11862](https://github.com/vuejs/core/issues/11862)
* Revert "fix: Revert "fix(reactivity): self-referencing computed should refresh"" ([e596378](https://github.com/vuejs/core/commit/e596378e0be728dad7d60938449f3fa557ca2ec9))
* **runtime-core:** handle shallow reactive arrays in renderList correctly ([#11870](https://github.com/vuejs/core/issues/11870)) ([ced59ab](https://github.com/vuejs/core/commit/ced59ab8f2f2e89c13119bab3a0c25a1a1f1c3d6)), closes [#11869](https://github.com/vuejs/core/issues/11869)
* **types:** correctly infer `TypeEmits` with both tuple and function syntax ([#11840](https://github.com/vuejs/core/issues/11840)) ([dad6738](https://github.com/vuejs/core/commit/dad673809929c084dcb8e42640eb7daa675d4ea4)), closes [#11836](https://github.com/vuejs/core/issues/11836)
### Performance Improvements
* **reactivity:** trigger deps directly instead of storing in an array first ([#11695](https://github.com/vuejs/core/issues/11695)) ([f80d447](https://github.com/vuejs/core/commit/f80d447c17662556e9e3f99f6d199967f4c8cf3d))
## [3.5.3](https://github.com/vuejs/core/compare/v3.5.2...v3.5.3) (2024-09-06)
### Bug Fixes
* **hydration:** check __asyncHydrate presence for vue3-lazy-hydration compat ([#11825](https://github.com/vuejs/core/issues/11825)) ([8e6c337](https://github.com/vuejs/core/commit/8e6c3378676be05cea7f53664442acdfb86784f9)), closes [#11793](https://github.com/vuejs/core/issues/11793)
* Revert "fix(reactivity): self-referencing computed should refresh" ([35c760f](https://github.com/vuejs/core/commit/35c760f82f749f7c6e3f9bfead8221ce498e892f))
* **ssr:** respect app.config.warnHandler during ssr ([bf3d9a2](https://github.com/vuejs/core/commit/bf3d9a2af41659a743706306fc798b3d215df5af)), closes [#11830](https://github.com/vuejs/core/issues/11830)
* **Transition:** handle KeepAlive child unmount in Transition out-in mode ([#11833](https://github.com/vuejs/core/issues/11833)) ([6b7901d](https://github.com/vuejs/core/commit/6b7901d28ed3a6a9242c666cc1b8e3c0b0b0fe62)), closes [#11775](https://github.com/vuejs/core/issues/11775)
* **useId:** make generated IDs selector compatible ([babfb4c](https://github.com/vuejs/core/commit/babfb4cbcbf98601d76c1d7653eae8d250ce2710)), closes [#11828](https://github.com/vuejs/core/issues/11828)
## [3.5.2](https://github.com/vuejs/core/compare/v3.5.1...v3.5.2) (2024-09-05)
### Bug Fixes
* **reactivity:** make toRaw work on proxies created by proxyRef ([46c3ab1](https://github.com/vuejs/core/commit/46c3ab1d714024894fa1d33e495d5d35c7817d4d))
* **reactivity:** pass oldValue to computed getter ([#11813](https://github.com/vuejs/core/issues/11813)) ([98864a7](https://github.com/vuejs/core/commit/98864a7ef5c8080c407166c8221488a4eacbbc81)), closes [#11812](https://github.com/vuejs/core/issues/11812)
* **reactivity:** prevent endless recursion in computed getters ([#11797](https://github.com/vuejs/core/issues/11797)) ([716275d](https://github.com/vuejs/core/commit/716275d1b1d2383d8ef0306fcd94558d4d9170f2))
* **reactivity:** self-referencing computed should refresh ([e84c4a6](https://github.com/vuejs/core/commit/e84c4a608e9dc96fb2a4a29d538bcc64f26103a2)), closes [/github.com/vuejs/core/pull/11797#issuecomment-2330738633](https://github.com//github.com/vuejs/core/pull/11797/issues/issuecomment-2330738633)
* **scheduler:** prevent duplicate jobs being queued ([#11826](https://github.com/vuejs/core/issues/11826)) ([df56cc5](https://github.com/vuejs/core/commit/df56cc528793b1d6131a1e64095dd5cb95c56bee)), closes [#11712](https://github.com/vuejs/core/issues/11712) [#11807](https://github.com/vuejs/core/issues/11807)
* **suspense:** avoid updating anchor if activeBranch has not been rendered to the actual container ([#11818](https://github.com/vuejs/core/issues/11818)) ([3c0d531](https://github.com/vuejs/core/commit/3c0d531fa7fe762bfe46fbe63f318adc95221795)), closes [#11806](https://github.com/vuejs/core/issues/11806)
* **Transition:** handle KeepAlive child unmount in Transition out-in mode ([#11778](https://github.com/vuejs/core/issues/11778)) ([3116553](https://github.com/vuejs/core/commit/311655352931863dfcf520b8cf29cebc5b7e1e00)), closes [#11775](https://github.com/vuejs/core/issues/11775)
* **types:** add HTMLDialogElement missing close event ([#11811](https://github.com/vuejs/core/issues/11811)) ([3634f7a](https://github.com/vuejs/core/commit/3634f7a4c1649ad2e7e969eb4512512868c61d01))
* **types:** added name attribute support to details tag ([#11823](https://github.com/vuejs/core/issues/11823)) ([c74176e](https://github.com/vuejs/core/commit/c74176ec7b4d1d34159ce21d600c04b157ac5549)), closes [#11821](https://github.com/vuejs/core/issues/11821)
* **types:** fix defineComponent props inference when setup() has explicit annotation ([fca20a3](https://github.com/vuejs/core/commit/fca20a39aa4a6f98c8f972bd435ebb7dc535648a)), closes [#11803](https://github.com/vuejs/core/issues/11803)
* **useTemplateRef:** properly fix readonly warning in dev and ensure prod behavior consistency ([9b7797d](https://github.com/vuejs/core/commit/9b7797d0d1fc773e979e042673d5b9b3151c40fc)), closes [#11808](https://github.com/vuejs/core/issues/11808) [#11816](https://github.com/vuejs/core/issues/11816) [#11810](https://github.com/vuejs/core/issues/11810)
### Features
* **compiler-core:** parse modifiers as expression to provide location data ([#11819](https://github.com/vuejs/core/issues/11819)) ([3f13203](https://github.com/vuejs/core/commit/3f13203564164eeb2945bdc0b9ef755c37477d75))
## [3.5.1](https://github.com/vuejs/core/compare/v3.5.0...v3.5.1) (2024-09-04)
### Bug Fixes
* **build:** improve built-in components treeshakability ([4eee630](https://github.com/vuejs/core/commit/4eee630b3122a10d0baf9b91358cfffa92d6fd81))
* **reactivity:** handle non-array arguments in reactive `concat` method ([#11794](https://github.com/vuejs/core/issues/11794)) ([475977a](https://github.com/vuejs/core/commit/475977a6f76b77392610e0a3ec2b0e076d1e1d59)), closes [#11792](https://github.com/vuejs/core/issues/11792)
* **Transition:** avoid applying transition hooks on comment vnode ([#11788](https://github.com/vuejs/core/issues/11788)) ([51912f8](https://github.com/vuejs/core/commit/51912f8a02e35f172f6d30ed7a2f3a92c1407cf9)), closes [#11782](https://github.com/vuejs/core/issues/11782)
* **types:** avoid using intersection type in `Readonly<...>` to fix JSDoc emit ([#11799](https://github.com/vuejs/core/issues/11799)) ([7518bc1](https://github.com/vuejs/core/commit/7518bc19dc73ba46dcf1eef6e23f9e6e75552675))
* **useTemplateRef:** fix readonly warning when useTemplateRef has same variable name as template ref ([bc63df0](https://github.com/vuejs/core/commit/bc63df01992fdbf0b6749ad234153725697ed896)), closes [#11795](https://github.com/vuejs/core/issues/11795) [#11802](https://github.com/vuejs/core/issues/11802) [#11804](https://github.com/vuejs/core/issues/11804)
# [3.5.0](https://github.com/vuejs/core/compare/v3.5.0-rc.1...v3.5.0) (2024-09-03)
## Aggregated Features List for 3.5 (alpha to stable)
### Reactivity
- **reactivity**: Refactor reactivity system to use version counting and doubly-linked list tracking ([#10397](https://github.com/vuejs/core/pull/10397)) ([05eb4e0](https://github.com/vuejs/core/commit/05eb4e0fefd585125dd60b7f8fe9c36928d921aa))
- **reactivity**: Optimize array tracking ([#9511](https://github.com/vuejs/core/pull/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e))
- **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
- **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
- **reactivity:** add `failSilently` argument for `onScopeDispose` ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
- **reactivity/watch:** base `watch`, `getCurrentWatcher`, and `onWatcherCleanup` ([#9927](https://github.com/vuejs/core/issues/9927)) ([205e5b5](https://github.com/vuejs/core/commit/205e5b5e277243c3af2c937d9bd46cf671296b72))
- **reactivity/watch:** add pause/resume for ReactiveEffect, EffectScope, and WatchHandle ([#9651](https://github.com/vuejs/core/issues/9651)) ([267093c](https://github.com/vuejs/core/commit/267093c31490050bfcf3ff2b30a2aefee2dad582))
- **watch:** support passing number to `deep` option to control the watch depth ([#9572](https://github.com/vuejs/core/issues/9572)) ([22f7d96](https://github.com/vuejs/core/commit/22f7d96757956ebe0baafe52256aa327908cc51c))
- **types:** export `MultiWatchSources` type ([#9563](https://github.com/vuejs/core/issues/9563)) ([998dca5](https://github.com/vuejs/core/commit/998dca59f140420280803233f41707580688562c))
- **types:** allow computed getter and setter types to be unrelated ([#11472](https://github.com/vuejs/core/issues/11472)) ([a01675e](https://github.com/vuejs/core/commit/a01675ef8f99b5acd6832c53051f4415b18609f2)), closes [#7271](https://github.com/vuejs/core/issues/7271)
### SSR
- **runtime-core:** `useId()` and `app.config.idPrefix` ([#11404](https://github.com/vuejs/core/issues/11404)) ([73ef156](https://github.com/vuejs/core/commit/73ef1561f6905d69f968c094d0180c61824f1247))
- **hydration:** lazy hydration strategies for async components ([#11458](https://github.com/vuejs/core/issues/11458)) ([d14a11c](https://github.com/vuejs/core/commit/d14a11c1cdcee88452f17ce97758743c863958f4))
- **hydration:** support suppressing hydration mismatch via data-allow-mismatch ([94fb2b8](https://github.com/vuejs/core/commit/94fb2b8106a66bcca1a3f922a246a29fdd1274b1))
### Custom Element
- **custom-element:** `useHost()` helper ([775103a](https://github.com/vuejs/core/commit/775103af37df69d34c79f12c4c1776c47d07f0a0))
- **custom-element:** `useShadowRoot()` helper ([5a1a89b](https://github.com/vuejs/core/commit/5a1a89bd6178cc2f84ba91da7d72aee4c6ec1282)), closes [#6113](https://github.com/vuejs/core/issues/6113) [#8195](https://github.com/vuejs/core/issues/8195)
- **custom-element:** expose `this.$host` in Options API ([1ef8f46](https://github.com/vuejs/core/commit/1ef8f46af0cfdec2fed66376772409e0aa25ad50))
- **custom-element:** inject child components styles to custom element shadow root ([#11517](https://github.com/vuejs/core/issues/11517)) ([56c76a8](https://github.com/vuejs/core/commit/56c76a8b05c45f782ed3a16ec77c6292b71a17f1)), closes [#4662](https://github.com/vuejs/core/issues/4662) [#7941](https://github.com/vuejs/core/issues/7941) [#7942](https://github.com/vuejs/core/issues/7942)
- **custom-element:** support configurable app instance in defineCustomElement ([6758c3c](https://github.com/vuejs/core/commit/6758c3cd0427f97394d95168c655dae3b7fa62cd)), closes [#4356](https://github.com/vuejs/core/issues/4356) [#4635](https://github.com/vuejs/core/issues/4635)
- **custom-element:** support css `:host` selector by applying css vars on host element ([#8830](https://github.com/vuejs/core/issues/8830)) ([03a9ea2](https://github.com/vuejs/core/commit/03a9ea2b88df0842a820e09f7445c4b9189e3fcb)), closes [#8826](https://github.com/vuejs/core/issues/8826)
- **custom-element:** support emit with options ([e181bff](https://github.com/vuejs/core/commit/e181bff6dc39d5cef92000c10291243c7d6e4d08)), closes [#7605](https://github.com/vuejs/core/issues/7605)
- **custom-element:** support expose on customElement ([#6256](https://github.com/vuejs/core/issues/6256)) ([af838c1](https://github.com/vuejs/core/commit/af838c1b5ec23552e52e64ffa7db0eb0246c3624)), closes [#5540](https://github.com/vuejs/core/issues/5540)
- **custom-element:** support `nonce` option for injected style tags ([bb4a02a](https://github.com/vuejs/core/commit/bb4a02a70c30e739a3c705b3d96d09258d7d7ded)), closes [#6530](https://github.com/vuejs/core/issues/6530)
- **custom-element:** support passing custom-element-specific options via 2nd argument of defineCustomElement ([60a88a2](https://github.com/vuejs/core/commit/60a88a2b129714186cf6ba66f30f31d733d0311e))
- **custom-element:** support `shadowRoot: false` in `defineCustomElement()` ([37d2ce5](https://github.com/vuejs/core/commit/37d2ce5d8e0fac4a00064f02b05f91f69b2d5d5e)), closes [#4314](https://github.com/vuejs/core/issues/4314) [#4404](https://github.com/vuejs/core/issues/4404)
### Teleport
- **teleport:** support deferred Teleport ([#11387](https://github.com/vuejs/core/issues/11387)) ([59a3e88](https://github.com/vuejs/core/commit/59a3e88903b10ac2278170a44d5a03f24fef23ef)), closes [#2015](https://github.com/vuejs/core/issues/2015) [#11386](https://github.com/vuejs/core/issues/11386)
- **teleport/transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
### Misc
- **runtime-core:** `useTemplateRef()` ([3ba70e4](https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df))
- **runtime-core:** add `app.onUnmount()` for registering cleanup functions ([#4619](https://github.com/vuejs/core/issues/4619)) ([582a3a3](https://github.com/vuejs/core/commit/582a3a382b1adda565bac576b913a88d9e8d7a9e)), closes [#4516](https://github.com/vuejs/core/issues/4516)
- **runtime-core:** add `app.config.throwUnhandledErrorInProduction` ([f476b7f](https://github.com/vuejs/core/commit/f476b7f030f2dd427ca655fcea36f4933a4b4da0)), closes [#7876](https://github.com/vuejs/core/issues/7876)
- **runtime-dom:** Trusted Types compatibility ([#10844](https://github.com/vuejs/core/issues/10844)) ([6d4eb94](https://github.com/vuejs/core/commit/6d4eb94853ed1b2b1675bdd7d5ba9c75cc6daed5))
- **compiler-core:** support `Symbol` global in template expressions ([#9069](https://github.com/vuejs/core/issues/9069)) ([a501a85](https://github.com/vuejs/core/commit/a501a85a7c910868e01a5c70a2abea4e9d9e87f3))
- **types:** export more emit related types ([#11017](https://github.com/vuejs/core/issues/11017)) ([189573d](https://github.com/vuejs/core/commit/189573dcee2a16bd3ed36ff5589d43f535e5e733))
* **types:** add loading prop to iframe ([#11767](https://github.com/vuejs/core/issues/11767)) ([d86fe0e](https://github.com/vuejs/core/commit/d86fe0ec002901dc359a0e85f3a421b4a8538d68))
### Internals
- **reactivity:** store value cache on CustomRefs impls ([#11539](https://github.com/vuejs/core/issues/11539)) ([e044b6e](https://github.com/vuejs/core/commit/e044b6e737efc9433d1d84590036b82280da6292))
- **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
- **types:** provide internal options for using refs type in language tools ([#11492](https://github.com/vuejs/core/issues/11492)) ([5ffd1a8](https://github.com/vuejs/core/commit/5ffd1a89455807d5069eb2c28eba0379641dca76))
## Bug Fixes
* **compiler-sfc:** fix import usage check for kebab-case same name shorthand binding ([0f7c0e5](https://github.com/vuejs/core/commit/0f7c0e5dc0eedada7a5194db87fd0a7dbd1d3354)), closes [#11745](https://github.com/vuejs/core/issues/11745) [#11754](https://github.com/vuejs/core/issues/11754)
* **cssVars:** correctly escape double quotes in SSR ([#11784](https://github.com/vuejs/core/issues/11784)) ([7b5b6e0](https://github.com/vuejs/core/commit/7b5b6e0275f35748dca6d7eb842f8ab2364c6b9a)), closes [#11779](https://github.com/vuejs/core/issues/11779)
* **deps:** update dependency postcss to ^8.4.44 ([#11774](https://github.com/vuejs/core/issues/11774)) ([cb843e0](https://github.com/vuejs/core/commit/cb843e0be31f9e563ccfc30eca0c06f2a224b505))
* **hydration:** escape css var name to avoid mismatch ([#11739](https://github.com/vuejs/core/issues/11739)) ([ca12e77](https://github.com/vuejs/core/commit/ca12e776bc53aaa31f2df6bb6edc6be1b2f10c37)), closes [#11735](https://github.com/vuejs/core/issues/11735)
* **hydration:** handle text nodes with 0 during hydration ([#11772](https://github.com/vuejs/core/issues/11772)) ([c756da2](https://github.com/vuejs/core/commit/c756da24b2d8635cf52b4c7d3abf5bf938852cc5)), closes [#11771](https://github.com/vuejs/core/issues/11771)
* **reactivity:** correctly handle method calls on user-extended arrays ([#11760](https://github.com/vuejs/core/issues/11760)) ([9817c80](https://github.com/vuejs/core/commit/9817c80187bec6a3344c74d65fac92262de0fcdd)), closes [#11759](https://github.com/vuejs/core/issues/11759)
* **runtime-dom:** avoid unnecessary prop patch for checkbox ([#11657](https://github.com/vuejs/core/issues/11657)) ([c3ce9fe](https://github.com/vuejs/core/commit/c3ce9fe3d8fc27d864ce7148cd36da882cfc21ab)), closes [#11647](https://github.com/vuejs/core/issues/11647)
* **runtime-dom:** prevent unnecessary DOM update from v-model ([#11656](https://github.com/vuejs/core/issues/11656)) ([b1be9bd](https://github.com/vuejs/core/commit/b1be9bd64f2c7c4286fecb25bad5d5edd49efce9)), closes [#11647](https://github.com/vuejs/core/issues/11647)
* **server-renderer:** Fix call to serverPrefetch in server renderer with an async setup ([#10893](https://github.com/vuejs/core/issues/10893)) ([6039e25](https://github.com/vuejs/core/commit/6039e25e04a8c1db5821955f011d57f1615807ab))
* **server-renderer:** render `className` during SSR ([#11722](https://github.com/vuejs/core/issues/11722)) ([52cdb0f](https://github.com/vuejs/core/commit/52cdb0f991dc154ae32a2900874d5dbc4e078565))
* **types/defineModel:** allow getter and setter types to be unrelated ([#11699](https://github.com/vuejs/core/issues/11699)) ([fe07f70](https://github.com/vuejs/core/commit/fe07f7073617df358c2f8cbc3de433359e873c96)), closes [#11697](https://github.com/vuejs/core/issues/11697)
# [3.5.0-rc.1](https://github.com/vuejs/core/compare/v3.5.0-beta.3...v3.5.0-rc.1) (2024-08-29)
### Bug Fixes
* **compiler-sfc:** skip circular tsconfig project reference ([#11680](https://github.com/vuejs/core/issues/11680)) ([9c4c2e5](https://github.com/vuejs/core/commit/9c4c2e51b045218d0c5ca64b4fb58b17d5d580cc)), closes [#11382](https://github.com/vuejs/core/issues/11382)
* **custom-element:** handle keys set on custom elements ([#11655](https://github.com/vuejs/core/issues/11655)) ([f1d1831](https://github.com/vuejs/core/commit/f1d1831f07fe52d5681a5ec9ec310572463abf26)), closes [#11641](https://github.com/vuejs/core/issues/11641)
* **deps:** update dependency monaco-editor to ^0.51.0 ([#11713](https://github.com/vuejs/core/issues/11713)) ([434f8a9](https://github.com/vuejs/core/commit/434f8a97c77f68aeae050e9e4e1f54f63bc4bd26))
* **keep-alive:** reset keep alive flag when the component is removed from include ([#11718](https://github.com/vuejs/core/issues/11718)) ([29c321b](https://github.com/vuejs/core/commit/29c321bfd33f9197244dec3d027077e63b2cdf2f)), closes [#11717](https://github.com/vuejs/core/issues/11717)
* **reactivity:** avoid infinite recursion when mutating ref wrapped in reactive ([313e4bf](https://github.com/vuejs/core/commit/313e4bf55214ac1e334a99c329a3ba5daca4f156)), closes [#11696](https://github.com/vuejs/core/issues/11696)
* **reactivity:** ensure watcher with once: true are properly removed from effect scope ([#11665](https://github.com/vuejs/core/issues/11665)) ([fbc0c42](https://github.com/vuejs/core/commit/fbc0c42bcf6dea5a6ae664223fa19d4375ca39f0))
* **runtime-dom:** setting innerHTML when patching props should go through trusted types ([d875de5](https://github.com/vuejs/core/commit/d875de54e9e03e0768fe550aa4c4886a4baf3bd7))
* **types:** GlobalDirective / GlobalComponents should not be records ([42e8df6](https://github.com/vuejs/core/commit/42e8df62030e7f2c287d9103f045e67b34a63e3b))
# [3.5.0-beta.3](https://github.com/vuejs/core/compare/v3.5.0-beta.2...v3.5.0-beta.3) (2024-08-20)
### Bug Fixes
* **reactivity:** extended methods respect reactive ([#11629](https://github.com/vuejs/core/issues/11629)) ([9de1d10](https://github.com/vuejs/core/commit/9de1d101f98bf6081f41038f6974826f190330a0)), closes [#11628](https://github.com/vuejs/core/issues/11628)
* **runtime-core:** correct type inference for PascalCase emits ([#11579](https://github.com/vuejs/core/issues/11579)) ([d7d0371](https://github.com/vuejs/core/commit/d7d0371e74707ee601020f67de88e091cdae2673)), closes [vuejs/language-tools#4269](https://github.com/vuejs/language-tools/issues/4269)
* **runtime-core:** ensure suspense content inherit scopeId ([#10652](https://github.com/vuejs/core/issues/10652)) ([ac2a410](https://github.com/vuejs/core/commit/ac2a410e46392db63ca4ed2db3c0fa71ebe1e855)), closes [#5148](https://github.com/vuejs/core/issues/5148)
* **runtime-core:** pre jobs without an id should run first ([#7746](https://github.com/vuejs/core/issues/7746)) ([b332f80](https://github.com/vuejs/core/commit/b332f80f0edb018229a23b43b93bb402b6368a3c))
* **ssr:** apply ssr props to the the fallback vnode-based branch in ssr ([#7247](https://github.com/vuejs/core/issues/7247)) ([98b83e8](https://github.com/vuejs/core/commit/98b83e86d16c635547a1e735e5fb675aea2f0f1b)), closes [#6123](https://github.com/vuejs/core/issues/6123)
* **types/custom-element:** `defineCustomElement` with required props ([#11578](https://github.com/vuejs/core/issues/11578)) ([5e0f6d5](https://github.com/vuejs/core/commit/5e0f6d5f8fe7c4eb8f247357c3e2e281726f36db))
* **types:** strip non-prop default values from return type of withDefaults ([#9998](https://github.com/vuejs/core/issues/9998)) ([44973bb](https://github.com/vuejs/core/commit/44973bb3e790db7d8aa7af4eda21c80cac73a8de)), closes [#9899](https://github.com/vuejs/core/issues/9899)
* **watch:** handle errors in computed used as watch source ([#11626](https://github.com/vuejs/core/issues/11626)) ([8bcaad4](https://github.com/vuejs/core/commit/8bcaad4a32cf0f1f89e0259f6a53036620b7fe9f)), closes [#11624](https://github.com/vuejs/core/issues/11624)
### Features
* **reactivity:** base `watch`, `getCurrentWatcher`, and `onWatcherCleanup` ([#9927](https://github.com/vuejs/core/issues/9927)) ([205e5b5](https://github.com/vuejs/core/commit/205e5b5e277243c3af2c937d9bd46cf671296b72))
### Performance Improvements
* **runtime-core:** use `apply` to avoid spreading. ([#5985](https://github.com/vuejs/core/issues/5985)) ([bb6babc](https://github.com/vuejs/core/commit/bb6babca8f206615d4e246457cd54d21bb3bc5a4))
# [3.5.0-beta.2](https://github.com/vuejs/core/compare/v3.5.0-beta.1...v3.5.0-beta.2) (2024-08-15)
### Bug Fixes
* **build:** revert entities to 4.5 to avoid runtime resolution errors ([e9e0815](https://github.com/vuejs/core/commit/e9e08155bf8d00c3327ed7371330eb2ae467e560)), closes [#11603](https://github.com/vuejs/core/issues/11603)
* **compiler-core:** use ast-based check for function expressions when possible ([5861229](https://github.com/vuejs/core/commit/58612294757480974e667652ede5bbcf72b1089d)), closes [#11615](https://github.com/vuejs/core/issues/11615)
* **compiler-sfc:** fix prefixIdentifier default value ([3d6f015](https://github.com/vuejs/core/commit/3d6f01571b3fb61b32da599d0419eff4e3ebb231))
* **compiler-sfc:** handle keyof operator with index object ([#11581](https://github.com/vuejs/core/issues/11581)) ([fe00815](https://github.com/vuejs/core/commit/fe008152c0612ff3ecc7ad88e7e66a06b1b2bc3f))
* **custom-element:** keep instance.isCE for backwards compat ([e19fc27](https://github.com/vuejs/core/commit/e19fc270428b59456fee43224990138c4d6ccb2d))
* **deps:** update dependency postcss to ^8.4.41 ([#11585](https://github.com/vuejs/core/issues/11585)) ([4c4e12a](https://github.com/vuejs/core/commit/4c4e12ae28d67d616924b0601e68adc551959971))
* **keep-alive:** ensure include/exclude regexp work with global flag ([#11595](https://github.com/vuejs/core/issues/11595)) ([3653bc0](https://github.com/vuejs/core/commit/3653bc0f45d6fedf84e29b64ca52584359c383c0))
* **reactivity:** ensure extended method arguments are not lost ([#11574](https://github.com/vuejs/core/issues/11574)) ([4085def](https://github.com/vuejs/core/commit/4085def1bae42d01ee3c22c731cc4a02096464ee)), closes [#11570](https://github.com/vuejs/core/issues/11570)
* **reactivity:** sync watch should be executed correctly ([#11589](https://github.com/vuejs/core/issues/11589)) ([3bda3e8](https://github.com/vuejs/core/commit/3bda3e83fd9e2fbe451a1c79dae82ff6a7467683)), closes [#11577](https://github.com/vuejs/core/issues/11577)
* **types/computed:** ensure type safety for `WritableComputedRef` ([#11608](https://github.com/vuejs/core/issues/11608)) ([5cf5a16](https://github.com/vuejs/core/commit/5cf5a1620d9a97382d386c277265d9dd051fe484))
* **types:** add fallback stub for DOM types when DOM lib is absent ([#11598](https://github.com/vuejs/core/issues/11598)) ([fee6697](https://github.com/vuejs/core/commit/fee669764fbf475adce9e47a7a73b4937ab31ffc))
### Features
* **deprecated:** remove deprecated parseExpressions option ([#11597](https://github.com/vuejs/core/issues/11597)) ([4e7d5db](https://github.com/vuejs/core/commit/4e7d5db4d276a5d4aaf3af7d43cfd28c171db307))
# [3.5.0-beta.1](https://github.com/vuejs/core/compare/v3.4.37...v3.5.0-beta.1) (2024-08-08)
### Bug Fixes
* **custom-element:** delay mounting of custom elements with async parent ([37ccb9b](https://github.com/vuejs/core/commit/37ccb9b9a0e4381f9465e0fc6459609003030da4)), closes [#8127](https://github.com/vuejs/core/issues/8127) [#9341](https://github.com/vuejs/core/issues/9341) [#9351](https://github.com/vuejs/core/issues/9351) [#9351](https://github.com/vuejs/core/issues/9351)
* **custom-element:** delete prop on attribute removal ([506c4c5](https://github.com/vuejs/core/commit/506c4c53fdf9766c2ce9517ad58d501ef6b1b9de)), closes [#11276](https://github.com/vuejs/core/issues/11276)
* **custom-element:** ignore scoped id ([7f2c505](https://github.com/vuejs/core/commit/7f2c505f92026408a8262ba9b5104a465be19446))
* **custom-element:** reflect prop default value on custom element ([63689ed](https://github.com/vuejs/core/commit/63689ed77601d5f9b78540f810612806c3a5de15)), closes [#9006](https://github.com/vuejs/core/issues/9006) [#10537](https://github.com/vuejs/core/issues/10537)
* **custom-element:** support early-set domProps for async custom elements ([a07e7bf](https://github.com/vuejs/core/commit/a07e7bf5536a6b3db70ba9bb1c3f366dac1bf5a0)), closes [#11081](https://github.com/vuejs/core/issues/11081) [#11082](https://github.com/vuejs/core/issues/11082)
* **types/custome-element:** `defineCustomElement` props inference with array emits ([#11384](https://github.com/vuejs/core/issues/11384)) ([e94b01b](https://github.com/vuejs/core/commit/e94b01bd8a1ec740eddc823839ab2627b307c1b0)), closes [#11353](https://github.com/vuejs/core/issues/11353)
* **types:** allow using InjectionKey as valid property key ([321d807](https://github.com/vuejs/core/commit/321d80758c42fccbd39ecbb63f1a4f6632a1580a)), closes [#5089](https://github.com/vuejs/core/issues/5089)
### Features
* **custom-element:** expose this.$host in Options API ([1ef8f46](https://github.com/vuejs/core/commit/1ef8f46af0cfdec2fed66376772409e0aa25ad50))
* **custom-element:** inject child components styles to custom element shadow root ([#11517](https://github.com/vuejs/core/issues/11517)) ([56c76a8](https://github.com/vuejs/core/commit/56c76a8b05c45f782ed3a16ec77c6292b71a17f1)), closes [#4662](https://github.com/vuejs/core/issues/4662) [#7941](https://github.com/vuejs/core/issues/7941) [#7942](https://github.com/vuejs/core/issues/7942)
* **custom-element:** support configurable app instance in defineCustomElement ([6758c3c](https://github.com/vuejs/core/commit/6758c3cd0427f97394d95168c655dae3b7fa62cd)), closes [#4356](https://github.com/vuejs/core/issues/4356) [#4635](https://github.com/vuejs/core/issues/4635)
* **custom-element:** support css `:host` selector by applying css vars on host element ([#8830](https://github.com/vuejs/core/issues/8830)) ([03a9ea2](https://github.com/vuejs/core/commit/03a9ea2b88df0842a820e09f7445c4b9189e3fcb)), closes [#8826](https://github.com/vuejs/core/issues/8826)
* **custom-element:** support emit with options ([e181bff](https://github.com/vuejs/core/commit/e181bff6dc39d5cef92000c10291243c7d6e4d08)), closes [#7605](https://github.com/vuejs/core/issues/7605)
* **custom-element:** support for expose on customElement ([#6256](https://github.com/vuejs/core/issues/6256)) ([af838c1](https://github.com/vuejs/core/commit/af838c1b5ec23552e52e64ffa7db0eb0246c3624)), closes [#5540](https://github.com/vuejs/core/issues/5540)
* **custom-element:** support nonce option for injected style tags ([bb4a02a](https://github.com/vuejs/core/commit/bb4a02a70c30e739a3c705b3d96d09258d7d7ded)), closes [#6530](https://github.com/vuejs/core/issues/6530)
* **custom-element:** support passing custom-element-specific options via 2nd argument of defineCustomElement ([60a88a2](https://github.com/vuejs/core/commit/60a88a2b129714186cf6ba66f30f31d733d0311e))
* **custom-element:** support shadowRoot: false in defineCustomElement() ([37d2ce5](https://github.com/vuejs/core/commit/37d2ce5d8e0fac4a00064f02b05f91f69b2d5d5e)), closes [#4314](https://github.com/vuejs/core/issues/4314) [#4404](https://github.com/vuejs/core/issues/4404)
* **custom-element:** useHost() helper ([775103a](https://github.com/vuejs/core/commit/775103af37df69d34c79f12c4c1776c47d07f0a0))
* **custom-element:** useShadowRoot() helper ([5a1a89b](https://github.com/vuejs/core/commit/5a1a89bd6178cc2f84ba91da7d72aee4c6ec1282)), closes [#6113](https://github.com/vuejs/core/issues/6113) [#8195](https://github.com/vuejs/core/issues/8195)
* **hydration:** allow fine tuning of lazy hydration strategy triggers ([#11530](https://github.com/vuejs/core/issues/11530)) ([261c8b1](https://github.com/vuejs/core/commit/261c8b111d046204bd22392a8b920e3c3d4def48))
* **reactivity/watch:** add pause/resume for ReactiveEffect, EffectScope, and WatchHandle ([#9651](https://github.com/vuejs/core/issues/9651)) ([267093c](https://github.com/vuejs/core/commit/267093c31490050bfcf3ff2b30a2aefee2dad582))
* **reactivity:** store value cache on CustomRefs impls ([#11539](https://github.com/vuejs/core/issues/11539)) ([e044b6e](https://github.com/vuejs/core/commit/e044b6e737efc9433d1d84590036b82280da6292))
* **runtime-dom:** Trusted Types compatibility ([#10844](https://github.com/vuejs/core/issues/10844)) ([6d4eb94](https://github.com/vuejs/core/commit/6d4eb94853ed1b2b1675bdd7d5ba9c75cc6daed5))
* support specifying allowed keys via generic argument in useTemplateRef() ([1fbfa69](https://github.com/vuejs/core/commit/1fbfa6962b48634ff60837084b82dd57f215c109))
* **types:** allow computed getter and setter types to be unrelated ([#11472](https://github.com/vuejs/core/issues/11472)) ([a01675e](https://github.com/vuejs/core/commit/a01675ef8f99b5acd6832c53051f4415b18609f2)), closes [#7271](https://github.com/vuejs/core/issues/7271)
* **types:** export `MultiWatchSources` type ([#9563](https://github.com/vuejs/core/issues/9563)) ([998dca5](https://github.com/vuejs/core/commit/998dca59f140420280803233f41707580688562c))
* **types:** provide internal options for using refs type in language tools ([#11492](https://github.com/vuejs/core/issues/11492)) ([5ffd1a8](https://github.com/vuejs/core/commit/5ffd1a89455807d5069eb2c28eba0379641dca76))
* **watch:** support passing number to `deep` option to control the watch depth ([#9572](https://github.com/vuejs/core/issues/9572)) ([22f7d96](https://github.com/vuejs/core/commit/22f7d96757956ebe0baafe52256aa327908cc51c))
# [3.5.0-alpha.5](https://github.com/vuejs/core/compare/v3.4.35...v3.5.0-alpha.5) (2024-07-31)
### Features
* **hydration:** support suppressing hydration mismatch via data-allow-mismatch ([94fb2b8](https://github.com/vuejs/core/commit/94fb2b8106a66bcca1a3f922a246a29fdd1274b1))
* lazy hydration strategies for async components ([#11458](https://github.com/vuejs/core/issues/11458)) ([d14a11c](https://github.com/vuejs/core/commit/d14a11c1cdcee88452f17ce97758743c863958f4))
# [3.5.0-alpha.4](https://github.com/vuejs/core/compare/v3.4.34...v3.5.0-alpha.4) (2024-07-24)
### Bug Fixes
* **suspense/hydration:** fix hydration timing of async component inside suspense ([1b8e197](https://github.com/vuejs/core/commit/1b8e197a5b65d67a9703b8511786fb81df9aa7cc)), closes [#6638](https://github.com/vuejs/core/issues/6638)
* **useId:** properly mark async boundary for already resolved async component ([cd28172](https://github.com/vuejs/core/commit/cd281725781ada2ab279e919031ae307e146a9d9))
# [3.5.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.33...v3.5.0-alpha.3) (2024-07-19)
### Bug Fixes
* **build:** enable SSR branches in esm-browser builds ([b14cd9a](https://github.com/vuejs/core/commit/b14cd9a68bab082332b0169be075be357be076ca))
* **compiler-core:** change node hoisting to caching per instance ([#11067](https://github.com/vuejs/core/issues/11067)) ([cd0ea0d](https://github.com/vuejs/core/commit/cd0ea0d479a276583fa181d8ecbc97fb0e4a9dce)), closes [#5256](https://github.com/vuejs/core/issues/5256) [#9219](https://github.com/vuejs/core/issues/9219) [#10959](https://github.com/vuejs/core/issues/10959)
* **compiler-sfc:** should properly walk desutructured props when reactive destructure is not enabled ([0fd6193](https://github.com/vuejs/core/commit/0fd6193def2380916eb51a118f37f2d9ec2ace23)), closes [#11325](https://github.com/vuejs/core/issues/11325)
* **types:** respect props with default on instance type when using __typeProps ([96e4738](https://github.com/vuejs/core/commit/96e473833422342c5ca371ae1aeb186dec9a55e3))
### Features
* **runtime-core:** useTemplateRef() ([3ba70e4](https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df))
* **runtime-core:** useId() and app.config.idPrefix ([#11404](https://github.com/vuejs/core/issues/11404)) ([73ef156](https://github.com/vuejs/core/commit/73ef1561f6905d69f968c094d0180c61824f1247))
* **runtime-core:** add app.config.throwUnhandledErrorInProduction ([f476b7f](https://github.com/vuejs/core/commit/f476b7f030f2dd427ca655fcea36f4933a4b4da0)), closes [#7876](https://github.com/vuejs/core/issues/7876)
* **teleport:** support deferred Teleport ([#11387](https://github.com/vuejs/core/issues/11387)) ([59a3e88](https://github.com/vuejs/core/commit/59a3e88903b10ac2278170a44d5a03f24fef23ef)), closes [#2015](https://github.com/vuejs/core/issues/2015) [#11386](https://github.com/vuejs/core/issues/11386)
* **compiler-core:** support `Symbol` global in template expressions ([#9069](https://github.com/vuejs/core/issues/9069)) ([a501a85](https://github.com/vuejs/core/commit/a501a85a7c910868e01a5c70a2abea4e9d9e87f3))
* **types:** export more emit related types ([#11017](https://github.com/vuejs/core/issues/11017)) ([189573d](https://github.com/vuejs/core/commit/189573dcee2a16bd3ed36ff5589d43f535e5e733))
# [3.5.0-alpha.2](https://github.com/vuejs/core/compare/v3.4.26...v3.5.0-alpha.2) (2024-05-04)
### Bug Fixes
* **types:** fix app.component() typing with inline defineComponent ([908f70a](https://github.com/vuejs/core/commit/908f70adc06038d1ea253d96f4024367f4a7545d)), closes [#10843](https://github.com/vuejs/core/issues/10843)
* **types:** fix compat with generated types that rely on CreateComponentPublicInstance ([c146186](https://github.com/vuejs/core/commit/c146186396d0c1a65423b8c9a21251c5a6467336)), closes [#10842](https://github.com/vuejs/core/issues/10842)
* **types:** props in defineOptions type should be optional ([124c4ca](https://github.com/vuejs/core/commit/124c4cac833a28ae9bc8edc576c1d0c7c41f5985)), closes [#10841](https://github.com/vuejs/core/issues/10841)
### Features
* **runtime-core:** add app.onUnmount() for registering cleanup functions ([#4619](https://github.com/vuejs/core/issues/4619)) ([582a3a3](https://github.com/vuejs/core/commit/582a3a382b1adda565bac576b913a88d9e8d7a9e)), closes [#4516](https://github.com/vuejs/core/issues/4516)
# [3.5.0-alpha.1](https://github.com/vuejs/core/compare/v3.4.25...v3.5.0-alpha.1) (2024-04-29)
### Bug Fixes
* **reactivity:** fix call sequence of ontrigger in effect ([#10501](https://github.com/vuejs/core/issues/10501)) ([28841fe](https://github.com/vuejs/core/commit/28841fee43a45c37905c2c1ed9ace23067539045))
### Features
* **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
* **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
* **reactivity:** add failSilently argument for onScopeDispose ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
* **transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
* **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
### Performance Improvements
* **reactivity:** optimize array tracking ([#9511](https://github.com/vuejs/core/issues/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e)), closes [#4318](https://github.com/vuejs/core/issues/4318)

View File

@ -1,7 +1,7 @@
{ {
"private": true, "private": true,
"version": "3.5.14", "version": "3.6.0-alpha.1",
"packageManager": "pnpm@10.9.0", "packageManager": "pnpm@10.12.4",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "node scripts/dev.js", "dev": "node scripts/dev.js",
@ -65,28 +65,28 @@
"@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.3", "@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-json": "^6.1.0", "@rollup/plugin-json": "^6.1.0",
"@rollup/plugin-node-resolve": "^16.0.1", "@rollup/plugin-node-resolve": "^16.0.1",
"@rollup/plugin-replace": "5.0.4", "@rollup/plugin-replace": "5.0.4",
"@swc/core": "^1.11.24", "@swc/core": "^1.12.9",
"@types/hash-sum": "^1.0.2", "@types/hash-sum": "^1.0.2",
"@types/node": "^22.14.1", "@types/node": "^22.16.0",
"@types/semver": "^7.7.0", "@types/semver": "^7.7.0",
"@types/serve-handler": "^6.1.4", "@types/serve-handler": "^6.1.4",
"@vitest/ui": "^3.0.2", "@vitest/ui": "^3.0.2",
"@vitest/coverage-v8": "^3.1.3", "@vitest/coverage-v8": "^3.1.4",
"@vitest/eslint-plugin": "^1.1.44", "@vitest/eslint-plugin": "^1.2.1",
"@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.25.4", "esbuild": "^0.25.5",
"esbuild-plugin-polyfill-node": "^0.3.0", "esbuild-plugin-polyfill-node": "^0.3.0",
"eslint": "^9.25.1", "eslint": "^9.27.0",
"eslint-plugin-import-x": "^4.11.0", "eslint-plugin-import-x": "^4.13.1",
"estree-walker": "catalog:", "estree-walker": "catalog:",
"jsdom": "^26.1.0", "jsdom": "^26.1.0",
"lint-staged": "^15.5.1", "lint-staged": "^16.0.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"magic-string": "^0.30.17", "magic-string": "^0.30.17",
"markdown-table": "^3.0.4", "markdown-table": "^3.0.4",
@ -96,21 +96,21 @@
"prettier": "^3.5.3", "prettier": "^3.5.3",
"pretty-bytes": "^6.1.1", "pretty-bytes": "^6.1.1",
"pug": "^3.0.3", "pug": "^3.0.3",
"puppeteer": "~24.8.2", "puppeteer": "~24.9.0",
"rimraf": "^6.0.1", "rimraf": "^6.0.1",
"rollup": "^4.40.2", "rollup": "^4.44.1",
"rollup-plugin-dts": "^6.2.1", "rollup-plugin-dts": "^6.2.1",
"rollup-plugin-esbuild": "^6.2.1", "rollup-plugin-esbuild": "^6.2.1",
"rollup-plugin-polyfill-node": "^0.13.0", "rollup-plugin-polyfill-node": "^0.13.0",
"semver": "^7.7.1", "semver": "^7.7.2",
"serve": "^14.2.4", "serve": "^14.2.4",
"serve-handler": "^6.1.6", "serve-handler": "^6.1.6",
"simple-git-hooks": "^2.13.0", "simple-git-hooks": "^2.13.0",
"todomvc-app-css": "^2.4.3", "todomvc-app-css": "^2.4.3",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"typescript": "~5.6.2", "typescript": "~5.6.2",
"typescript-eslint": "^8.31.1", "typescript-eslint": "^8.32.1",
"vite": "catalog:", "vite": "catalog:",
"vitest": "^3.1.3" "vitest": "^3.1.4"
} }
} }

View File

@ -137,3 +137,18 @@ describe('Generic component', () => {
expectType<string | number>(comp.msg) expectType<string | number>(comp.msg)
expectType<Array<string | number>>(comp.list) expectType<Array<string | number>>(comp.list)
}) })
// #12751
{
const Comp = defineComponent({
__typeEmits: {} as {
'update:visible': [value?: boolean]
},
})
const comp: ComponentInstance<typeof Comp> = {} as any
expectType<((value?: boolean) => any) | undefined>(comp['onUpdate:visible'])
expectType<{ 'onUpdate:visible'?: (value?: boolean) => any }>(comp['$props'])
// @ts-expect-error
comp['$props']['$props']
}

View File

@ -20,6 +20,9 @@ import { type IsAny, type IsUnion, describe, expectType } from './utils'
describe('with object props', () => { describe('with object props', () => {
interface ExpectedProps { interface ExpectedProps {
a?: number | undefined a?: number | undefined
aa: number
aaa: number | null
aaaa: number | undefined
b: string b: string
e?: Function e?: Function
h: boolean h: boolean
@ -53,6 +56,19 @@ describe('with object props', () => {
const props = { const props = {
a: Number, a: Number,
aa: {
type: Number as PropType<number | undefined>,
default: 1,
},
aaa: {
type: Number as PropType<number | null>,
default: 1,
},
aaaa: {
type: Number as PropType<number | undefined>,
// `as const` prevents widening to `boolean` (keeps literal `true` type)
required: true as const,
},
// required should make property non-void // required should make property non-void
b: { b: {
type: String, type: String,
@ -146,6 +162,13 @@ describe('with object props', () => {
setup(props) { setup(props) {
// type assertion. See https://github.com/SamVerschueren/tsd // type assertion. See https://github.com/SamVerschueren/tsd
expectType<ExpectedProps['a']>(props.a) expectType<ExpectedProps['a']>(props.a)
expectType<ExpectedProps['aa']>(props.aa)
expectType<ExpectedProps['aaa']>(props.aaa)
// @ts-expect-error should included `undefined`
expectType<number>(props.aaaa)
expectType<ExpectedProps['aaaa']>(props.aaaa)
expectType<ExpectedProps['b']>(props.b) expectType<ExpectedProps['b']>(props.b)
expectType<ExpectedProps['e']>(props.e) expectType<ExpectedProps['e']>(props.e)
expectType<ExpectedProps['h']>(props.h) expectType<ExpectedProps['h']>(props.h)
@ -198,6 +221,8 @@ describe('with object props', () => {
render() { render() {
const props = this.$props const props = this.$props
expectType<ExpectedProps['a']>(props.a) expectType<ExpectedProps['a']>(props.a)
expectType<ExpectedProps['aa']>(props.aa)
expectType<ExpectedProps['aaa']>(props.aaa)
expectType<ExpectedProps['b']>(props.b) expectType<ExpectedProps['b']>(props.b)
expectType<ExpectedProps['e']>(props.e) expectType<ExpectedProps['e']>(props.e)
expectType<ExpectedProps['h']>(props.h) expectType<ExpectedProps['h']>(props.h)
@ -225,6 +250,8 @@ describe('with object props', () => {
// should also expose declared props on `this` // should also expose declared props on `this`
expectType<ExpectedProps['a']>(this.a) expectType<ExpectedProps['a']>(this.a)
expectType<ExpectedProps['aa']>(this.aa)
expectType<ExpectedProps['aaa']>(this.aaa)
expectType<ExpectedProps['b']>(this.b) expectType<ExpectedProps['b']>(this.b)
expectType<ExpectedProps['e']>(this.e) expectType<ExpectedProps['e']>(this.e)
expectType<ExpectedProps['h']>(this.h) expectType<ExpectedProps['h']>(this.h)
@ -269,6 +296,7 @@ describe('with object props', () => {
expectType<JSX.Element>( expectType<JSX.Element>(
<MyComponent <MyComponent
a={1} a={1}
aaaa={1}
b="b" b="b"
bb="bb" bb="bb"
e={() => {}} e={() => {}}
@ -295,6 +323,7 @@ describe('with object props', () => {
expectType<Component>( expectType<Component>(
<MyComponent <MyComponent
aaaa={1}
b="b" b="b"
dd={{ n: 1 }} dd={{ n: 1 }}
ddd={['ddd']} ddd={['ddd']}

View File

@ -13,7 +13,7 @@
"vite": "catalog:" "vite": "catalog:"
}, },
"dependencies": { "dependencies": {
"@vue/repl": "^4.5.1", "@vue/repl": "^4.6.1",
"file-saver": "^2.0.5", "file-saver": "^2.0.5",
"jszip": "^3.10.1", "jszip": "^3.10.1",
"vue": "workspace:*" "vue": "workspace:*"

View File

@ -156,6 +156,8 @@ onMounted(() => {
:editorOptions="{ autoSaveText: false }" :editorOptions="{ autoSaveText: false }"
:store="store" :store="store"
:showCompileOutput="true" :showCompileOutput="true"
:showSsrOutput="useSSRMode"
:showOpenSourceMap="true"
:autoResize="true" :autoResize="true"
:clearConsole="false" :clearConsole="false"
:preview-options="{ :preview-options="{

View File

@ -44,7 +44,7 @@ const App = {
h( h(
'a', 'a',
{ {
href: `https://github.com/vuejs/vue/tree/${__COMMIT__}`, href: `https://github.com/vuejs/core/tree/${__COMMIT__}`,
target: `_blank`, target: `_blank`,
}, },
`@${__COMMIT__}`, `@${__COMMIT__}`,

View File

@ -2271,6 +2271,11 @@ describe('compiler: parse', () => {
expect(span.loc.start.offset).toBe(0) expect(span.loc.start.offset).toBe(0)
expect(span.loc.end.offset).toBe(27) expect(span.loc.end.offset).toBe(27)
}) })
test('correct loc when a line in attribute value ends with &', () => {
const [span] = baseParse(`<span v-if="foo &&\nbar"></span>`).children
expect(span.loc.end.line).toBe(2)
})
}) })
describe('decodeEntities option', () => { describe('decodeEntities option', () => {

View File

@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */) _createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
]))) ])))
} }
}" }"
@ -25,11 +25,11 @@ return function render(_ctx, _cache) {
_createElementVNode("p", null, [ _createElementVNode("p", null, [
_createElementVNode("span"), _createElementVNode("span"),
_createElementVNode("span") _createElementVNode("span")
], -1 /* HOISTED */), ], -1 /* CACHED */),
_createElementVNode("p", null, [ _createElementVNode("p", null, [
_createElementVNode("span"), _createElementVNode("span"),
_createElementVNode("span") _createElementVNode("span")
], -1 /* HOISTED */) ], -1 /* CACHED */)
]))) ])))
} }
}" }"
@ -45,7 +45,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("div", null, [ _createElementVNode("div", null, [
_createCommentVNode("comment") _createCommentVNode("comment")
], -1 /* HOISTED */) ], -1 /* CACHED */)
]))) ])))
} }
}" }"
@ -59,9 +59,9 @@ return function render(_ctx, _cache) {
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* HOISTED */), _createElementVNode("span", null, null, -1 /* CACHED */),
_createTextVNode("foo"), _createTextVNode("foo"),
_createElementVNode("div", null, null, -1 /* HOISTED */) _createElementVNode("div", null, null, -1 /* CACHED */)
]))) ])))
} }
}" }"
@ -75,7 +75,7 @@ return function render(_ctx, _cache) {
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */) _createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
]))) ])))
} }
}" }"
@ -148,7 +148,7 @@ return function render(_ctx, _cache) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* HOISTED */) _createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
]))) ])))
} }
}" }"
@ -162,7 +162,7 @@ return function render(_ctx, _cache) {
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* HOISTED */) _createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
]))) ])))
} }
}" }"
@ -178,7 +178,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [ return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => { (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
_createElementVNode("span", { class: "hi" }, null, -1 /* HOISTED */) _createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
]))])) ]))]))
}), 256 /* UNKEYED_FRAGMENT */)) }), 256 /* UNKEYED_FRAGMENT */))
])) ]))
@ -216,7 +216,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [ return (_openBlock(), _createElementBlock("div", null, [
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [ _withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */) _createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
]))), [ ]))), [
[_directive_foo] [_directive_foo]
]) ])
@ -402,7 +402,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [ return (_openBlock(), _createElementBlock("div", null, [
ok ok
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [ ? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* HOISTED */) _createElementVNode("span", null, null, -1 /* CACHED */)
]))) ])))
: _createCommentVNode("v-if", true) : _createCommentVNode("v-if", true)
])) ]))
@ -410,6 +410,32 @@ return function render(_ctx, _cache) {
}" }"
`; `;
exports[`compiler: cacheStatic transform > should hoist props for root with single element excluding comments 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
const _hoisted_1 = { id: "a" }
return function render(_ctx, _cache) {
with (_ctx) {
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createCommentVNode("comment"),
_createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
_createElementVNode("div", { id: "b" }, [
_createElementVNode("div", { id: "c" }, [
_createElementVNode("div", { id: "d" }, [
_createElementVNode("div", { id: "e" }, "hello")
])
])
], -1 /* CACHED */)
]))
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
}
}"
`;
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = ` exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
"const _Vue = Vue "const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue const { createElementVNode: _createElementVNode } = _Vue
@ -423,7 +449,7 @@ return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, [ return (_openBlock(), _createElementBlock("div", null, [
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => { (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
_createElementVNode("span", null, null, -1 /* HOISTED */) _createElementVNode("span", null, null, -1 /* CACHED */)
]))) ])))
}), 256 /* UNKEYED_FRAGMENT */)) }), 256 /* UNKEYED_FRAGMENT */))
])) ]))

View File

@ -246,6 +246,28 @@ return function render(_ctx, _cache) {
}" }"
`; `;
exports[`compiler: transform component slots > with whitespace: 'preserve' > named slot with v-if + v-else 1`] = `
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createSlots: _createSlots, openBlock: _openBlock, createBlock: _createBlock } = Vue
return function render(_ctx, _cache) {
const _component_Comp = _resolveComponent("Comp")
return (_openBlock(), _createBlock(_component_Comp, null, _createSlots({ _: 2 /* DYNAMIC */ }, [
ok
? {
name: "one",
fn: _withCtx(() => ["foo"]),
key: "0"
}
: {
name: "two",
fn: _withCtx(() => ["baz"]),
key: "1"
}
]), 1024 /* DYNAMIC_SLOTS */))
}"
`;
exports[`compiler: transform component slots > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = ` exports[`compiler: transform component slots > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = `
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue "const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue

View File

@ -543,6 +543,32 @@ describe('compiler: cacheStatic transform', () => {
expect(generate(root).code).toMatchSnapshot() expect(generate(root).code).toMatchSnapshot()
}) })
test('should hoist props for root with single element excluding comments', () => {
// deeply nested div to trigger stringification condition
const root = transformWithCache(
`<!--comment--><div id="a"><div id="b"><div id="c"><div id="d"><div id="e">hello</div></div></div></div></div>`,
)
expect(root.cached.length).toBe(1)
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'a' })])
expect((root.codegenNode as VNodeCall).children).toMatchObject([
{
type: NodeTypes.COMMENT,
content: 'comment',
},
{
type: NodeTypes.ELEMENT,
codegenNode: {
type: NodeTypes.VNODE_CALL,
tag: `"div"`,
props: { content: `_hoisted_1` },
children: { type: NodeTypes.JS_CACHE_EXPRESSION },
},
},
])
expect(generate(root).code).toMatchSnapshot()
})
describe('prefixIdentifiers', () => { describe('prefixIdentifiers', () => {
test('cache nested static tree with static interpolation', () => { test('cache nested static tree with static interpolation', () => {
const root = transformWithCache( const root = transformWithCache(

View File

@ -988,5 +988,19 @@ describe('compiler: transform component slots', () => {
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot() expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
}) })
test('named slot with v-if + v-else', () => {
const source = `
<Comp>
<template #one v-if="ok">foo</template>
<template #two v-else>baz</template>
</Comp>
`
const { root } = parseWithSlots(source, {
whitespace: 'preserve',
})
expect(generate(root, { prefixIdentifiers: true }).code).toMatchSnapshot()
})
}) })
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-core", "name": "@vue/compiler-core",
"version": "3.5.14", "version": "3.6.0-alpha.1",
"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",

View File

@ -204,7 +204,9 @@ function createCodegenContext(
name = content name = content
} }
} }
addMapping(node.loc.start, name) if (node.loc.source) {
addMapping(node.loc.start, name)
}
} }
if (newlineIndex === NewlineType.Unknown) { if (newlineIndex === NewlineType.Unknown) {
// multiple newlines, full iteration // multiple newlines, full iteration
@ -241,7 +243,7 @@ function createCodegenContext(
context.column = code.length - newlineIndex context.column = code.length - newlineIndex
} }
} }
if (node && node.loc !== locStub) { if (node && node.loc !== locStub && node.loc.source) {
addMapping(node.loc.end) addMapping(node.loc.end)
} }
} }

View File

@ -647,7 +647,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
// whitespace management // whitespace management
if (!tokenizer.inRCDATA) { if (!tokenizer.inRCDATA) {
el.children = condenseWhitespace(children, tag) el.children = condenseWhitespace(children)
} }
if (ns === Namespaces.HTML && currentOptions.isIgnoreNewlineTag(tag)) { if (ns === Namespaces.HTML && currentOptions.isIgnoreNewlineTag(tag)) {
@ -832,10 +832,7 @@ function isUpperCase(c: number) {
} }
const windowsNewlineRE = /\r\n/g const windowsNewlineRE = /\r\n/g
function condenseWhitespace( function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
nodes: TemplateChildNode[],
tag?: string,
): TemplateChildNode[] {
const shouldCondense = currentOptions.whitespace !== 'preserve' const shouldCondense = currentOptions.whitespace !== 'preserve'
let removedWhitespace = false let removedWhitespace = false
for (let i = 0; i < nodes.length; i++) { for (let i = 0; i < nodes.length; i++) {

View File

@ -929,7 +929,7 @@ export default class Tokenizer {
this.buffer = input this.buffer = input
while (this.index < this.buffer.length) { while (this.index < this.buffer.length) {
const c = this.buffer.charCodeAt(this.index) const c = this.buffer.charCodeAt(this.index)
if (c === CharCodes.NewLine) { if (c === CharCodes.NewLine && this.state !== State.InEntity) {
this.newlines.push(this.index) this.newlines.push(this.index)
} }
switch (this.state) { switch (this.state) {

View File

@ -37,7 +37,7 @@ import {
helperNameMap, helperNameMap,
} from './runtimeHelpers' } from './runtimeHelpers'
import { isVSlot } from './utils' import { isVSlot } from './utils'
import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic' import { cacheStatic, getSingleElementRoot } from './transforms/cacheStatic'
import type { CompilerCompatOptions } from './compat/compatConfig' import type { CompilerCompatOptions } from './compat/compatConfig'
// There are two types of transforms: // There are two types of transforms:
@ -360,12 +360,12 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
const { helper } = context const { helper } = context
const { children } = root const { children } = root
if (children.length === 1) { if (children.length === 1) {
const child = children[0] const singleElementRootChild = getSingleElementRoot(root)
// if the single child is an element, turn it into a block. // if the single child is an element, turn it into a block.
if (isSingleElementRoot(root, child) && child.codegenNode) { if (singleElementRootChild && singleElementRootChild.codegenNode) {
// single element root is never hoisted so codegenNode will never be // single element root is never hoisted so codegenNode will never be
// SimpleExpressionNode // SimpleExpressionNode
const codegenNode = child.codegenNode const codegenNode = singleElementRootChild.codegenNode
if (codegenNode.type === NodeTypes.VNODE_CALL) { if (codegenNode.type === NodeTypes.VNODE_CALL) {
convertToBlock(codegenNode, context) convertToBlock(codegenNode, context)
} }
@ -374,7 +374,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
// - single <slot/>, IfNode, ForNode: already blocks. // - single <slot/>, IfNode, ForNode: already blocks.
// - single text node: always patched. // - single text node: always patched.
// root codegen falls through via genNode() // root codegen falls through via genNode()
root.codegenNode = child root.codegenNode = children[0]
} }
} else if (children.length > 1) { } else if (children.length > 1) {
// root has multiple nodes - return a fragment block. // root has multiple nodes - return a fragment block.

View File

@ -41,20 +41,19 @@ export function cacheStatic(root: RootNode, context: TransformContext): void {
context, context,
// Root node is unfortunately non-hoistable due to potential parent // Root node is unfortunately non-hoistable due to potential parent
// fallthrough attributes. // fallthrough attributes.
isSingleElementRoot(root, root.children[0]), !!getSingleElementRoot(root),
) )
} }
export function isSingleElementRoot( export function getSingleElementRoot(
root: RootNode, root: RootNode,
child: TemplateChildNode, ): PlainElementNode | ComponentNode | TemplateNode | null {
): child is PlainElementNode | ComponentNode | TemplateNode { const children = root.children.filter(x => x.type !== NodeTypes.COMMENT)
const { children } = root return children.length === 1 &&
return ( children[0].type === NodeTypes.ELEMENT &&
children.length === 1 && !isSlotOutlet(children[0])
child.type === NodeTypes.ELEMENT && ? children[0]
!isSlotOutlet(child) : null
)
} }
function walk( function walk(

View File

@ -594,11 +594,9 @@ export function buildProps(
hasDynamicKeys = true hasDynamicKeys = true
if (exp) { if (exp) {
if (isVBind) { if (isVBind) {
// #10696 in case a v-bind object contains ref
pushRefVForMarker()
// have to merge early for compat build check
pushMergeArg()
if (__COMPAT__) { if (__COMPAT__) {
// have to merge early for compat build check
pushMergeArg()
// 2.x v-bind object order compat // 2.x v-bind object order compat
if (__DEV__) { if (__DEV__) {
const hasOverridableKeys = mergeArgs.some(arg => { const hasOverridableKeys = mergeArgs.some(arg => {
@ -641,6 +639,9 @@ export function buildProps(
} }
} }
// #10696 in case a v-bind object contains ref
pushRefVForMarker()
pushMergeArg()
mergeArgs.push(exp) mergeArgs.push(exp)
} else { } else {
// v-on="obj" -> toHandlers(obj) // v-on="obj" -> toHandlers(obj)

View File

@ -263,7 +263,7 @@ export function processFor(
dir: DirectiveNode, dir: DirectiveNode,
context: TransformContext, context: TransformContext,
processCodegen?: (forNode: ForNode) => (() => void) | undefined, processCodegen?: (forNode: ForNode) => (() => void) | undefined,
) { ): (() => void) | undefined {
if (!dir.exp) { if (!dir.exp) {
context.onError( context.onError(
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc), createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc),

View File

@ -222,7 +222,7 @@ export function buildSlots(
let prev let prev
while (j--) { while (j--) {
prev = children[j] prev = children[j]
if (prev.type !== NodeTypes.COMMENT) { if (prev.type !== NodeTypes.COMMENT && isNonWhitespaceContent(prev)) {
break break
} }
} }

View File

@ -6,7 +6,7 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible
return function render(_ctx, _cache) { return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [ return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20), _createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
_createElementVNode("div", { key: "1" }, "1", -1 /* HOISTED */), _createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20) _createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
]))) ])))
}" }"
@ -54,7 +54,7 @@ return function render(_ctx, _cache) {
_createElementVNode("option", { value: "1" }), _createElementVNode("option", { value: "1" }),
_createElementVNode("option", { value: "1" }), _createElementVNode("option", { value: "1" }),
_createElementVNode("option", { value: "1" }) _createElementVNode("option", { value: "1" })
], -1 /* HOISTED */) ], -1 /* CACHED */)
]))) ])))
}" }"
`; `;
@ -70,11 +70,27 @@ return function render(_ctx, _cache) {
_createElementVNode("option", { value: 1 }), _createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 }), _createElementVNode("option", { value: 1 }),
_createElementVNode("option", { value: 1 }) _createElementVNode("option", { value: 1 })
], -1 /* HOISTED */) ], -1 /* CACHED */)
]))) ])))
}" }"
`; `;
exports[`stringify static html > should bail for comments 1`] = `
"const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
const _hoisted_1 = { class: "a" }
return function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createCommentVNode(" Comment 1 "),
_createElementVNode("div", _hoisted_1, [
_createCommentVNode(" Comment 2 "),
_cache[0] || (_cache[0] = _createStaticVNode("<span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span><span class=\\"b\\"></span>", 5))
])
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
}"
`;
exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = ` exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = `
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue "const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
@ -87,7 +103,7 @@ return function render(_ctx, _cache) {
_createElementVNode("span", { class: "foo" }, "foo"), _createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("span", { class: "foo" }, "foo"), _createElementVNode("span", { class: "foo" }, "foo"),
_createElementVNode("img", { src: _imports_0_ }) _createElementVNode("img", { src: _imports_0_ })
], -1 /* HOISTED */) ], -1 /* CACHED */)
]))) ])))
}" }"
`; `;

View File

@ -491,6 +491,16 @@ describe('stringify static html', () => {
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
}) })
test('should bail for comments', () => {
const { code } = compileWithStringify(
`<!-- Comment 1 --><div class="a"><!-- Comment 2 -->${repeat(
`<span class="b"/>`,
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
)}</div>`,
)
expect(code).toMatchSnapshot()
})
test('should bail for <option> elements with null values', () => { test('should bail for <option> elements with null values', () => {
const { ast, code } = compileWithStringify( const { ast, code } = compileWithStringify(
`<div><select><option :value="null" />${repeat( `<div><select><option :value="null" />${repeat(

View File

@ -1,4 +1,5 @@
import { type CompilerError, compile } from '../../src' import { type CompilerError, compile } from '../../src'
import { isValidHTMLNesting } from '../../src/htmlNesting'
describe('validate html nesting', () => { describe('validate html nesting', () => {
it('should warn with p > div', () => { it('should warn with p > div', () => {
@ -17,4 +18,185 @@ describe('validate html nesting', () => {
}) })
expect(err).toBeUndefined() expect(err).toBeUndefined()
}) })
// #13318
it('should not warn when parent tag is template', () => {
let err: CompilerError | undefined
compile(`<template><tr/></template>`, {
onWarn: e => (err = e),
})
expect(err).toBeUndefined()
})
})
/**
* Copied from https://github.com/MananTank/validate-html-nesting
* with ISC license
*/
describe('isValidHTMLNesting', () => {
test('form', () => {
// invalid
expect(isValidHTMLNesting('form', 'form')).toBe(false)
// valid
expect(isValidHTMLNesting('form', 'div')).toBe(true)
expect(isValidHTMLNesting('form', 'input')).toBe(true)
expect(isValidHTMLNesting('form', 'select')).toBe(true)
expect(isValidHTMLNesting('form', 'button')).toBe(true)
expect(isValidHTMLNesting('form', 'label')).toBe(true)
expect(isValidHTMLNesting('form', 'h1')).toBe(true)
})
test('p', () => {
// invalid
expect(isValidHTMLNesting('p', 'p')).toBe(false)
expect(isValidHTMLNesting('p', 'div')).toBe(false)
expect(isValidHTMLNesting('p', 'hr')).toBe(false)
expect(isValidHTMLNesting('p', 'blockquote')).toBe(false)
expect(isValidHTMLNesting('p', 'pre')).toBe(false)
// valid
expect(isValidHTMLNesting('p', 'a')).toBe(true)
expect(isValidHTMLNesting('p', 'span')).toBe(true)
expect(isValidHTMLNesting('p', 'abbr')).toBe(true)
expect(isValidHTMLNesting('p', 'button')).toBe(true)
expect(isValidHTMLNesting('p', 'b')).toBe(true)
expect(isValidHTMLNesting('p', 'i')).toBe(true)
expect(isValidHTMLNesting('p', 'input')).toBe(true)
expect(isValidHTMLNesting('p', 'label')).toBe(true)
})
test('a', () => {
// invalid
expect(isValidHTMLNesting('a', 'a')).toBe(false)
// valid
expect(isValidHTMLNesting('a', 'div')).toBe(true)
expect(isValidHTMLNesting('a', 'span')).toBe(true)
})
test('button', () => {
// invalid
expect(isValidHTMLNesting('button', 'button')).toBe(false)
// valid
expect(isValidHTMLNesting('button', 'div')).toBe(true)
expect(isValidHTMLNesting('button', 'span')).toBe(true)
})
test('table', () => {
// invalid
expect(isValidHTMLNesting('table', 'tr')).toBe(false)
expect(isValidHTMLNesting('table', 'table')).toBe(false)
expect(isValidHTMLNesting('table', 'td')).toBe(false)
// valid
expect(isValidHTMLNesting('table', 'thead')).toBe(true)
expect(isValidHTMLNesting('table', 'tbody')).toBe(true)
expect(isValidHTMLNesting('table', 'tfoot')).toBe(true)
expect(isValidHTMLNesting('table', 'caption')).toBe(true)
expect(isValidHTMLNesting('table', 'colgroup')).toBe(true)
})
test('td', () => {
// valid
expect(isValidHTMLNesting('td', 'span')).toBe(true)
expect(isValidHTMLNesting('tr', 'td')).toBe(true)
// invalid
expect(isValidHTMLNesting('td', 'td')).toBe(false)
expect(isValidHTMLNesting('div', 'td')).toBe(false)
})
test('tbody', () => {
// invalid
expect(isValidHTMLNesting('tbody', 'td')).toBe(false)
// valid
expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
})
test('tr', () => {
// invalid
expect(isValidHTMLNesting('tr', 'tr')).toBe(false)
expect(isValidHTMLNesting('table', 'tr')).toBe(false)
// valid
expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
expect(isValidHTMLNesting('thead', 'tr')).toBe(true)
expect(isValidHTMLNesting('tfoot', 'tr')).toBe(true)
expect(isValidHTMLNesting('tr', 'td')).toBe(true)
expect(isValidHTMLNesting('tr', 'th')).toBe(true)
})
test('li', () => {
// invalid
expect(isValidHTMLNesting('li', 'li')).toBe(false)
// valid
expect(isValidHTMLNesting('li', 'div')).toBe(true)
expect(isValidHTMLNesting('li', 'ul')).toBe(true)
})
test('headings', () => {
// invalid
expect(isValidHTMLNesting('h1', 'h1')).toBe(false)
expect(isValidHTMLNesting('h2', 'h1')).toBe(false)
expect(isValidHTMLNesting('h3', 'h1')).toBe(false)
expect(isValidHTMLNesting('h1', 'h6')).toBe(false)
// valid
expect(isValidHTMLNesting('h1', 'div')).toBe(true)
})
describe('SVG', () => {
test('svg', () => {
// invalid non-svg tags as children
expect(isValidHTMLNesting('svg', 'div')).toBe(false)
expect(isValidHTMLNesting('svg', 'img')).toBe(false)
expect(isValidHTMLNesting('svg', 'p')).toBe(false)
expect(isValidHTMLNesting('svg', 'h2')).toBe(false)
expect(isValidHTMLNesting('svg', 'span')).toBe(false)
// valid non-svg tags as children
expect(isValidHTMLNesting('svg', 'a')).toBe(true)
expect(isValidHTMLNesting('svg', 'textarea')).toBe(true)
expect(isValidHTMLNesting('svg', 'input')).toBe(true)
expect(isValidHTMLNesting('svg', 'select')).toBe(true)
// valid svg tags as children
expect(isValidHTMLNesting('svg', 'g')).toBe(true)
expect(isValidHTMLNesting('svg', 'ellipse')).toBe(true)
expect(isValidHTMLNesting('svg', 'feOffset')).toBe(true)
})
test('foreignObject', () => {
// valid
expect(isValidHTMLNesting('foreignObject', 'g')).toBe(true)
expect(isValidHTMLNesting('foreignObject', 'div')).toBe(true)
expect(isValidHTMLNesting('foreignObject', 'a')).toBe(true)
expect(isValidHTMLNesting('foreignObject', 'textarea')).toBe(true)
})
test('g', () => {
// valid
expect(isValidHTMLNesting('g', 'div')).toBe(true)
expect(isValidHTMLNesting('g', 'p')).toBe(true)
expect(isValidHTMLNesting('g', 'a')).toBe(true)
expect(isValidHTMLNesting('g', 'textarea')).toBe(true)
expect(isValidHTMLNesting('g', 'g')).toBe(true)
})
test('dl', () => {
// valid
expect(isValidHTMLNesting('dl', 'dt')).toBe(true)
expect(isValidHTMLNesting('dl', 'dd')).toBe(true)
expect(isValidHTMLNesting('dl', 'div')).toBe(true)
expect(isValidHTMLNesting('div', 'dt')).toBe(true)
expect(isValidHTMLNesting('div', 'dd')).toBe(true)
// invalid
expect(isValidHTMLNesting('span', 'dt')).toBe(false)
expect(isValidHTMLNesting('span', 'dd')).toBe(false)
})
})
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-dom", "name": "@vue/compiler-dom",
"version": "3.5.14", "version": "3.6.0-alpha.1",
"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",

View File

@ -11,6 +11,11 @@
* returns true if given parent-child nesting is valid HTML * returns true if given parent-child nesting is valid HTML
*/ */
export function isValidHTMLNesting(parent: string, child: string): boolean { export function isValidHTMLNesting(parent: string, child: string): boolean {
// if the parent is a template, it can have any child
if (parent === 'template') {
return true
}
// if we know the list of children that are the only valid children for the given parent // if we know the list of children that are the only valid children for the given parent
if (parent in onlyValidChildren) { if (parent in onlyValidChildren) {
return onlyValidChildren[parent].has(child) return onlyValidChildren[parent].has(child)

View File

@ -11,7 +11,6 @@ This package contains lower level utilities that you can use if you are writing
The API is intentionally low-level due to the various considerations when integrating Vue SFCs in a build system: The API is intentionally low-level due to the various considerations when integrating Vue SFCs in a build system:
- Separate hot-module replacement (HMR) for script, template and styles - Separate hot-module replacement (HMR) for script, template and styles
- template updates should not reset component state - template updates should not reset component state
- style updates should be performed without component re-render - style updates should be performed without component re-render

View File

@ -861,7 +861,7 @@ export default {
return (_ctx, _cache) => { return (_ctx, _cache) => {
return (_openBlock(), _createElementBlock(_Fragment, null, [ return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */), _createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */),
_cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* HOISTED */)) _cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
} }
} }
@ -884,9 +884,9 @@ export default {
return (_ctx, _push, _parent, _attrs) => { return (_ctx, _push, _parent, _attrs) => {
const _cssVars = { style: { const _cssVars = { style: {
"--xxxxxxxx-count": (count.value), ":--xxxxxxxx-count": (count.value),
"--xxxxxxxx-style\\\\.color": (style.color), ":--xxxxxxxx-style\\\\.color": (style.color),
"--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px") ":--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
}} }}
_push(\`<!--[--><div\${ _push(\`<!--[--><div\${
_ssrRenderAttrs(_cssVars) _ssrRenderAttrs(_cssVars)

View File

@ -41,8 +41,8 @@ const _hoisted_1 = _imports_0 + '#fragment'
export function render(_ctx, _cache) { export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [ return (_openBlock(), _createElementBlock(_Fragment, null, [
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)), _cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)) _cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
}" }"
`; `;

View File

@ -10,8 +10,8 @@ const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x'
export function render(_ctx, _cache) { export function render(_ctx, _cache) {
return (_openBlock(), _createElementBlock(_Fragment, null, [ return (_openBlock(), _createElementBlock(_Fragment, null, [
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)), _cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* HOISTED */)) _cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
}" }"
`; `;
@ -35,51 +35,51 @@ export function render(_ctx, _cache) {
_cache[0] || (_cache[0] = _createElementVNode("img", { _cache[0] || (_cache[0] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "" srcset: ""
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", { _cache[1] || (_cache[1] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_1 srcset: _hoisted_1
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[2] || (_cache[2] = _createElementVNode("img", { _cache[2] || (_cache[2] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_2 srcset: _hoisted_2
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[3] || (_cache[3] = _createElementVNode("img", { _cache[3] || (_cache[3] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_3 srcset: _hoisted_3
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[4] || (_cache[4] = _createElementVNode("img", { _cache[4] || (_cache[4] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_4 srcset: _hoisted_4
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[5] || (_cache[5] = _createElementVNode("img", { _cache[5] || (_cache[5] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_5 srcset: _hoisted_5
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[6] || (_cache[6] = _createElementVNode("img", { _cache[6] || (_cache[6] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_6 srcset: _hoisted_6
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[7] || (_cache[7] = _createElementVNode("img", { _cache[7] || (_cache[7] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_7 srcset: _hoisted_7
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[8] || (_cache[8] = _createElementVNode("img", { _cache[8] || (_cache[8] = _createElementVNode("img", {
src: "/logo.png", src: "/logo.png",
srcset: "/logo.png, /logo.png 2x" srcset: "/logo.png, /logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[9] || (_cache[9] = _createElementVNode("img", { _cache[9] || (_cache[9] = _createElementVNode("img", {
src: "https://example.com/logo.png", src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x" srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[10] || (_cache[10] = _createElementVNode("img", { _cache[10] || (_cache[10] = _createElementVNode("img", {
src: "/logo.png", src: "/logo.png",
srcset: _hoisted_8 srcset: _hoisted_8
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[11] || (_cache[11] = _createElementVNode("img", { _cache[11] || (_cache[11] = _createElementVNode("img", {
src: "data:image/png;base64,i", src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x" srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* HOISTED */)) }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
}" }"
`; `;
@ -92,51 +92,51 @@ export function render(_ctx, _cache) {
_cache[0] || (_cache[0] = _createElementVNode("img", { _cache[0] || (_cache[0] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "" srcset: ""
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", { _cache[1] || (_cache[1] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "/foo/logo.png" srcset: "/foo/logo.png"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[2] || (_cache[2] = _createElementVNode("img", { _cache[2] || (_cache[2] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "/foo/logo.png 2x" srcset: "/foo/logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[3] || (_cache[3] = _createElementVNode("img", { _cache[3] || (_cache[3] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "/foo/logo.png 2x" srcset: "/foo/logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[4] || (_cache[4] = _createElementVNode("img", { _cache[4] || (_cache[4] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "/foo/logo.png, /foo/logo.png 2x" srcset: "/foo/logo.png, /foo/logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[5] || (_cache[5] = _createElementVNode("img", { _cache[5] || (_cache[5] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "/foo/logo.png 2x, /foo/logo.png" srcset: "/foo/logo.png 2x, /foo/logo.png"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[6] || (_cache[6] = _createElementVNode("img", { _cache[6] || (_cache[6] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "/foo/logo.png 2x, /foo/logo.png 3x" srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[7] || (_cache[7] = _createElementVNode("img", { _cache[7] || (_cache[7] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x" srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[8] || (_cache[8] = _createElementVNode("img", { _cache[8] || (_cache[8] = _createElementVNode("img", {
src: "/logo.png", src: "/logo.png",
srcset: "/logo.png, /logo.png 2x" srcset: "/logo.png, /logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[9] || (_cache[9] = _createElementVNode("img", { _cache[9] || (_cache[9] = _createElementVNode("img", {
src: "https://example.com/logo.png", src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x" srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[10] || (_cache[10] = _createElementVNode("img", { _cache[10] || (_cache[10] = _createElementVNode("img", {
src: "/logo.png", src: "/logo.png",
srcset: "/logo.png, /foo/logo.png 2x" srcset: "/logo.png, /foo/logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[11] || (_cache[11] = _createElementVNode("img", { _cache[11] || (_cache[11] = _createElementVNode("img", {
src: "data:image/png;base64,i", src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x" srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* HOISTED */)) }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
}" }"
`; `;
@ -162,51 +162,51 @@ export function render(_ctx, _cache) {
_cache[0] || (_cache[0] = _createElementVNode("img", { _cache[0] || (_cache[0] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: "" srcset: ""
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[1] || (_cache[1] = _createElementVNode("img", { _cache[1] || (_cache[1] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_1 srcset: _hoisted_1
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[2] || (_cache[2] = _createElementVNode("img", { _cache[2] || (_cache[2] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_2 srcset: _hoisted_2
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[3] || (_cache[3] = _createElementVNode("img", { _cache[3] || (_cache[3] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_3 srcset: _hoisted_3
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[4] || (_cache[4] = _createElementVNode("img", { _cache[4] || (_cache[4] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_4 srcset: _hoisted_4
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[5] || (_cache[5] = _createElementVNode("img", { _cache[5] || (_cache[5] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_5 srcset: _hoisted_5
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[6] || (_cache[6] = _createElementVNode("img", { _cache[6] || (_cache[6] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_6 srcset: _hoisted_6
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[7] || (_cache[7] = _createElementVNode("img", { _cache[7] || (_cache[7] = _createElementVNode("img", {
src: "./logo.png", src: "./logo.png",
srcset: _hoisted_7 srcset: _hoisted_7
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[8] || (_cache[8] = _createElementVNode("img", { _cache[8] || (_cache[8] = _createElementVNode("img", {
src: "/logo.png", src: "/logo.png",
srcset: _hoisted_8 srcset: _hoisted_8
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[9] || (_cache[9] = _createElementVNode("img", { _cache[9] || (_cache[9] = _createElementVNode("img", {
src: "https://example.com/logo.png", src: "https://example.com/logo.png",
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x" srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[10] || (_cache[10] = _createElementVNode("img", { _cache[10] || (_cache[10] = _createElementVNode("img", {
src: "/logo.png", src: "/logo.png",
srcset: _hoisted_9 srcset: _hoisted_9
}, null, -1 /* HOISTED */)), }, null, -1 /* CACHED */)),
_cache[11] || (_cache[11] = _createElementVNode("img", { _cache[11] || (_cache[11] = _createElementVNode("img", {
src: "data:image/png;base64,i", src: "data:image/png;base64,i",
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x" srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
}, null, -1 /* HOISTED */)) }, null, -1 /* CACHED */))
], 64 /* STABLE_FRAGMENT */)) ], 64 /* STABLE_FRAGMENT */))
}" }"
`; `;

View File

@ -1,5 +1,11 @@
import { BindingTypes } from '@vue/compiler-core' import { BindingTypes } from '@vue/compiler-core'
import { assertCode, compileSFCScript as compile, mockId } from './utils' import {
assertCode,
compileSFCScript as compile,
getPositionInCode,
mockId,
} from './utils'
import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
describe('SFC compile <script setup>', () => { describe('SFC compile <script setup>', () => {
test('should compile JS syntax', () => { test('should compile JS syntax', () => {
@ -646,10 +652,10 @@ describe('SFC compile <script setup>', () => {
expect(content).toMatch(`return (_ctx, _push`) expect(content).toMatch(`return (_ctx, _push`)
expect(content).toMatch(`ssrInterpolate`) expect(content).toMatch(`ssrInterpolate`)
expect(content).not.toMatch(`useCssVars`) expect(content).not.toMatch(`useCssVars`)
expect(content).toMatch(`"--${mockId}-count": (count.value)`) expect(content).toMatch(`":--${mockId}-count": (count.value)`)
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`) expect(content).toMatch(`":--${mockId}-style\\\\.color": (style.color)`)
expect(content).toMatch( expect(content).toMatch(
`"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`, `":--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
) )
assertCode(content) assertCode(content)
}) })
@ -690,6 +696,27 @@ describe('SFC compile <script setup>', () => {
expect(content).toMatch(`new (_unref(Foo)).Bar()`) expect(content).toMatch(`new (_unref(Foo)).Bar()`)
assertCode(content) assertCode(content)
}) })
// #12682
test('source map', () => {
const source = `
<script setup>
const count = ref(0)
</script>
<template>
<button @click="throw new Error(\`msg\`);"></button>
</template>
`
const { content, map } = compile(source, { inlineTemplate: true })
expect(map).not.toBeUndefined()
const consumer = new SourceMapConsumer(map as RawSourceMap)
expect(
consumer.originalPositionFor(getPositionInCode(content, 'count')),
).toMatchObject(getPositionInCode(source, `count`))
expect(
consumer.originalPositionFor(getPositionInCode(content, 'Error')),
).toMatchObject(getPositionInCode(source, `Error`))
})
}) })
describe('with TypeScript', () => { describe('with TypeScript', () => {

View File

@ -278,6 +278,23 @@ describe('resolveType', () => {
}) })
}) })
test('utility type: mapped type with Omit and Pick', () => {
expect(
resolve(`
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>
interface Test {
foo: string;
bar?: string;
}
type OptionalTest = Optional<Test, 'foo'>
defineProps<OptionalTest>()
`).props,
).toStrictEqual({
foo: ['String'],
bar: ['String'],
})
})
test('utility type: ReadonlyArray', () => { test('utility type: ReadonlyArray', () => {
expect( expect(
resolve(` resolve(`
@ -714,6 +731,38 @@ describe('resolveType', () => {
}) })
}) })
describe('type alias declaration', () => {
// #13240
test('function type', () => {
expect(
resolve(`
type FunFoo<O> = (item: O) => boolean;
type FunBar = FunFoo<number>;
defineProps<{
foo?: FunFoo<number>;
bar?: FunBar;
}>()
`).props,
).toStrictEqual({
foo: ['Function'],
bar: ['Function'],
})
})
test('fallback to Unknown', () => {
expect(
resolve(`
type Brand<T> = T & {};
defineProps<{
foo: Brand<string>;
}>()
`).props,
).toStrictEqual({
foo: [UNKNOWN_TYPE],
})
})
})
describe('generics', () => { describe('generics', () => {
test('generic with type literal', () => { test('generic with type literal', () => {
expect( expect(
@ -1434,6 +1483,29 @@ describe('resolveType', () => {
colsLg: ['Number'], colsLg: ['Number'],
}) })
}) })
test('allowArbitraryExtensions', () => {
const files = {
'/foo.d.vue.ts': 'export type Foo = number;',
'/foo.vue': '<template><div /></template>',
'/bar.d.css.ts': 'export type Bar = string;',
'/bar.css': ':root { --color: red; }',
}
const { props } = resolve(
`
import { Foo } from './foo.vue'
import { Bar } from './bar.css'
defineProps<{ foo: Foo; bar: Bar }>()
`,
files,
)
expect(props).toStrictEqual({
foo: ['Number'],
bar: ['String'],
})
})
}) })
}) })

View File

@ -39,6 +39,24 @@ describe('SFC scoped CSS', () => {
expect(compileScoped(`h1 .foo { color: red; }`)).toMatch( expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
`h1 .foo[data-v-test] { color: red;`, `h1 .foo[data-v-test] { color: red;`,
) )
// #13387
expect(
compileScoped(`main {
width: 100%;
> * {
max-width: 200px;
}
}`),
).toMatchInlineSnapshot(`
"main {
&[data-v-test] {
width: 100%;
}
> *[data-v-test] {
max-width: 200px;
}
}"`)
}) })
test('nesting selector', () => { test('nesting selector', () => {

View File

@ -6,6 +6,7 @@ import {
} from '../src/compileTemplate' } from '../src/compileTemplate'
import { type SFCTemplateBlock, parse } from '../src/parse' import { type SFCTemplateBlock, parse } from '../src/parse'
import { compileScript } from '../src' import { compileScript } from '../src'
import { getPositionInCode } from './utils'
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) { function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
return compileTemplate({ return compileTemplate({
@ -157,6 +158,35 @@ test('source map', () => {
).toMatchObject(getPositionInCode(template.content, `foobar`)) ).toMatchObject(getPositionInCode(template.content, `foobar`))
}) })
test('source map: v-if generated comment should not have original position', () => {
const template = parse(
`
<template>
<div v-if="true"></div>
</template>
`,
{ filename: 'example.vue', sourceMap: true },
).descriptor.template!
const { code, map } = compile({
filename: 'example.vue',
source: template.content,
})
expect(map!.sources).toEqual([`example.vue`])
expect(map!.sourcesContent).toEqual([template.content])
const consumer = new SourceMapConsumer(map as RawSourceMap)
const commentNode = code.match(/_createCommentVNode\("v-if", true\)/)
expect(commentNode).not.toBeNull()
const commentPosition = getPositionInCode(code, commentNode![0])
const originalPosition = consumer.originalPositionFor(commentPosition)
// the comment node should not be mapped to the original source
expect(originalPosition.column).toBeNull()
expect(originalPosition.line).toBeNull()
expect(originalPosition.source).toBeNull()
})
test('should work w/ AST from descriptor', () => { test('should work w/ AST from descriptor', () => {
const source = ` const source = `
<template> <template>
@ -482,36 +512,3 @@ test('non-identifier expression in legacy filter syntax', () => {
babelParse(compilationResult.code, { sourceType: 'module' }) babelParse(compilationResult.code, { sourceType: 'module' })
}).not.toThrow() }).not.toThrow()
}) })
interface Pos {
line: number
column: number
name?: string
}
function getPositionInCode(
code: string,
token: string,
expectName: string | boolean = false,
): Pos {
const generatedOffset = code.indexOf(token)
let line = 1
let lastNewLinePos = -1
for (let i = 0; i < generatedOffset; i++) {
if (code.charCodeAt(i) === 10 /* newline char code */) {
line++
lastNewLinePos = i
}
}
const res: Pos = {
line,
column:
lastNewLinePos === -1
? generatedOffset
: generatedOffset - lastNewLinePos - 1,
}
if (expectName) {
res.name = typeof expectName === 'string' ? expectName : token
}
return res
}

View File

@ -40,3 +40,36 @@ export function assertCode(code: string): void {
} }
expect(code).toMatchSnapshot() expect(code).toMatchSnapshot()
} }
interface Pos {
line: number
column: number
name?: string
}
export function getPositionInCode(
code: string,
token: string,
expectName: string | boolean = false,
): Pos {
const generatedOffset = code.indexOf(token)
let line = 1
let lastNewLinePos = -1
for (let i = 0; i < generatedOffset; i++) {
if (code.charCodeAt(i) === 10 /* newline char code */) {
line++
lastNewLinePos = i
}
}
const res: Pos = {
line,
column:
lastNewLinePos === -1
? generatedOffset
: generatedOffset - lastNewLinePos - 1,
}
if (expectName) {
res.name = typeof expectName === 'string' ? expectName : token
}
return res
}

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-sfc", "name": "@vue/compiler-sfc",
"version": "3.5.14", "version": "3.6.0-alpha.1",
"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",
@ -50,7 +50,7 @@
"@vue/shared": "workspace:*", "@vue/shared": "workspace:*",
"estree-walker": "catalog:", "estree-walker": "catalog:",
"magic-string": "catalog:", "magic-string": "catalog:",
"postcss": "^8.5.3", "postcss": "^8.5.6",
"source-map-js": "catalog:" "source-map-js": "catalog:"
}, },
"devDependencies": { "devDependencies": {
@ -59,10 +59,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": "~10.0.1", "minimatch": "~10.0.3",
"postcss-modules": "^6.0.1", "postcss-modules": "^6.0.1",
"postcss-selector-parser": "^7.1.0", "postcss-selector-parser": "^7.1.0",
"pug": "^3.0.3", "pug": "^3.0.3",
"sass": "^1.86.3" "sass": "^1.89.2"
} }
} }

View File

@ -19,12 +19,17 @@ import type {
Declaration, Declaration,
ExportSpecifier, ExportSpecifier,
Identifier, Identifier,
LVal,
Node, Node,
ObjectPattern, ObjectPattern,
Statement, Statement,
} from '@babel/types' } from '@babel/types'
import { walk } from 'estree-walker' import { walk } from 'estree-walker'
import type { RawSourceMap } from 'source-map-js' import {
type RawSourceMap,
SourceMapConsumer,
SourceMapGenerator,
} from 'source-map-js'
import { import {
normalScriptDefaultVar, normalScriptDefaultVar,
processNormalScript, processNormalScript,
@ -543,7 +548,7 @@ export function compileScript(
} }
// defineProps // defineProps
const isDefineProps = processDefineProps(ctx, init, decl.id) const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
if (ctx.propsDestructureRestId) { if (ctx.propsDestructureRestId) {
setupBindings[ctx.propsDestructureRestId] = setupBindings[ctx.propsDestructureRestId] =
BindingTypes.SETUP_REACTIVE_CONST BindingTypes.SETUP_REACTIVE_CONST
@ -551,10 +556,10 @@ export function compileScript(
// defineEmits // defineEmits
const isDefineEmits = const isDefineEmits =
!isDefineProps && processDefineEmits(ctx, init, decl.id) !isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
!isDefineEmits && !isDefineEmits &&
(processDefineSlots(ctx, init, decl.id) || (processDefineSlots(ctx, init, decl.id as LVal) ||
processDefineModel(ctx, init, decl.id)) processDefineModel(ctx, init, decl.id as LVal))
if ( if (
isDefineProps && isDefineProps &&
@ -816,6 +821,7 @@ export function compileScript(
args += `, { ${destructureElements.join(', ')} }` args += `, { ${destructureElements.join(', ')} }`
} }
let templateMap
// 9. generate return statement // 9. generate return statement
let returned let returned
if ( if (
@ -865,7 +871,7 @@ export function compileScript(
} }
// inline render function mode - we are going to compile the template and // inline render function mode - we are going to compile the template and
// inline it right here // inline it right here
const { code, preamble, tips, errors, helpers } = compileTemplate({ const { code, preamble, tips, errors, helpers, map } = compileTemplate({
filename, filename,
ast: sfc.template.ast, ast: sfc.template.ast,
source: sfc.template.content, source: sfc.template.content,
@ -884,6 +890,7 @@ export function compileScript(
bindingMetadata: ctx.bindingMetadata, bindingMetadata: ctx.bindingMetadata,
}, },
}) })
templateMap = map
if (tips.length) { if (tips.length) {
tips.forEach(warnOnce) tips.forEach(warnOnce)
} }
@ -1030,19 +1037,28 @@ export function compileScript(
) )
} }
const content = ctx.s.toString()
let map =
options.sourceMap !== false
? (ctx.s.generateMap({
source: filename,
hires: true,
includeContent: true,
}) as unknown as RawSourceMap)
: undefined
// merge source maps of the script setup and template in inline mode
if (templateMap && map) {
const offset = content.indexOf(returned)
const templateLineOffset =
content.slice(0, offset).split(/\r?\n/).length - 1
map = mergeSourceMaps(map, templateMap, templateLineOffset)
}
return { return {
...scriptSetup, ...scriptSetup,
bindings: ctx.bindingMetadata, bindings: ctx.bindingMetadata,
imports: ctx.userImports, imports: ctx.userImports,
content: ctx.s.toString(), content,
map: map,
options.sourceMap !== false
? (ctx.s.generateMap({
source: filename,
hires: true,
includeContent: true,
}) as unknown as RawSourceMap)
: undefined,
scriptAst: scriptAst?.body, scriptAst: scriptAst?.body,
scriptSetupAst: scriptSetupAst?.body, scriptSetupAst: scriptSetupAst?.body,
deps: ctx.deps ? [...ctx.deps] : undefined, deps: ctx.deps ? [...ctx.deps] : undefined,
@ -1263,3 +1279,42 @@ function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
return false return false
} }
} }
export function mergeSourceMaps(
scriptMap: RawSourceMap,
templateMap: RawSourceMap,
templateLineOffset: number,
): RawSourceMap {
const generator = new SourceMapGenerator()
const addMapping = (map: RawSourceMap, lineOffset = 0) => {
const consumer = new SourceMapConsumer(map)
;(consumer as any).sources.forEach((sourceFile: string) => {
;(generator as any)._sources.add(sourceFile)
const sourceContent = consumer.sourceContentFor(sourceFile)
if (sourceContent != null) {
generator.setSourceContent(sourceFile, sourceContent)
}
})
consumer.eachMapping(m => {
if (m.originalLine == null) return
generator.addMapping({
generated: {
line: m.generatedLine + lineOffset,
column: m.generatedColumn,
},
original: {
line: m.originalLine,
column: m.originalColumn!,
},
source: m.source,
name: m.name,
})
})
}
addMapping(scriptMap)
addMapping(templateMap, templateLineOffset)
;(generator as any)._sourceRoot = scriptMap.sourceRoot
;(generator as any)._file = scriptMap.file
return (generator as any).toJSON()
}

View File

@ -84,7 +84,7 @@ export interface SFCDescriptor {
*/ */
slotted: boolean slotted: boolean
vapor: boolean vapor?: boolean
/** /**
* compare with an existing descriptor to determine whether HMR should perform * compare with an existing descriptor to determine whether HMR should perform

View File

@ -546,26 +546,43 @@ function resolveStringType(
ctx: TypeResolveContext, ctx: TypeResolveContext,
node: Node, node: Node,
scope: TypeScope, scope: TypeScope,
typeParameters?: Record<string, Node>,
): string[] { ): string[] {
switch (node.type) { switch (node.type) {
case 'StringLiteral': case 'StringLiteral':
return [node.value] return [node.value]
case 'TSLiteralType': case 'TSLiteralType':
return resolveStringType(ctx, node.literal, scope) return resolveStringType(ctx, node.literal, scope, typeParameters)
case 'TSUnionType': case 'TSUnionType':
return node.types.map(t => resolveStringType(ctx, t, scope)).flat() return node.types
.map(t => resolveStringType(ctx, t, scope, typeParameters))
.flat()
case 'TemplateLiteral': { case 'TemplateLiteral': {
return resolveTemplateKeys(ctx, node, scope) return resolveTemplateKeys(ctx, node, scope)
} }
case 'TSTypeReference': { case 'TSTypeReference': {
const resolved = resolveTypeReference(ctx, node, scope) const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) { if (resolved) {
return resolveStringType(ctx, resolved, scope) return resolveStringType(ctx, resolved, scope, typeParameters)
} }
if (node.typeName.type === 'Identifier') { if (node.typeName.type === 'Identifier') {
const name = node.typeName.name
if (typeParameters && typeParameters[name]) {
return resolveStringType(
ctx,
typeParameters[name],
scope,
typeParameters,
)
}
const getParam = (index = 0) => const getParam = (index = 0) =>
resolveStringType(ctx, node.typeParameters!.params[index], scope) resolveStringType(
switch (node.typeName.name) { ctx,
node.typeParameters!.params[index],
scope,
typeParameters,
)
switch (name) {
case 'Extract': case 'Extract':
return getParam(1) return getParam(1)
case 'Exclude': { case 'Exclude': {
@ -671,6 +688,7 @@ function resolveBuiltin(
ctx, ctx,
node.typeParameters!.params[1], node.typeParameters!.params[1],
scope, scope,
typeParameters,
) )
const res: ResolvedElements = { props: {}, calls: t.calls } const res: ResolvedElements = { props: {}, calls: t.calls }
for (const key of picked) { for (const key of picked) {
@ -683,6 +701,7 @@ function resolveBuiltin(
ctx, ctx,
node.typeParameters!.params[1], node.typeParameters!.params[1],
scope, scope,
typeParameters,
) )
const res: ResolvedElements = { props: {}, calls: t.calls } const res: ResolvedElements = { props: {}, calls: t.calls }
for (const key in t.props) { for (const key in t.props) {
@ -860,13 +879,13 @@ function resolveFS(ctx: TypeResolveContext): FS | undefined {
} }
return (ctx.fs = { return (ctx.fs = {
fileExists(file) { fileExists(file) {
if (file.endsWith('.vue.ts')) { if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) {
file = file.replace(/\.ts$/, '') file = file.replace(/\.ts$/, '')
} }
return fs.fileExists(file) return fs.fileExists(file)
}, },
readFile(file) { readFile(file) {
if (file.endsWith('.vue.ts')) { if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) {
file = file.replace(/\.ts$/, '') file = file.replace(/\.ts$/, '')
} }
return fs.readFile(file) return fs.readFile(file)
@ -1059,7 +1078,7 @@ function resolveWithTS(
if (res.resolvedModule) { if (res.resolvedModule) {
let filename = res.resolvedModule.resolvedFileName let filename = res.resolvedModule.resolvedFileName
if (filename.endsWith('.vue.ts')) { if (filename.endsWith('.vue.ts') && !filename.endsWith('.d.vue.ts')) {
filename = filename.replace(/\.ts$/, '') filename = filename.replace(/\.ts$/, '')
} }
return fs.realpath ? fs.realpath(filename) : filename return fs.realpath ? fs.realpath(filename) : filename
@ -1129,7 +1148,7 @@ export function fileToScope(
// fs should be guaranteed to exist here // fs should be guaranteed to exist here
const fs = resolveFS(ctx)! const fs = resolveFS(ctx)!
const source = fs.readFile(filename) || '' const source = fs.readFile(filename) || ''
const body = parseFile(filename, source, ctx.options.babelParserPlugins) const body = parseFile(filename, source, fs, ctx.options.babelParserPlugins)
const scope = new TypeScope(filename, source, 0, recordImports(body)) const scope = new TypeScope(filename, source, 0, recordImports(body))
recordTypes(ctx, body, scope, asGlobal) recordTypes(ctx, body, scope, asGlobal)
fileToScopeCache.set(filename, scope) fileToScopeCache.set(filename, scope)
@ -1139,6 +1158,7 @@ export function fileToScope(
function parseFile( function parseFile(
filename: string, filename: string,
content: string, content: string,
fs: FS,
parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'], parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
): Statement[] { ): Statement[] {
const ext = extname(filename) const ext = extname(filename)
@ -1151,7 +1171,21 @@ function parseFile(
), ),
sourceType: 'module', sourceType: 'module',
}).program.body }).program.body
} else if (ext === '.vue') { }
// simulate `allowArbitraryExtensions` on TypeScript >= 5.0
const isUnknownTypeSource = !/\.[cm]?[tj]sx?$/.test(filename)
const arbitraryTypeSource = `${filename.slice(0, -ext.length)}.d${ext}.ts`
const hasArbitraryTypeDeclaration =
isUnknownTypeSource && fs.fileExists(arbitraryTypeSource)
if (hasArbitraryTypeDeclaration) {
return babelParse(fs.readFile(arbitraryTypeSource)!, {
plugins: resolveParserPlugins('ts', parserPlugins, true),
sourceType: 'module',
}).program.body
}
if (ext === '.vue') {
const { const {
descriptor: { script, scriptSetup }, descriptor: { script, scriptSetup },
} = parse(content) } = parse(content)
@ -1554,6 +1588,15 @@ export function inferRuntimeType(
case 'TSTypeReference': { case 'TSTypeReference': {
const resolved = resolveTypeReference(ctx, node, scope) const resolved = resolveTypeReference(ctx, node, scope)
if (resolved) { if (resolved) {
// #13240
// Special case for function type aliases to ensure correct runtime behavior
// other type aliases still fallback to unknown as before
if (
resolved.type === 'TSTypeAliasDeclaration' &&
resolved.typeAnnotation.type === 'TSFunctionType'
) {
return ['Function']
}
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf) return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
} }

View File

@ -23,7 +23,12 @@ export function genCssVarsFromList(
return `{\n ${vars return `{\n ${vars
.map( .map(
key => key =>
`"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`, // The `:` prefix here is used in `ssrRenderStyle` to distinguish whether
// a custom property comes from `ssrCssVars`. If it does, we need to reset
// its value to `initial` on the component instance to avoid unintentionally
// inheriting the same property value from a different instance of the same
// component in the outer scope.
`"${isSSR ? `:--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
) )
.join(',\n ')}\n}` .join(',\n ')}\n}`
} }

View File

@ -167,6 +167,135 @@ describe('ssr: v-model', () => {
_push(\`</optgroup></select></div>\`) _push(\`</optgroup></select></div>\`)
}" }"
`) `)
expect(
compileWithWrapper(`
<select multiple v-model="model">
<optgroup>
<option v-for="item in items" :value="item">{{item}}</option>
</optgroup>
</select>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
_ssrRenderList(_ctx.items, (item) => {
_push(\`<option\${
_ssrRenderAttr("value", item)
}\${
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
? _ssrLooseContain(_ctx.model, item)
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
}>\${
_ssrInterpolate(item)
}</option>\`)
})
_push(\`<!--]--><!--for--></optgroup></select></div>\`)
}"
`)
expect(
compileWithWrapper(`
<select multiple v-model="model">
<optgroup>
<option v-if="true" :value="item">{{item}}</option>
</optgroup>
</select>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
if (true) {
_push(\`<option\${
_ssrRenderAttr("value", _ctx.item)
}\${
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
? _ssrLooseContain(_ctx.model, _ctx.item)
: _ssrLooseEqual(_ctx.model, _ctx.item))) ? " selected" : ""
}>\${
_ssrInterpolate(_ctx.item)
}</option>\`)
_push(\`<!--if-->\`)
} else {
_push(\`<!---->\`)
}
_push(\`</optgroup></select></div>\`)
}"
`)
expect(
compileWithWrapper(`
<select multiple v-model="model">
<optgroup>
<template v-if="ok">
<option v-for="item in items" :value="item">{{item}}</option>
</template>
</optgroup>
</select>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
if (_ctx.ok) {
_push(\`<!--[-->\`)
_ssrRenderList(_ctx.items, (item) => {
_push(\`<option\${
_ssrRenderAttr("value", item)
}\${
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
? _ssrLooseContain(_ctx.model, item)
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
}>\${
_ssrInterpolate(item)
}</option>\`)
})
_push(\`<!--]--><!--for-->\`)
_push(\`<!--if-->\`)
} else {
_push(\`<!---->\`)
}
_push(\`</optgroup></select></div>\`)
}"
`)
expect(
compileWithWrapper(`
<select multiple v-model="model">
<optgroup>
<template v-for="item in items" :value="item">
<option v-if="item===1" :value="item">{{item}}</option>
</template>
</optgroup>
</select>`).code,
).toMatchInlineSnapshot(`
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
_ssrRenderList(_ctx.items, (item) => {
_push(\`<!--[-->\`)
if (item===1) {
_push(\`<option\${
_ssrRenderAttr("value", item)
}\${
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
? _ssrLooseContain(_ctx.model, item)
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
}>\${
_ssrInterpolate(item)
}</option>\`)
_push(\`<!--if-->\`)
} else {
_push(\`<!---->\`)
}
_push(\`<!--]-->\`)
})
_push(\`<!--]--><!--for--></optgroup></select></div>\`)
}"
`)
}) })
test('<input type="radio">', () => { test('<input type="radio">', () => {

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-ssr", "name": "@vue/compiler-ssr",
"version": "3.5.14", "version": "3.6.0-alpha.1",
"description": "@vue/compiler-ssr", "description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js", "main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts", "types": "dist/compiler-ssr.d.ts",

View File

@ -39,6 +39,18 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
} }
} }
const processSelectChildren = (children: TemplateChildNode[]) => {
children.forEach(child => {
if (child.type === NodeTypes.ELEMENT) {
processOption(child as PlainElementNode)
} else if (child.type === NodeTypes.FOR) {
processSelectChildren(child.children)
} else if (child.type === NodeTypes.IF) {
child.branches.forEach(b => processSelectChildren(b.children))
}
})
}
function processOption(plainNode: PlainElementNode) { function processOption(plainNode: PlainElementNode) {
if (plainNode.tag === 'option') { if (plainNode.tag === 'option') {
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) { if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
@ -65,9 +77,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
) )
} }
} else if (plainNode.tag === 'optgroup') { } else if (plainNode.tag === 'optgroup') {
plainNode.children.forEach(option => processSelectChildren(plainNode.children)
processOption(option as PlainElementNode),
)
} }
} }
@ -163,18 +173,7 @@ 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') {
const processChildren = (children: TemplateChildNode[]) => { processSelectChildren(node.children)
children.forEach(child => {
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(

View File

@ -47,6 +47,21 @@ export function render(_ctx) {
}" }"
`; `;
exports[`compiler: v-for > key only binding pattern 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<tr> </tr>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
const n2 = t0()
const x2 = _child(n2)
_setText(x2, _toDisplayString(_for_item0.value.id + _for_item0.value.id))
return n2
}, (row) => (row.id))
return n0
}"
`;
exports[`compiler: v-for > multi effect 1`] = ` exports[`compiler: v-for > multi effect 1`] = `
"import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; "import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div></div>", true) const t0 = _template("<div></div>", true)
@ -130,6 +145,75 @@ export function render(_ctx) {
}" }"
`; `;
exports[`compiler: v-for > selector pattern 1`] = `
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<tr> </tr>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
const n2 = t0()
const x2 = _child(n2)
_selector0_0(() => {
_setText(x2, _toDisplayString(_ctx.selected === _for_item0.value.id ? 'danger' : ''))
})
return n2
}, (row) => (row.id))
const _selector0_0 = n0.useSelector(() => _ctx.selected)
return n0
}"
`;
exports[`compiler: v-for > selector pattern 2`] = `
"import { setClass as _setClass, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<tr></tr>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
const n2 = t0()
_selector0_0(() => {
_setClass(n2, _ctx.selected === _for_item0.value.id ? 'danger' : '')
})
return n2
}, (row) => (row.id))
const _selector0_0 = n0.useSelector(() => _ctx.selected)
return n0
}"
`;
exports[`compiler: v-for > selector pattern 3`] = `
"import { setClass as _setClass, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<tr></tr>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
const n2 = t0()
_renderEffect(() => {
const _row = _for_item0.value
_setClass(n2, _row.label === _row.id ? 'danger' : '')
})
return n2
}, (row) => (row.id))
return n0
}"
`;
exports[`compiler: v-for > selector pattern 4`] = `
"import { setClass as _setClass, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<tr></tr>", true)
export function render(_ctx) {
const n0 = _createFor(() => (_ctx.rows), (_for_item0) => {
const n2 = t0()
_selector0_0(() => {
_setClass(n2, { danger: _for_item0.value.id === _ctx.selected })
})
return n2
}, (row) => (row.id))
const _selector0_0 = n0.useSelector(() => _ctx.selected)
return n0
}"
`;
exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = ` exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
"import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue'; "import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
const t0 = _template("<div> </div>", true) const t0 = _template("<div> </div>", true)

View File

@ -67,6 +67,73 @@ describe('compiler: v-for', () => {
).lengthOf(1) ).lengthOf(1)
}) })
test('key only binding pattern', () => {
expect(
compileWithVFor(
`
<tr
v-for="row of rows"
:key="row.id"
>
{{ row.id + row.id }}
</tr>
`,
).code,
).matchSnapshot()
})
test('selector pattern', () => {
expect(
compileWithVFor(
`
<tr
v-for="row of rows"
:key="row.id"
>
{{ selected === row.id ? 'danger' : '' }}
</tr>
`,
).code,
).matchSnapshot()
expect(
compileWithVFor(
`
<tr
v-for="row of rows"
:key="row.id"
:class="selected === row.id ? 'danger' : ''"
></tr>
`,
).code,
).matchSnapshot()
// Should not be optimized because row.label is not from parent scope
expect(
compileWithVFor(
`
<tr
v-for="row of rows"
:key="row.id"
:class="row.label === row.id ? 'danger' : ''"
></tr>
`,
).code,
).matchSnapshot()
expect(
compileWithVFor(
`
<tr
v-for="row of rows"
:key="row.id"
:class="{ danger: row.id === selected }"
></tr>
`,
).code,
).matchSnapshot()
})
test('multi effect', () => { test('multi effect', () => {
const { code } = compileWithVFor( const { code } = compileWithVFor(
`<div v-for="(item, index) of items" :item="item" :index="index" />`, `<div v-for="(item, index) of items" :item="item" :index="index" />`,

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/compiler-vapor", "name": "@vue/compiler-vapor",
"version": "3.5.13", "version": "3.6.0-alpha.1",
"description": "@vue/compiler-vapor", "description": "@vue/compiler-vapor",
"main": "dist/compiler-vapor.cjs.js", "main": "dist/compiler-vapor.cjs.js",
"module": "dist/compiler-vapor.esm-bundler.js", "module": "dist/compiler-vapor.esm-bundler.js",
@ -42,8 +42,10 @@
}, },
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-vapor#readme", "homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-vapor#readme",
"dependencies": { "dependencies": {
"@babel/parser": "catalog:",
"@vue/compiler-dom": "workspace:*", "@vue/compiler-dom": "workspace:*",
"@vue/shared": "workspace:*", "@vue/shared": "workspace:*",
"estree-walker": "catalog:",
"source-map-js": "catalog:" "source-map-js": "catalog:"
} }
} }

View File

@ -19,14 +19,13 @@ export function genBlock(
context: CodegenContext, context: CodegenContext,
args: CodeFragment[] = [], args: CodeFragment[] = [],
root?: boolean, root?: boolean,
customReturns?: (returns: CodeFragment[]) => CodeFragment[],
): CodeFragment[] { ): CodeFragment[] {
return [ return [
'(', '(',
...args, ...args,
') => {', ') => {',
INDENT_START, INDENT_START,
...genBlockContent(oper, context, root, customReturns), ...genBlockContent(oper, context, root),
INDENT_END, INDENT_END,
NEWLINE, NEWLINE,
'}', '}',
@ -37,7 +36,7 @@ export function genBlockContent(
block: BlockIRNode, block: BlockIRNode,
context: CodegenContext, context: CodegenContext,
root?: boolean, root?: boolean,
customReturns?: (returns: CodeFragment[]) => CodeFragment[], genEffectsExtraFrag?: () => CodeFragment[],
): CodeFragment[] { ): CodeFragment[] {
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()
const { dynamic, effect, operation, returns } = block const { dynamic, effect, operation, returns } = block
@ -70,7 +69,7 @@ export function genBlockContent(
} }
push(...genOperations(operation, context)) push(...genOperations(operation, context))
push(...genEffects(effect, context)) push(...genEffects(effect, context, genEffectsExtraFrag))
push(NEWLINE, `return `) push(NEWLINE, `return `)
@ -79,7 +78,7 @@ export function genBlockContent(
returnNodes.length > 1 returnNodes.length > 1
? genMulti(DELIMITERS_ARRAY, ...returnNodes) ? genMulti(DELIMITERS_ARRAY, ...returnNodes)
: [returnNodes[0] || 'null'] : [returnNodes[0] || 'null']
push(...(customReturns ? customReturns(returnsCode) : returnsCode)) push(...returnsCode)
resetBlock() resetBlock()
return frag return frag

View File

@ -233,6 +233,7 @@ function canPrefix(name: string) {
type DeclarationResult = { type DeclarationResult = {
ids: Record<string, string> ids: Record<string, string>
frag: CodeFragment[] frag: CodeFragment[]
varNames: string[]
} }
type DeclarationValue = { type DeclarationValue = {
name: string name: string
@ -246,6 +247,7 @@ type DeclarationValue = {
export function processExpressions( export function processExpressions(
context: CodegenContext, context: CodegenContext,
expressions: SimpleExpressionNode[], expressions: SimpleExpressionNode[],
shouldDeclare: boolean,
): DeclarationResult { ): DeclarationResult {
// analyze variables // analyze variables
const { const {
@ -277,7 +279,11 @@ export function processExpressions(
expToVariableMap, expToVariableMap,
) )
return genDeclarations([...varDeclarations, ...expDeclarations], context) return genDeclarations(
[...varDeclarations, ...expDeclarations],
context,
shouldDeclare,
)
} }
function analyzeExpressions(expressions: SimpleExpressionNode[]) { function analyzeExpressions(expressions: SimpleExpressionNode[]) {
@ -592,15 +598,21 @@ function processRepeatedExpressions(
function genDeclarations( function genDeclarations(
declarations: DeclarationValue[], declarations: DeclarationValue[],
context: CodegenContext, context: CodegenContext,
shouldDeclare: boolean,
): DeclarationResult { ): DeclarationResult {
const [frag, push] = buildCodeFragment() const [frag, push] = buildCodeFragment()
const ids: Record<string, string> = Object.create(null) const ids: Record<string, string> = Object.create(null)
const varNames = new Set<string>()
// process identifiers first as expressions may rely on them // process identifiers first as expressions may rely on them
declarations.forEach(({ name, isIdentifier, value }) => { declarations.forEach(({ name, isIdentifier, value }) => {
if (isIdentifier) { if (isIdentifier) {
const varName = (ids[name] = `_${name}`) const varName = (ids[name] = `_${name}`)
push(`const ${varName} = `, ...genExpression(value, context), NEWLINE) varNames.add(varName)
if (shouldDeclare) {
push(`const `)
}
push(`${varName} = `, ...genExpression(value, context), NEWLINE)
} }
}) })
@ -608,15 +620,19 @@ function genDeclarations(
declarations.forEach(({ name, isIdentifier, value }) => { declarations.forEach(({ name, isIdentifier, value }) => {
if (!isIdentifier) { if (!isIdentifier) {
const varName = (ids[name] = `_${name}`) const varName = (ids[name] = `_${name}`)
varNames.add(varName)
if (shouldDeclare) {
push(`const `)
}
push( push(
`const ${varName} = `, `${varName} = `,
...context.withId(() => genExpression(value, context), ids), ...context.withId(() => genExpression(value, context), ids),
NEWLINE, NEWLINE,
) )
} }
}) })
return { ids, frag } return { ids, frag, varNames: [...varNames] }
} }
function escapeRegExp(string: string) { function escapeRegExp(string: string) {

View File

@ -1,16 +1,32 @@
import { import {
type SimpleExpressionNode, type SimpleExpressionNode,
createSimpleExpression, createSimpleExpression,
isStaticNode,
walkIdentifiers, walkIdentifiers,
} from '@vue/compiler-dom' } from '@vue/compiler-dom'
import { genBlock } from './block' import { genBlockContent } from './block'
import { genExpression } from './expression' import { genExpression } from './expression'
import type { CodegenContext } from '../generate' import type { CodegenContext } from '../generate'
import type { ForIRNode } from '../ir' import type { BlockIRNode, ForIRNode, IREffect } from '../ir'
import { type CodeFragment, NEWLINE, genCall, genMulti } from './utils' import {
import type { Identifier } from '@babel/types' type CodeFragment,
INDENT_END,
INDENT_START,
NEWLINE,
genCall,
genMulti,
} from './utils'
import {
type Expression,
type Identifier,
type Node,
isNodesEquivalent,
} from '@babel/types'
import { parseExpression } from '@babel/parser' import { parseExpression } from '@babel/parser'
import { VaporVForFlags } from '../../../shared/src/vaporFlags' import { VaporVForFlags } from '../../../shared/src/vaporFlags'
import { walk } from 'estree-walker'
import { genOperation } from './operation'
import { extend, isGloballyAllowed } from '@vue/shared'
export function genFor( export function genFor(
oper: ForIRNode, oper: ForIRNode,
@ -78,7 +94,62 @@ export function genFor(
idMap[indexVar] = null idMap[indexVar] = null
} }
const blockFn = context.withId(() => genBlock(render, context, args), idMap) const { selectorPatterns, keyOnlyBindingPatterns } = matchPatterns(
render,
keyProp,
idMap,
)
const patternFrag: CodeFragment[] = []
for (let i = 0; i < selectorPatterns.length; i++) {
const { selector } = selectorPatterns[i]
const selectorName = `_selector${id}_${i}`
patternFrag.push(
NEWLINE,
`const ${selectorName} = `,
...genCall(`n${id}.useSelector`, [
`() => `,
...genExpression(selector, context),
]),
)
}
const blockFn = context.withId(() => {
const frag: CodeFragment[] = []
frag.push('(', ...args, ') => {', INDENT_START)
if (selectorPatterns.length || keyOnlyBindingPatterns.length) {
frag.push(
...genBlockContent(render, context, false, () => {
const patternFrag: CodeFragment[] = []
for (let i = 0; i < selectorPatterns.length; i++) {
const { effect } = selectorPatterns[i]
patternFrag.push(
NEWLINE,
`_selector${id}_${i}(() => {`,
INDENT_START,
)
for (const oper of effect.operations) {
patternFrag.push(...genOperation(oper, context))
}
patternFrag.push(INDENT_END, NEWLINE, `})`)
}
for (const { effect } of keyOnlyBindingPatterns) {
for (const oper of effect.operations) {
patternFrag.push(...genOperation(oper, context))
}
}
return patternFrag
}),
)
} else {
frag.push(...genBlockContent(render, context))
}
frag.push(INDENT_END, NEWLINE, '}')
return frag
}, idMap)
exitScope() exitScope()
let flags = 0 let flags = 0
@ -103,6 +174,7 @@ export function genFor(
flags ? String(flags) : undefined, flags ? String(flags) : undefined,
// todo: hydrationNode // todo: hydrationNode
), ),
...patternFrag,
] ]
// construct a id -> accessor path map. // construct a id -> accessor path map.
@ -234,3 +306,223 @@ export function genFor(
return idMap return idMap
} }
} }
function matchPatterns(
render: BlockIRNode,
keyProp: SimpleExpressionNode | undefined,
idMap: Record<string, string | SimpleExpressionNode | null>,
) {
const selectorPatterns: NonNullable<
ReturnType<typeof matchSelectorPattern>
>[] = []
const keyOnlyBindingPatterns: NonNullable<
ReturnType<typeof matchKeyOnlyBindingPattern>
>[] = []
render.effect = render.effect.filter(effect => {
if (keyProp !== undefined) {
const selector = matchSelectorPattern(effect, keyProp.ast, idMap)
if (selector) {
selectorPatterns.push(selector)
return false
}
const keyOnly = matchKeyOnlyBindingPattern(effect, keyProp.ast)
if (keyOnly) {
keyOnlyBindingPatterns.push(keyOnly)
return false
}
}
return true
})
return {
keyOnlyBindingPatterns,
selectorPatterns,
}
}
function matchKeyOnlyBindingPattern(
effect: IREffect,
keyAst: any,
):
| {
effect: IREffect
}
| undefined {
// TODO: expressions can be multiple?
if (effect.expressions.length === 1) {
const ast = effect.expressions[0].ast
if (typeof ast === 'object' && ast !== null) {
if (isKeyOnlyBinding(ast, keyAst)) {
return { effect }
}
}
}
}
function matchSelectorPattern(
effect: IREffect,
keyAst: any,
idMap: Record<string, string | SimpleExpressionNode | null>,
):
| {
effect: IREffect
selector: SimpleExpressionNode
}
| undefined {
// TODO: expressions can be multiple?
if (effect.expressions.length === 1) {
const ast = effect.expressions[0].ast
if (typeof ast === 'object' && ast) {
const matcheds: [key: Expression, selector: Expression][] = []
walk(ast, {
enter(node) {
if (
typeof node === 'object' &&
node &&
node.type === 'BinaryExpression' &&
node.operator === '===' &&
node.left.type !== 'PrivateName'
) {
const { left, right } = node
for (const [a, b] of [
[left, right],
[right, left],
]) {
const aIsKey = isKeyOnlyBinding(a, keyAst)
const bIsKey = isKeyOnlyBinding(b, keyAst)
const bVars = analyzeVariableScopes(b, idMap)
if (aIsKey && !bIsKey && !bVars.locals.length) {
matcheds.push([a, b])
}
}
}
},
})
if (matcheds.length === 1) {
const [key, selector] = matcheds[0]
const content = effect.expressions[0].content
let hasExtraId = false
const parentStackMap = new Map<Identifier, Node[]>()
const parentStack: Node[] = []
walkIdentifiers(
ast,
id => {
if (id.start !== key.start && id.start !== selector.start) {
hasExtraId = true
}
parentStackMap.set(id, parentStack.slice())
},
false,
parentStack,
)
if (!hasExtraId) {
const name = content.slice(selector.start! - 1, selector.end! - 1)
return {
effect,
// @ts-expect-error
selector: {
content: name,
ast: extend({}, selector, {
start: 1,
end: name.length + 1,
}),
loc: selector.loc as any,
isStatic: false,
},
}
}
}
}
const content = effect.expressions[0].content
if (
typeof ast === 'object' &&
ast &&
ast.type === 'ConditionalExpression' &&
ast.test.type === 'BinaryExpression' &&
ast.test.operator === '===' &&
ast.test.left.type !== 'PrivateName' &&
isStaticNode(ast.consequent) &&
isStaticNode(ast.alternate)
) {
const left = ast.test.left
const right = ast.test.right
for (const [a, b] of [
[left, right],
[right, left],
]) {
const aIsKey = isKeyOnlyBinding(a, keyAst)
const bIsKey = isKeyOnlyBinding(b, keyAst)
const bVars = analyzeVariableScopes(b, idMap)
if (aIsKey && !bIsKey && !bVars.locals.length) {
return {
effect,
// @ts-expect-error
selector: {
content: content.slice(b.start! - 1, b.end! - 1),
ast: b,
loc: b.loc as any,
isStatic: false,
},
}
}
}
}
}
}
function analyzeVariableScopes(
ast: Node,
idMap: Record<string, string | SimpleExpressionNode | null>,
) {
let globals: string[] = []
let locals: string[] = []
const ids: Identifier[] = []
const parentStackMap = new Map<Identifier, Node[]>()
const parentStack: Node[] = []
walkIdentifiers(
ast,
id => {
ids.push(id)
parentStackMap.set(id, parentStack.slice())
},
false,
parentStack,
)
for (const id of ids) {
if (isGloballyAllowed(id.name)) {
continue
}
if (idMap[id.name]) {
locals.push(id.name)
} else {
globals.push(id.name)
}
}
return { globals, locals }
}
function isKeyOnlyBinding(expr: Node, keyAst: any) {
let only = true
walk(expr, {
enter(node) {
if (isNodesEquivalent(node, keyAst)) {
this.skip()
return
}
if (node.type === 'Identifier') {
only = false
}
},
})
return only
}

View File

@ -98,15 +98,18 @@ export function genOperation(
export function genEffects( export function genEffects(
effects: IREffect[], effects: IREffect[],
context: CodegenContext, context: CodegenContext,
genExtraFrag?: () => CodeFragment[],
): CodeFragment[] { ): CodeFragment[] {
const { helper } = context const { helper } = context
const expressions = effects.flatMap(effect => effect.expressions) const expressions = effects.flatMap(effect => effect.expressions)
const [frag, push, unshift] = buildCodeFragment() const [frag, push, unshift] = buildCodeFragment()
const shouldDeclare = genExtraFrag === undefined
let operationsCount = 0 let operationsCount = 0
const { ids, frag: declarationFrags } = processExpressions( const {
context, ids,
expressions, frag: declarationFrags,
) varNames,
} = processExpressions(context, expressions, shouldDeclare)
push(...declarationFrags) push(...declarationFrags)
for (let i = 0; i < effects.length; i++) { for (let i = 0; i < effects.length; i++) {
const effect = effects[i] const effect = effects[i]
@ -123,6 +126,9 @@ export function genEffects(
if (newLineCount > 1 || operationsCount > 1 || declarationFrags.length > 0) { if (newLineCount > 1 || operationsCount > 1 || declarationFrags.length > 0) {
unshift(`{`, INDENT_START, NEWLINE) unshift(`{`, INDENT_START, NEWLINE)
push(INDENT_END, NEWLINE, '}') push(INDENT_END, NEWLINE, '}')
if (!effects.length) {
unshift(NEWLINE)
}
} }
if (effects.length) { if (effects.length) {
@ -130,6 +136,14 @@ export function genEffects(
push(`)`) push(`)`)
} }
if (!shouldDeclare && varNames.length) {
unshift(NEWLINE, `let `, varNames.join(', '))
}
if (genExtraFrag) {
push(...context.withId(genExtraFrag, ids))
}
return frag return frag
} }

View File

@ -27,7 +27,7 @@ import {
} from '../src' } from '../src'
import type { ComputedRef, ComputedRefImpl } from '../src/computed' import type { ComputedRef, ComputedRefImpl } from '../src/computed'
import { pauseTracking, resetTracking } from '../src/effect' import { pauseTracking, resetTracking } from '../src/effect'
import { SubscriberFlags } from '../src/system' import { ReactiveFlags } from '../src/system'
describe('reactivity/computed', () => { describe('reactivity/computed', () => {
it('should return updated value', () => { it('should return updated value', () => {
@ -467,12 +467,8 @@ describe('reactivity/computed', () => {
const c2 = computed(() => c1.value) as unknown as ComputedRefImpl const c2 = computed(() => c1.value) as unknown as ComputedRefImpl
c2.value c2.value
expect( expect(c1.flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)).toBe(0)
c1.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed), expect(c2.flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)).toBe(0)
).toBe(0)
expect(
c2.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed),
).toBe(0)
}) })
it('should chained computeds dirtyLevel update with first computed effect', () => { it('should chained computeds dirtyLevel update with first computed effect', () => {

View File

@ -22,7 +22,7 @@ import {
stop, stop,
toRaw, toRaw,
} from '../src/index' } from '../src/index'
import { type Dependency, endBatch, startBatch } from '../src/system' import { type ReactiveNode, endBatch, startBatch } from '../src/system'
describe('reactivity/effect', () => { describe('reactivity/effect', () => {
it('should run the passed function once (wrapped by a effect)', () => { it('should run the passed function once (wrapped by a effect)', () => {
@ -1178,7 +1178,7 @@ describe('reactivity/effect', () => {
}) })
describe('dep unsubscribe', () => { describe('dep unsubscribe', () => {
function getSubCount(dep: Dependency | undefined) { function getSubCount(dep: ReactiveNode | undefined) {
let count = 0 let count = 0
let sub = dep!.subs let sub = dep!.subs
while (sub) { while (sub) {

View File

@ -2,6 +2,7 @@ import { nextTick, watch, watchEffect } from '@vue/runtime-core'
import { import {
type ComputedRef, type ComputedRef,
EffectScope, EffectScope,
ReactiveEffect,
computed, computed,
effect, effect,
effectScope, effectScope,
@ -9,6 +10,7 @@ import {
onScopeDispose, onScopeDispose,
reactive, reactive,
ref, ref,
setCurrentScope,
} from '../src' } from '../src'
describe('reactivity/effect/scope', () => { describe('reactivity/effect/scope', () => {
@ -20,7 +22,7 @@ describe('reactivity/effect/scope', () => {
it('should accept zero argument', () => { it('should accept zero argument', () => {
const scope = effectScope() const scope = effectScope()
expect(scope.effects.length).toBe(0) expect(getEffectsCount(scope)).toBe(0)
}) })
it('should return run value', () => { it('should return run value', () => {
@ -29,7 +31,8 @@ describe('reactivity/effect/scope', () => {
it('should work w/ active property', () => { it('should work w/ active property', () => {
const scope = effectScope() const scope = effectScope()
scope.run(() => 1) const src = computed(() => 1)
scope.run(() => src.value)
expect(scope.active).toBe(true) expect(scope.active).toBe(true)
scope.stop() scope.stop()
expect(scope.active).toBe(false) expect(scope.active).toBe(false)
@ -47,7 +50,7 @@ describe('reactivity/effect/scope', () => {
expect(dummy).toBe(7) expect(dummy).toBe(7)
}) })
expect(scope.effects.length).toBe(1) expect(getEffectsCount(scope)).toBe(1)
}) })
it('stop', () => { it('stop', () => {
@ -60,7 +63,7 @@ describe('reactivity/effect/scope', () => {
effect(() => (doubled = counter.num * 2)) effect(() => (doubled = counter.num * 2))
}) })
expect(scope.effects.length).toBe(2) expect(getEffectsCount(scope)).toBe(2)
expect(dummy).toBe(0) expect(dummy).toBe(0)
counter.num = 7 counter.num = 7
@ -87,9 +90,8 @@ describe('reactivity/effect/scope', () => {
}) })
}) })
expect(scope.effects.length).toBe(1) expect(getEffectsCount(scope)).toBe(1)
expect(scope.scopes!.length).toBe(1) expect(scope.deps?.nextDep?.dep).toBeInstanceOf(EffectScope)
expect(scope.scopes![0]).toBeInstanceOf(EffectScope)
expect(dummy).toBe(0) expect(dummy).toBe(0)
counter.num = 7 counter.num = 7
@ -117,7 +119,7 @@ describe('reactivity/effect/scope', () => {
}) })
}) })
expect(scope.effects.length).toBe(1) expect(getEffectsCount(scope)).toBe(1)
expect(dummy).toBe(0) expect(dummy).toBe(0)
counter.num = 7 counter.num = 7
@ -142,13 +144,13 @@ describe('reactivity/effect/scope', () => {
effect(() => (dummy = counter.num)) effect(() => (dummy = counter.num))
}) })
expect(scope.effects.length).toBe(1) expect(getEffectsCount(scope)).toBe(1)
scope.run(() => { scope.run(() => {
effect(() => (doubled = counter.num * 2)) effect(() => (doubled = counter.num * 2))
}) })
expect(scope.effects.length).toBe(2) expect(getEffectsCount(scope)).toBe(2)
counter.num = 7 counter.num = 7
expect(dummy).toBe(7) expect(dummy).toBe(7)
@ -166,21 +168,21 @@ describe('reactivity/effect/scope', () => {
effect(() => (dummy = counter.num)) effect(() => (dummy = counter.num))
}) })
expect(scope.effects.length).toBe(1) expect(getEffectsCount(scope)).toBe(1)
scope.stop() scope.stop()
expect(getEffectsCount(scope)).toBe(0)
scope.run(() => { scope.run(() => {
effect(() => (doubled = counter.num * 2)) effect(() => (doubled = counter.num * 2))
}) })
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned() expect(getEffectsCount(scope)).toBe(1)
expect(scope.effects.length).toBe(0)
counter.num = 7 counter.num = 7
expect(dummy).toBe(0) expect(dummy).toBe(0)
expect(doubled).toBe(undefined) expect(doubled).toBe(14)
}) })
it('should fire onScopeDispose hook', () => { it('should fire onScopeDispose hook', () => {
@ -224,9 +226,9 @@ describe('reactivity/effect/scope', () => {
it('should dereference child scope from parent scope after stopping child scope (no memleaks)', () => { it('should dereference child scope from parent scope after stopping child scope (no memleaks)', () => {
const parent = effectScope() const parent = effectScope()
const child = parent.run(() => effectScope())! const child = parent.run(() => effectScope())!
expect(parent.scopes!.includes(child)).toBe(true) expect(parent.deps?.dep).toBe(child)
child.stop() child.stop()
expect(parent.scopes!.includes(child)).toBe(false) expect(parent.deps).toBeUndefined()
}) })
it('test with higher level APIs', async () => { it('test with higher level APIs', async () => {
@ -290,21 +292,7 @@ describe('reactivity/effect/scope', () => {
parentScope.run(() => { parentScope.run(() => {
const childScope = effectScope(true) const childScope = effectScope(true)
childScope.on() setCurrentScope(setCurrentScope(childScope))
childScope.off()
expect(getCurrentScope()).toBe(parentScope)
})
})
it('calling on() and off() multiple times inside an active scope should not break currentScope', () => {
const parentScope = effectScope()
parentScope.run(() => {
const childScope = effectScope(true)
childScope.on()
childScope.on()
childScope.off()
childScope.off()
childScope.off()
expect(getCurrentScope()).toBe(parentScope) expect(getCurrentScope()).toBe(parentScope)
}) })
}) })
@ -372,7 +360,17 @@ describe('reactivity/effect/scope', () => {
expect(watcherCalls).toBe(3) expect(watcherCalls).toBe(3)
expect(cleanupCalls).toBe(1) expect(cleanupCalls).toBe(1)
expect(scope.effects.length).toBe(0) expect(getEffectsCount(scope)).toBe(0)
expect(scope.cleanups.length).toBe(0) expect(scope.cleanupsLength).toBe(0)
}) })
}) })
function getEffectsCount(scope: EffectScope): number {
let n = 0
for (let dep = scope.deps; dep !== undefined; dep = dep.nextDep) {
if (dep.dep instanceof ReactiveEffect) {
n++
}
}
return n
}

View File

@ -195,8 +195,8 @@ describe('reactivity/reactive', () => {
test('toRaw on object using reactive as prototype', () => { test('toRaw on object using reactive as prototype', () => {
const original = { foo: 1 } const original = { foo: 1 }
const observed = reactive(original) const observed = reactive(original)
const inherted = Object.create(observed) const inherited = Object.create(observed)
expect(toRaw(inherted)).toBe(inherted) expect(toRaw(inherited)).toBe(inherited)
}) })
test('toRaw on user Proxy wrapping reactive', () => { test('toRaw on user Proxy wrapping reactive', () => {

View File

@ -8,7 +8,9 @@ import {
reactive, reactive,
readonly, readonly,
ref, ref,
shallowRef,
toRaw, toRaw,
triggerRef,
} from '../src' } from '../src'
/** /**
@ -520,3 +522,16 @@ describe('reactivity/readonly', () => {
expect(r.value).toBe(ro) expect(r.value).toBe(ro)
}) })
}) })
test('should be able to trigger with triggerRef', () => {
const r = shallowRef({ a: 1 })
const ror = readonly(r)
let dummy
effect(() => {
dummy = ror.value.a
})
r.value.a = 2
expect(dummy).toBe(1)
triggerRef(ror)
expect(dummy).toBe(2)
})

View File

@ -3,40 +3,12 @@ import {
type Ref, type Ref,
WatchErrorCodes, WatchErrorCodes,
type WatchOptions, type WatchOptions,
type WatchScheduler,
computed, computed,
onWatcherCleanup, onWatcherCleanup,
ref, ref,
watch, watch,
} from '../src' } from '../src'
const queue: (() => void)[] = []
// a simple scheduler for testing purposes
let isFlushPending = false
const resolvedPromise = /*@__PURE__*/ Promise.resolve() as Promise<any>
const nextTick = (fn?: () => any) =>
fn ? resolvedPromise.then(fn) : resolvedPromise
const scheduler: WatchScheduler = (job, isFirstRun) => {
if (isFirstRun) {
job()
} else {
queue.push(job)
flushJobs()
}
}
const flushJobs = () => {
if (isFlushPending) return
isFlushPending = true
resolvedPromise.then(() => {
queue.forEach(job => job())
queue.length = 0
isFlushPending = false
})
}
describe('watch', () => { describe('watch', () => {
test('effect', () => { test('effect', () => {
let dummy: any let dummy: any
@ -147,54 +119,6 @@ describe('watch', () => {
expect(dummy).toBe(30) expect(dummy).toBe(30)
}) })
test('nested calls to baseWatch and onWatcherCleanup', async () => {
let calls: string[] = []
let source: Ref<number>
let copyist: Ref<number>
const scope = new EffectScope()
scope.run(() => {
source = ref(0)
copyist = ref(0)
// sync by default
watch(
() => {
const current = (copyist.value = source.value)
onWatcherCleanup(() => calls.push(`sync ${current}`))
},
null,
{},
)
// with scheduler
watch(
() => {
const current = copyist.value
onWatcherCleanup(() => calls.push(`post ${current}`))
},
null,
{ scheduler },
)
})
await nextTick()
expect(calls).toEqual([])
scope.run(() => source.value++)
expect(calls).toEqual(['sync 0'])
await nextTick()
expect(calls).toEqual(['sync 0', 'post 0'])
calls.length = 0
scope.run(() => source.value++)
expect(calls).toEqual(['sync 1'])
await nextTick()
expect(calls).toEqual(['sync 1', 'post 1'])
calls.length = 0
scope.stop()
expect(calls).toEqual(['sync 2', 'post 2'])
})
test('once option should be ignored by simple watch', async () => { test('once option should be ignored by simple watch', async () => {
let dummy: any let dummy: any
const source = ref(0) const source = ref(0)
@ -277,4 +201,16 @@ describe('watch', () => {
expect(dummy).toEqual([1, 2, 3]) expect(dummy).toEqual([1, 2, 3])
}) })
test('watch with immediate reset and sync flush', () => {
const value = ref(false)
watch(value, () => {
value.value = false
})
value.value = true
value.value = true
expect(value.value).toBe(false)
})
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/reactivity", "name": "@vue/reactivity",
"version": "3.5.14", "version": "3.6.0-alpha.1",
"description": "@vue/reactivity", "description": "@vue/reactivity",
"main": "index.js", "main": "index.js",
"module": "dist/reactivity.esm-bundler.js", "module": "dist/reactivity.esm-bundler.js",

View File

@ -1,9 +1,8 @@
import { isArray } from '@vue/shared' import { isArray } from '@vue/shared'
import { TrackOpTypes } from './constants' import { TrackOpTypes } from './constants'
import { ARRAY_ITERATE_KEY, track } from './dep' import { ARRAY_ITERATE_KEY, track } from './dep'
import { pauseTracking, resetTracking } from './effect'
import { isProxy, isShallow, toRaw, toReactive } from './reactive' import { isProxy, isShallow, toRaw, toReactive } from './reactive'
import { endBatch, startBatch } from './system' import { endBatch, setActiveSub, startBatch } from './system'
/** /**
* Track array iteration and return: * Track array iteration and return:
@ -320,10 +319,10 @@ function noTracking(
method: keyof Array<any>, method: keyof Array<any>,
args: unknown[] = [], args: unknown[] = [],
) { ) {
pauseTracking()
startBatch() startBatch()
const prevSub = setActiveSub()
const res = (toRaw(self) as any)[method].apply(self, args) const res = (toRaw(self) as any)[method].apply(self, args)
setActiveSub(prevSub)
endBatch() endBatch()
resetTracking()
return res return res
} }

View File

@ -96,15 +96,20 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
} }
} }
const wasRef = isRef(target)
const res = Reflect.get( const res = Reflect.get(
target, target,
key, key,
// if this is a proxy wrapping a ref, return methods using the raw ref // if this is a proxy wrapping a ref, return methods using the raw ref
// as receiver so that we don't have to call `toRaw` on the ref in all // as receiver so that we don't have to call `toRaw` on the ref in all
// its class methods // its class methods
isRef(target) ? target : receiver, wasRef ? target : receiver,
) )
if (wasRef && key !== 'value') {
return res
}
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) { if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
return res return res
} }

View File

@ -1,24 +1,19 @@
import { hasChanged, isFunction } from '@vue/shared' import { hasChanged, isFunction } from '@vue/shared'
import { ReactiveFlags, TrackOpTypes } from './constants' import { ReactiveFlags, TrackOpTypes } from './constants'
import { onTrack, setupOnTrigger } from './debug' import { onTrack, setupOnTrigger } from './debug'
import { import type { DebuggerEvent, DebuggerOptions } from './effect'
type DebuggerEvent,
type DebuggerOptions,
activeSub,
setActiveSub,
} from './effect'
import { activeEffectScope } from './effectScope' import { activeEffectScope } from './effectScope'
import type { Ref } from './ref' import type { Ref } from './ref'
import { import {
type Dependency,
type Link, type Link,
type Subscriber, type ReactiveNode,
SubscriberFlags, ReactiveFlags as SystemReactiveFlags,
activeSub,
checkDirty,
endTracking, endTracking,
link, link,
processComputedUpdate, shallowPropagate,
startTracking, startTracking,
updateDirtyFlag,
} from './system' } from './system'
import { warn } from './warning' import { warn } from './warning'
@ -53,20 +48,18 @@ export interface WritableComputedOptions<T, S = T> {
* @private exported by @vue/reactivity for Vue core use, but not exported from * @private exported by @vue/reactivity for Vue core use, but not exported from
* the main vue package * the main vue package
*/ */
export class ComputedRefImpl<T = any> implements Dependency, Subscriber { export class ComputedRefImpl<T = any> implements ReactiveNode {
/** /**
* @internal * @internal
*/ */
_value: T | undefined = undefined _value: T | undefined = undefined
// Dependency
subs: Link | undefined = undefined subs: Link | undefined = undefined
subsTail: Link | undefined = undefined subsTail: Link | undefined = undefined
// Subscriber
deps: Link | undefined = undefined deps: Link | undefined = undefined
depsTail: Link | undefined = undefined depsTail: Link | undefined = undefined
flags: SubscriberFlags = SubscriberFlags.Computed | SubscriberFlags.Dirty flags: SystemReactiveFlags =
SystemReactiveFlags.Mutable | SystemReactiveFlags.Dirty
/** /**
* @internal * @internal
@ -84,7 +77,7 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
return this return this
} }
// for backwards compat // for backwards compat
get dep(): Dependency { get dep(): ReactiveNode {
return this return this
} }
/** /**
@ -93,13 +86,17 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
*/ */
get _dirty(): boolean { get _dirty(): boolean {
const flags = this.flags const flags = this.flags
if ( if (flags & SystemReactiveFlags.Dirty) {
flags & SubscriberFlags.Dirty ||
(flags & SubscriberFlags.PendingComputed &&
updateDirtyFlag(this, this.flags))
) {
return true return true
} }
if (flags & SystemReactiveFlags.Pending) {
if (checkDirty(this.deps!, this)) {
this.flags = flags | SystemReactiveFlags.Dirty
return true
} else {
this.flags = flags & ~SystemReactiveFlags.Pending
}
}
return false return false
} }
/** /**
@ -108,9 +105,9 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
*/ */
set _dirty(v: boolean) { set _dirty(v: boolean) {
if (v) { if (v) {
this.flags |= SubscriberFlags.Dirty this.flags |= SystemReactiveFlags.Dirty
} else { } else {
this.flags &= ~(SubscriberFlags.Dirty | SubscriberFlags.PendingComputed) this.flags &= ~(SystemReactiveFlags.Dirty | SystemReactiveFlags.Pending)
} }
} }
@ -128,8 +125,18 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
get value(): T { get value(): T {
const flags = this.flags const flags = this.flags
if (flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)) { if (
processComputedUpdate(this, flags) flags & SystemReactiveFlags.Dirty ||
(flags & SystemReactiveFlags.Pending && checkDirty(this.deps!, this))
) {
if (this.update()) {
const subs = this.subs
if (subs !== undefined) {
shallowPropagate(subs)
}
}
} else if (flags & SystemReactiveFlags.Pending) {
this.flags = flags & ~SystemReactiveFlags.Pending
} }
if (activeSub !== undefined) { if (activeSub !== undefined) {
if (__DEV__) { if (__DEV__) {
@ -155,9 +162,7 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
} }
update(): boolean { update(): boolean {
const prevSub = activeSub const prevSub = startTracking(this)
setActiveSub(this)
startTracking(this)
try { try {
const oldValue = this._value const oldValue = this._value
const newValue = this.fn(oldValue) const newValue = this.fn(oldValue)
@ -167,8 +172,7 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
} }
return false return false
} finally { } finally {
setActiveSub(prevSub) endTracking(this, prevSub)
endTracking(this)
} }
} }
} }

View File

@ -1,6 +1,6 @@
import { extend } from '@vue/shared' import { extend } from '@vue/shared'
import type { DebuggerEventExtraInfo, ReactiveEffectOptions } from './effect' import type { DebuggerEventExtraInfo, ReactiveEffectOptions } from './effect'
import { type Link, type Subscriber, SubscriberFlags } from './system' import { type Link, ReactiveFlags, type ReactiveNode } from './system'
export const triggerEventInfos: DebuggerEventExtraInfo[] = [] export const triggerEventInfos: DebuggerEventExtraInfo[] = []
@ -61,7 +61,7 @@ export function setupOnTrigger(target: { new (...args: any[]): any }): void {
}) })
} }
function setupFlagsHandler(target: Subscriber): void { function setupFlagsHandler(target: ReactiveNode): void {
;(target as any)._flags = target.flags ;(target as any)._flags = target.flags
Object.defineProperty(target, 'flags', { Object.defineProperty(target, 'flags', {
get() { get() {
@ -69,8 +69,11 @@ function setupFlagsHandler(target: Subscriber): void {
}, },
set(value) { set(value) {
if ( if (
!((target as any)._flags & SubscriberFlags.Propagated) && !(
!!(value & SubscriberFlags.Propagated) (target as any)._flags &
(ReactiveFlags.Dirty | ReactiveFlags.Pending)
) &&
!!(value & (ReactiveFlags.Dirty | ReactiveFlags.Pending))
) { ) {
onTrigger(this) onTrigger(this)
} }

View File

@ -1,19 +1,22 @@
import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared' import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
import { type TrackOpTypes, TriggerOpTypes } from './constants' import { type TrackOpTypes, TriggerOpTypes } from './constants'
import { onTrack, triggerEventInfos } from './debug' import { onTrack, triggerEventInfos } from './debug'
import { activeSub } from './effect'
import { import {
type Dependency,
type Link, type Link,
ReactiveFlags,
type ReactiveNode,
activeSub,
endBatch, endBatch,
link, link,
propagate, propagate,
shallowPropagate,
startBatch, startBatch,
} from './system' } from './system'
class Dep implements Dependency { class Dep implements ReactiveNode {
_subs: Link | undefined = undefined _subs: Link | undefined = undefined
subsTail: Link | undefined = undefined subsTail: Link | undefined = undefined
flags: ReactiveFlags = ReactiveFlags.None
constructor( constructor(
private map: KeyToDepMap, private map: KeyToDepMap,
@ -103,7 +106,7 @@ export function trigger(
return return
} }
const run = (dep: Dependency | undefined) => { const run = (dep: ReactiveNode | undefined) => {
if (dep !== undefined && dep.subs !== undefined) { if (dep !== undefined && dep.subs !== undefined) {
if (__DEV__) { if (__DEV__) {
triggerEventInfos.push({ triggerEventInfos.push({
@ -116,6 +119,7 @@ export function trigger(
}) })
} }
propagate(dep.subs) propagate(dep.subs)
shallowPropagate(dep.subs)
if (__DEV__) { if (__DEV__) {
triggerEventInfos.pop() triggerEventInfos.pop()
} }
@ -190,7 +194,7 @@ export function trigger(
export function getDepFromReactive( export function getDepFromReactive(
object: any, object: any,
key: string | number | symbol, key: string | number | symbol,
): Dependency | undefined { ): ReactiveNode | undefined {
const depMap = targetMap.get(object) const depMap = targetMap.get(object)
return depMap && depMap.get(key) return depMap && depMap.get(key)
} }

View File

@ -4,18 +4,22 @@ import { setupOnTrigger } from './debug'
import { activeEffectScope } from './effectScope' import { activeEffectScope } from './effectScope'
import { import {
type Link, type Link,
type Subscriber, ReactiveFlags,
SubscriberFlags, type ReactiveNode,
activeSub,
checkDirty,
endTracking, endTracking,
link,
setActiveSub,
startTracking, startTracking,
updateDirtyFlag, unlink,
} from './system' } from './system'
import { warn } from './warning' import { warn } from './warning'
export type EffectScheduler = (...args: any[]) => any export type EffectScheduler = (...args: any[]) => any
export type DebuggerEvent = { export type DebuggerEvent = {
effect: Subscriber effect: ReactiveNode
} & DebuggerEventExtraInfo } & DebuggerEventExtraInfo
export type DebuggerEventExtraInfo = { export type DebuggerEventExtraInfo = {
@ -48,28 +52,41 @@ export enum EffectFlags {
*/ */
ALLOW_RECURSE = 1 << 7, ALLOW_RECURSE = 1 << 7,
PAUSED = 1 << 8, PAUSED = 1 << 8,
NOTIFIED = 1 << 9,
STOP = 1 << 10, STOP = 1 << 10,
} }
export class ReactiveEffect<T = any> implements ReactiveEffectOptions { export class ReactiveEffect<T = any>
// Subscriber implements ReactiveEffectOptions, ReactiveNode
{
deps: Link | undefined = undefined deps: Link | undefined = undefined
depsTail: Link | undefined = undefined depsTail: Link | undefined = undefined
flags: number = SubscriberFlags.Effect subs: Link | undefined = undefined
subsTail: Link | undefined = undefined
flags: number = ReactiveFlags.Watching | ReactiveFlags.Dirty
/** /**
* @internal * @internal
*/ */
cleanup?: () => void = undefined cleanups: (() => void)[] = []
/**
* @internal
*/
cleanupsLength = 0
onStop?: () => void // dev only
onTrack?: (event: DebuggerEvent) => void onTrack?: (event: DebuggerEvent) => void
// dev only
onTrigger?: (event: DebuggerEvent) => void onTrigger?: (event: DebuggerEvent) => void
constructor(public fn: () => T) { // @ts-expect-error
if (activeEffectScope && activeEffectScope.active) { fn(): T {}
activeEffectScope.effects.push(this)
constructor(fn?: () => T) {
if (fn !== undefined) {
this.fn = fn
}
if (activeEffectScope) {
link(this, activeEffectScope)
} }
} }
@ -78,88 +95,72 @@ export class ReactiveEffect<T = any> implements ReactiveEffectOptions {
} }
pause(): void { pause(): void {
if (!(this.flags & EffectFlags.PAUSED)) { this.flags |= EffectFlags.PAUSED
this.flags |= EffectFlags.PAUSED
}
} }
resume(): void { resume(): void {
const flags = this.flags const flags = (this.flags &= ~EffectFlags.PAUSED)
if (flags & EffectFlags.PAUSED) { if (flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) {
this.flags &= ~EffectFlags.PAUSED
}
if (flags & EffectFlags.NOTIFIED) {
this.flags &= ~EffectFlags.NOTIFIED
this.notify() this.notify()
} }
} }
notify(): void { notify(): void {
const flags = this.flags if (!(this.flags & EffectFlags.PAUSED) && this.dirty) {
if (!(flags & EffectFlags.PAUSED)) {
this.scheduler()
} else {
this.flags |= EffectFlags.NOTIFIED
}
}
scheduler(): void {
if (this.dirty) {
this.run() this.run()
} }
} }
run(): T { run(): T {
// TODO cleanupEffect
if (!this.active) { if (!this.active) {
// stopped during cleanup
return this.fn() return this.fn()
} }
cleanupEffect(this) cleanup(this)
const prevSub = activeSub const prevSub = startTracking(this)
setActiveSub(this)
startTracking(this)
try { try {
return this.fn() return this.fn()
} finally { } finally {
if (__DEV__ && activeSub !== this) { endTracking(this, prevSub)
warn( const flags = this.flags
'Active effect was not restored correctly - ' +
'this is likely a Vue internal bug.',
)
}
setActiveSub(prevSub)
endTracking(this)
if ( if (
this.flags & SubscriberFlags.Recursed && (flags & (ReactiveFlags.Recursed | EffectFlags.ALLOW_RECURSE)) ===
this.flags & EffectFlags.ALLOW_RECURSE (ReactiveFlags.Recursed | EffectFlags.ALLOW_RECURSE)
) { ) {
this.flags &= ~SubscriberFlags.Recursed this.flags = flags & ~ReactiveFlags.Recursed
this.notify() this.notify()
} }
} }
} }
stop(): void { stop(): void {
if (this.active) { if (!this.active) {
startTracking(this) return
endTracking(this)
cleanupEffect(this)
this.onStop && this.onStop()
this.flags |= EffectFlags.STOP
} }
this.flags = EffectFlags.STOP
let dep = this.deps
while (dep !== undefined) {
dep = unlink(dep, this)
}
const sub = this.subs
if (sub !== undefined) {
unlink(sub)
}
cleanup(this)
} }
get dirty(): boolean { get dirty(): boolean {
const flags = this.flags const flags = this.flags
if ( if (flags & ReactiveFlags.Dirty) {
flags & SubscriberFlags.Dirty ||
(flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags))
) {
return true return true
} }
if (flags & ReactiveFlags.Pending) {
if (checkDirty(this.deps!, this)) {
this.flags = flags | ReactiveFlags.Dirty
return true
} else {
this.flags = flags & ~ReactiveFlags.Pending
}
}
return false return false
} }
} }
@ -183,6 +184,23 @@ export function effect<T = any>(
const e = new ReactiveEffect(fn) const e = new ReactiveEffect(fn)
if (options) { if (options) {
const { onStop, scheduler } = options
if (onStop) {
options.onStop = undefined
const stop = e.stop.bind(e)
e.stop = () => {
stop()
onStop()
}
}
if (scheduler) {
options.scheduler = undefined
e.notify = () => {
if (!(e.flags & EffectFlags.PAUSED)) {
scheduler()
}
}
}
extend(e, options) extend(e, options)
} }
try { try {
@ -205,14 +223,14 @@ export function stop(runner: ReactiveEffectRunner): void {
runner.effect.stop() runner.effect.stop()
} }
const resetTrackingStack: (Subscriber | undefined)[] = [] const resetTrackingStack: (ReactiveNode | undefined)[] = []
/** /**
* Temporarily pauses tracking. * Temporarily pauses tracking.
*/ */
export function pauseTracking(): void { export function pauseTracking(): void {
resetTrackingStack.push(activeSub) resetTrackingStack.push(activeSub)
activeSub = undefined setActiveSub()
} }
/** /**
@ -230,7 +248,7 @@ export function enableTracking(): void {
resetTrackingStack.push(undefined) resetTrackingStack.push(undefined)
for (let i = resetTrackingStack.length - 1; i >= 0; i--) { for (let i = resetTrackingStack.length - 1; i >= 0; i--) {
if (resetTrackingStack[i] !== undefined) { if (resetTrackingStack[i] !== undefined) {
activeSub = resetTrackingStack[i] setActiveSub(resetTrackingStack[i])
break break
} }
} }
@ -248,9 +266,21 @@ export function resetTracking(): void {
) )
} }
if (resetTrackingStack.length) { if (resetTrackingStack.length) {
activeSub = resetTrackingStack.pop()! setActiveSub(resetTrackingStack.pop()!)
} else { } else {
activeSub = undefined setActiveSub()
}
}
export function cleanup(
sub: ReactiveNode & { cleanups: (() => void)[]; cleanupsLength: number },
): void {
const l = sub.cleanupsLength
if (l) {
for (let i = 0; i < l; i++) {
sub.cleanups[i]()
}
sub.cleanupsLength = 0
} }
} }
@ -268,7 +298,7 @@ export function resetTracking(): void {
*/ */
export function onEffectCleanup(fn: () => void, failSilently = false): void { export function onEffectCleanup(fn: () => void, failSilently = false): void {
if (activeSub instanceof ReactiveEffect) { if (activeSub instanceof ReactiveEffect) {
activeSub.cleanup = fn activeSub.cleanups[activeSub.cleanupsLength++] = () => cleanupEffect(fn)
} else if (__DEV__ && !failSilently) { } else if (__DEV__ && !failSilently) {
warn( warn(
`onEffectCleanup() was called when there was no active effect` + `onEffectCleanup() was called when there was no active effect` +
@ -277,23 +307,12 @@ export function onEffectCleanup(fn: () => void, failSilently = false): void {
} }
} }
function cleanupEffect(e: ReactiveEffect) { function cleanupEffect(fn: () => void) {
const { cleanup } = e // run cleanup without active effect
e.cleanup = undefined const prevSub = setActiveSub()
if (cleanup !== undefined) { try {
// run cleanup without active effect fn()
const prevSub = activeSub } finally {
activeSub = undefined setActiveSub(prevSub)
try {
cleanup()
} finally {
activeSub = prevSub
}
} }
} }
export let activeSub: Subscriber | undefined = undefined
export function setActiveSub(sub: Subscriber | undefined): void {
activeSub = sub
}

View File

@ -1,57 +1,34 @@
import { EffectFlags, type ReactiveEffect } from './effect' import { EffectFlags, cleanup } from './effect'
import { import {
type Link, type Link,
type Subscriber, type ReactiveNode,
endTracking, link,
startTracking, setActiveSub,
unlink,
} from './system' } from './system'
import { warn } from './warning' import { warn } from './warning'
export let activeEffectScope: EffectScope | undefined export let activeEffectScope: EffectScope | undefined
export class EffectScope implements Subscriber { export class EffectScope implements ReactiveNode {
// Subscriber: In order to collect orphans computeds
deps: Link | undefined = undefined deps: Link | undefined = undefined
depsTail: Link | undefined = undefined depsTail: Link | undefined = undefined
subs: Link | undefined = undefined
subsTail: Link | undefined = undefined
flags: number = 0 flags: number = 0
/**
* @internal track `on` calls, allow `on` call multiple times
*/
private _on = 0
/**
* @internal
*/
effects: ReactiveEffect[] = []
/** /**
* @internal * @internal
*/ */
cleanups: (() => void)[] = [] cleanups: (() => void)[] = []
/**
* @internal
*/
cleanupsLength = 0
/** constructor(detached = false) {
* only assigned by undetached scope if (!detached && activeEffectScope) {
* @internal link(this, activeEffectScope)
*/
parent: EffectScope | undefined
/**
* record undetached scopes
* @internal
*/
scopes: EffectScope[] | undefined
/**
* track a child scope's index in its parent's scopes array for optimized
* removal
* @internal
*/
private index: number | undefined
constructor(
public detached = false,
parent: EffectScope | undefined = activeEffectScope,
) {
this.parent = parent
if (!detached && parent) {
this.index = (parent.scopes || (parent.scopes = [])).push(this) - 1
} }
} }
@ -62,15 +39,12 @@ export class EffectScope implements Subscriber {
pause(): void { pause(): void {
if (!(this.flags & EffectFlags.PAUSED)) { if (!(this.flags & EffectFlags.PAUSED)) {
this.flags |= EffectFlags.PAUSED this.flags |= EffectFlags.PAUSED
let i, l for (let link = this.deps; link !== undefined; link = link.nextDep) {
if (this.scopes) { const dep = link.dep
for (i = 0, l = this.scopes.length; i < l; i++) { if ('pause' in dep) {
this.scopes[i].pause() dep.pause()
} }
} }
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].pause()
}
} }
} }
@ -78,91 +52,50 @@ export class EffectScope implements Subscriber {
* Resumes the effect scope, including all child scopes and effects. * Resumes the effect scope, including all child scopes and effects.
*/ */
resume(): void { resume(): void {
if (this.flags & EffectFlags.PAUSED) { const flags = this.flags
this.flags &= ~EffectFlags.PAUSED if (flags & EffectFlags.PAUSED) {
let i, l this.flags = flags & ~EffectFlags.PAUSED
if (this.scopes) { for (let link = this.deps; link !== undefined; link = link.nextDep) {
for (i = 0, l = this.scopes.length; i < l; i++) { const dep = link.dep
this.scopes[i].resume() if ('resume' in dep) {
dep.resume()
} }
} }
for (i = 0, l = this.effects.length; i < l; i++) {
this.effects[i].resume()
}
} }
} }
run<T>(fn: () => T): T | undefined { run<T>(fn: () => T): T | undefined {
if (this.active) { const prevSub = setActiveSub()
const prevEffectScope = activeEffectScope const prevScope = activeEffectScope
try { try {
activeEffectScope = this
return fn()
} finally {
activeEffectScope = prevEffectScope
}
} else if (__DEV__) {
warn(`cannot run an inactive effect scope.`)
}
}
prevScope: EffectScope | undefined
/**
* This should only be called on non-detached scopes
* @internal
*/
on(): void {
if (++this._on === 1) {
this.prevScope = activeEffectScope
activeEffectScope = this activeEffectScope = this
return fn()
} finally {
activeEffectScope = prevScope
setActiveSub(prevSub)
} }
} }
/** stop(): void {
* This should only be called on non-detached scopes if (!this.active) {
* @internal return
*/
off(): void {
if (this._on > 0 && --this._on === 0) {
activeEffectScope = this.prevScope
this.prevScope = undefined
} }
} this.flags = EffectFlags.STOP
let dep = this.deps
stop(fromParent?: boolean): void { while (dep !== undefined) {
if (this.active) { const node = dep.dep
this.flags |= EffectFlags.STOP if ('stop' in node) {
startTracking(this) dep = dep.nextDep
endTracking(this) node.stop()
let i, l } else {
for (i = 0, l = this.effects.length; i < l; i++) { dep = unlink(dep, this)
this.effects[i].stop()
} }
this.effects.length = 0
for (i = 0, l = this.cleanups.length; i < l; i++) {
this.cleanups[i]()
}
this.cleanups.length = 0
if (this.scopes) {
for (i = 0, l = this.scopes.length; i < l; i++) {
this.scopes[i].stop(true)
}
this.scopes.length = 0
}
// nested scope, dereference from parent to avoid memory leaks
if (!this.detached && this.parent && !fromParent) {
// optimized O(1) removal
const last = this.parent.scopes!.pop()
if (last && last !== this) {
this.parent.scopes![this.index!] = last
last.index = this.index!
}
}
this.parent = undefined
} }
const sub = this.subs
if (sub !== undefined) {
unlink(sub)
}
cleanup(this)
} }
} }
@ -188,6 +121,14 @@ export function getCurrentScope(): EffectScope | undefined {
return activeEffectScope return activeEffectScope
} }
export function setCurrentScope(scope?: EffectScope): EffectScope | undefined {
try {
return activeEffectScope
} finally {
activeEffectScope = scope
}
}
/** /**
* Registers a dispose callback on the current active effect scope. The * Registers a dispose callback on the current active effect scope. The
* callback will be invoked when the associated effect scope is stopped. * callback will be invoked when the associated effect scope is stopped.
@ -196,8 +137,8 @@ export function getCurrentScope(): EffectScope | undefined {
* @see {@link https://vuejs.org/api/reactivity-advanced.html#onscopedispose} * @see {@link https://vuejs.org/api/reactivity-advanced.html#onscopedispose}
*/ */
export function onScopeDispose(fn: () => void, failSilently = false): void { export function onScopeDispose(fn: () => void, failSilently = false): void {
if (activeEffectScope) { if (activeEffectScope !== undefined) {
activeEffectScope.cleanups.push(fn) activeEffectScope.cleanups[activeEffectScope.cleanupsLength++] = fn
} else if (__DEV__ && !failSilently) { } else if (__DEV__ && !failSilently) {
warn( warn(
`onScopeDispose() is called when there is no active effect scope` + `onScopeDispose() is called when there is no active effect scope` +

View File

@ -76,6 +76,10 @@ export {
effectScope, effectScope,
EffectScope, EffectScope,
getCurrentScope, getCurrentScope,
/**
* @internal
*/
setCurrentScope,
onScopeDispose, onScopeDispose,
} from './effectScope' } from './effectScope'
export { reactiveReadArray, shallowReadArray } from './arrayInstrumentations' export { reactiveReadArray, shallowReadArray } from './arrayInstrumentations'
@ -86,8 +90,11 @@ export {
traverse, traverse,
onWatcherCleanup, onWatcherCleanup,
WatchErrorCodes, WatchErrorCodes,
/**
* @internal
*/
WatcherEffect,
type WatchOptions, type WatchOptions,
type WatchScheduler,
type WatchStopHandle, type WatchStopHandle,
type WatchHandle, type WatchHandle,
type WatchEffect, type WatchEffect,
@ -95,3 +102,7 @@ export {
type WatchCallback, type WatchCallback,
type OnCleanup, type OnCleanup,
} from './watch' } from './watch'
/**
* @internal
*/
export { setActiveSub } from './system'

View File

@ -9,7 +9,6 @@ import type { ComputedRef, WritableComputedRef } from './computed'
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants' import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
import { onTrack, triggerEventInfos } from './debug' import { onTrack, triggerEventInfos } from './debug'
import { getDepFromReactive } from './dep' import { getDepFromReactive } from './dep'
import { activeSub } from './effect'
import { import {
type Builtin, type Builtin,
type ShallowReactiveMarker, type ShallowReactiveMarker,
@ -19,7 +18,17 @@ import {
toRaw, toRaw,
toReactive, toReactive,
} from './reactive' } from './reactive'
import { type Dependency, type Link, link, propagate } from './system' import {
type Link,
type ReactiveNode,
ReactiveFlags as _ReactiveFlags,
activeSub,
batchDepth,
flush,
link,
propagate,
shallowPropagate,
} from './system'
declare const RefSymbol: unique symbol declare const RefSymbol: unique symbol
export declare const RawSymbol: unique symbol export declare const RawSymbol: unique symbol
@ -106,31 +115,46 @@ function createRef(rawValue: unknown, wrap?: <T>(v: T) => T) {
/** /**
* @internal * @internal
*/ */
class RefImpl<T = any> implements Dependency { class RefImpl<T = any> implements ReactiveNode {
// Dependency
subs: Link | undefined = undefined subs: Link | undefined = undefined
subsTail: Link | undefined = undefined subsTail: Link | undefined = undefined
flags: _ReactiveFlags = _ReactiveFlags.Mutable
_value: T _value: T
_wrap?: <T>(v: T) => T _wrap?: <T>(v: T) => T
private _oldValue: T
private _rawValue: T private _rawValue: T
public readonly [ReactiveFlags.IS_REF] = true /**
public readonly [ReactiveFlags.IS_SHALLOW]: boolean = false * @internal
*/
readonly __v_isRef = true
// TODO isolatedDeclarations ReactiveFlags.IS_REF
/**
* @internal
*/
readonly __v_isShallow: boolean = false
// TODO isolatedDeclarations ReactiveFlags.IS_SHALLOW
constructor(value: T, wrap: (<T>(v: T) => T) | undefined) { constructor(value: T, wrap: (<T>(v: T) => T) | undefined) {
this._rawValue = wrap ? toRaw(value) : value this._oldValue = this._rawValue = wrap ? toRaw(value) : value
this._value = wrap ? wrap(value) : value this._value = wrap ? wrap(value) : value
this._wrap = wrap this._wrap = wrap
this[ReactiveFlags.IS_SHALLOW] = !wrap this[ReactiveFlags.IS_SHALLOW] = !wrap
} }
get dep() { get dep(): this {
return this return this
} }
get value() { get value(): T {
trackRef(this) trackRef(this)
if (this.flags & _ReactiveFlags.Dirty && this.update()) {
const subs = this.subs
if (subs !== undefined) {
shallowPropagate(subs)
}
}
return this._value return this._value
} }
@ -142,24 +166,36 @@ class RefImpl<T = any> implements Dependency {
isReadonly(newValue) isReadonly(newValue)
newValue = useDirectValue ? newValue : toRaw(newValue) newValue = useDirectValue ? newValue : toRaw(newValue)
if (hasChanged(newValue, oldValue)) { if (hasChanged(newValue, oldValue)) {
this.flags |= _ReactiveFlags.Dirty
this._rawValue = newValue this._rawValue = newValue
this._value = this._value =
this._wrap && !useDirectValue ? this._wrap(newValue) : newValue !useDirectValue && this._wrap ? this._wrap(newValue) : newValue
if (__DEV__) { const subs = this.subs
triggerEventInfos.push({ if (subs !== undefined) {
target: this, if (__DEV__) {
type: TriggerOpTypes.SET, triggerEventInfos.push({
key: 'value', target: this,
newValue, type: TriggerOpTypes.SET,
oldValue, key: 'value',
}) newValue,
} oldValue,
triggerRef(this as unknown as Ref) })
if (__DEV__) { }
triggerEventInfos.pop() propagate(subs)
if (!batchDepth) {
flush()
}
if (__DEV__) {
triggerEventInfos.pop()
}
} }
} }
} }
update(): boolean {
this.flags &= ~_ReactiveFlags.Dirty
return hasChanged(this._oldValue, (this._oldValue = this._rawValue))
}
} }
/** /**
@ -192,10 +228,14 @@ export function triggerRef(ref: Ref): void {
const dep = (ref as unknown as RefImpl).dep const dep = (ref as unknown as RefImpl).dep
if (dep !== undefined && dep.subs !== undefined) { if (dep !== undefined && dep.subs !== undefined) {
propagate(dep.subs) propagate(dep.subs)
shallowPropagate(dep.subs)
if (!batchDepth) {
flush()
}
} }
} }
function trackRef(dep: Dependency) { function trackRef(dep: ReactiveNode) {
if (activeSub !== undefined) { if (activeSub !== undefined) {
if (__DEV__) { if (__DEV__) {
onTrack(activeSub!, { onTrack(activeSub!, {
@ -296,10 +336,10 @@ export type CustomRefFactory<T> = (
set: (value: T) => void set: (value: T) => void
} }
class CustomRefImpl<T> implements Dependency { class CustomRefImpl<T> implements ReactiveNode {
// Dependency
subs: Link | undefined = undefined subs: Link | undefined = undefined
subsTail: Link | undefined = undefined subsTail: Link | undefined = undefined
flags: _ReactiveFlags = _ReactiveFlags.None
private readonly _get: ReturnType<CustomRefFactory<T>>['get'] private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set'] private readonly _set: ReturnType<CustomRefFactory<T>>['set']
@ -380,7 +420,7 @@ class ObjectRefImpl<T extends object, K extends keyof T> {
this._object[this._key] = newVal this._object[this._key] = newVal
} }
get dep(): Dependency | undefined { get dep(): ReactiveNode | undefined {
return getDepFromReactive(toRaw(this._object), this._key) return getDepFromReactive(toRaw(this._object), this._key)
} }
} }

View File

@ -1,220 +1,255 @@
/* eslint-disable */ /* eslint-disable */
// Ported from https://github.com/stackblitz/alien-signals/blob/v1.0.13/src/system.ts // Ported from https://github.com/stackblitz/alien-signals/blob/v2.0.4/src/system.ts
import type { ComputedRefImpl as Computed } from './computed.js' import type { ComputedRefImpl as Computed } from './computed.js'
import type { ReactiveEffect as Effect } from './effect.js' import type { ReactiveEffect as Effect } from './effect.js'
import type { EffectScope } from './effectScope.js'
import { warn } from './warning.js'
export interface Dependency { export interface ReactiveNode {
subs: Link | undefined deps?: Link
subsTail: Link | undefined depsTail?: Link
} subs?: Link
subsTail?: Link
export interface Subscriber { flags: ReactiveFlags
flags: SubscriberFlags
deps: Link | undefined
depsTail: Link | undefined
} }
export interface Link { export interface Link {
dep: Dependency | Computed dep: ReactiveNode | Computed | Effect | EffectScope
sub: Subscriber | Computed | Effect sub: ReactiveNode | Computed | Effect | EffectScope
prevSub: Link | undefined prevSub: Link | undefined
nextSub: Link | undefined nextSub: Link | undefined
prevDep: Link | undefined
nextDep: Link | undefined nextDep: Link | undefined
} }
export const enum SubscriberFlags { interface Stack<T> {
Computed = 1 << 0, value: T
Effect = 1 << 1, prev: Stack<T> | undefined
Tracking = 1 << 2,
Recursed = 1 << 4,
Dirty = 1 << 5,
PendingComputed = 1 << 6,
Propagated = Dirty | PendingComputed,
} }
interface OneWayLink<T> { export const enum ReactiveFlags {
target: T None = 0,
linked: OneWayLink<T> | undefined Mutable = 1 << 0,
Watching = 1 << 1,
RecursedCheck = 1 << 2,
Recursed = 1 << 3,
Dirty = 1 << 4,
Pending = 1 << 5,
} }
const notifyBuffer: (Effect | undefined)[] = [] const notifyBuffer: (Effect | undefined)[] = []
let batchDepth = 0 export let batchDepth = 0
export let activeSub: ReactiveNode | undefined = undefined
let notifyIndex = 0 let notifyIndex = 0
let notifyBufferLength = 0 let notifyBufferLength = 0
export function setActiveSub(sub?: ReactiveNode): ReactiveNode | undefined {
try {
return activeSub
} finally {
activeSub = sub
}
}
export function startBatch(): void { export function startBatch(): void {
++batchDepth ++batchDepth
} }
export function endBatch(): void { export function endBatch(): void {
if (!--batchDepth) { if (!--batchDepth && notifyBufferLength) {
processEffectNotifications() flush()
} }
} }
export function link(dep: Dependency, sub: Subscriber): Link | undefined { export function link(dep: ReactiveNode, sub: ReactiveNode): void {
const currentDep = sub.depsTail const prevDep = sub.depsTail
if (currentDep !== undefined && currentDep.dep === dep) { if (prevDep !== undefined && prevDep.dep === dep) {
return return
} }
const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps let nextDep: Link | undefined = undefined
if (nextDep !== undefined && nextDep.dep === dep) { const recursedCheck = sub.flags & ReactiveFlags.RecursedCheck
sub.depsTail = nextDep if (recursedCheck) {
return nextDep = prevDep !== undefined ? prevDep.nextDep : sub.deps
if (nextDep !== undefined && nextDep.dep === dep) {
sub.depsTail = nextDep
return
}
} }
const depLastSub = dep.subsTail const prevSub = dep.subsTail
if ( if (
depLastSub !== undefined && prevSub !== undefined &&
depLastSub.sub === sub && prevSub.sub === sub &&
isValidLink(depLastSub, sub) (!recursedCheck || isValidLink(prevSub, sub))
) { ) {
return return
} }
return linkNewDep(dep, sub, nextDep, currentDep) const newLink =
(sub.depsTail =
dep.subsTail =
{
dep,
sub,
prevDep,
nextDep,
prevSub,
nextSub: undefined,
})
if (nextDep !== undefined) {
nextDep.prevDep = newLink
}
if (prevDep !== undefined) {
prevDep.nextDep = newLink
} else {
sub.deps = newLink
}
if (prevSub !== undefined) {
prevSub.nextSub = newLink
} else {
dep.subs = newLink
}
} }
export function propagate(current: Link): void { export function unlink(
let next = current.nextSub link: Link,
let branchs: OneWayLink<Link | undefined> | undefined sub: ReactiveNode = link.sub,
let branchDepth = 0 ): Link | undefined {
let targetFlag = SubscriberFlags.Dirty const dep = link.dep
const prevDep = link.prevDep
const nextDep = link.nextDep
const nextSub = link.nextSub
const prevSub = link.prevSub
if (nextDep !== undefined) {
nextDep.prevDep = prevDep
} else {
sub.depsTail = prevDep
}
if (prevDep !== undefined) {
prevDep.nextDep = nextDep
} else {
sub.deps = nextDep
}
if (nextSub !== undefined) {
nextSub.prevSub = prevSub
} else {
dep.subsTail = prevSub
}
if (prevSub !== undefined) {
prevSub.nextSub = nextSub
} else if ((dep.subs = nextSub) === undefined) {
let toRemove = dep.deps
if (toRemove !== undefined) {
do {
toRemove = unlink(toRemove, dep)
} while (toRemove !== undefined)
dep.flags |= ReactiveFlags.Dirty
}
}
return nextDep
}
export function propagate(link: Link): void {
let next = link.nextSub
let stack: Stack<Link | undefined> | undefined
top: do { top: do {
const sub = current.sub const sub = link.sub
const subFlags = sub.flags
let shouldNotify = false let flags = sub.flags
if ( if (flags & (ReactiveFlags.Mutable | ReactiveFlags.Watching)) {
!( if (
subFlags & !(
(SubscriberFlags.Tracking | flags &
SubscriberFlags.Recursed | (ReactiveFlags.RecursedCheck |
SubscriberFlags.Propagated) ReactiveFlags.Recursed |
) ReactiveFlags.Dirty |
) { ReactiveFlags.Pending)
sub.flags = subFlags | targetFlag )
shouldNotify = true ) {
} else if ( sub.flags = flags | ReactiveFlags.Pending
subFlags & SubscriberFlags.Recursed && } else if (
!(subFlags & SubscriberFlags.Tracking) !(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))
) { ) {
sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag flags = ReactiveFlags.None
shouldNotify = true } else if (!(flags & ReactiveFlags.RecursedCheck)) {
} else if ( sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending
!(subFlags & SubscriberFlags.Propagated) && } else if (
isValidLink(current, sub) !(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) &&
) { isValidLink(link, sub)
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag ) {
shouldNotify = (sub as Dependency).subs !== undefined sub.flags = flags | ReactiveFlags.Recursed | ReactiveFlags.Pending
} flags &= ReactiveFlags.Mutable
} else {
if (shouldNotify) { flags = ReactiveFlags.None
const subSubs = (sub as Dependency).subs
if (subSubs !== undefined) {
current = subSubs
if (subSubs.nextSub !== undefined) {
branchs = { target: next, linked: branchs }
++branchDepth
next = current.nextSub
}
targetFlag = SubscriberFlags.PendingComputed
continue
} }
if (subFlags & SubscriberFlags.Effect) {
if (flags & ReactiveFlags.Watching) {
notifyBuffer[notifyBufferLength++] = sub as Effect notifyBuffer[notifyBufferLength++] = sub as Effect
} }
} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
sub.flags = subFlags | targetFlag if (flags & ReactiveFlags.Mutable) {
} else if ( const subSubs = sub.subs
!(subFlags & targetFlag) && if (subSubs !== undefined) {
subFlags & SubscriberFlags.Propagated && link = subSubs
isValidLink(current, sub) if (subSubs.nextSub !== undefined) {
) { stack = { value: next, prev: stack }
sub.flags = subFlags | targetFlag next = link.nextSub
}
continue
}
}
} }
if ((current = next!) !== undefined) { if ((link = next!) !== undefined) {
next = current.nextSub next = link.nextSub
targetFlag = branchDepth
? SubscriberFlags.PendingComputed
: SubscriberFlags.Dirty
continue continue
} }
while (branchDepth--) { while (stack !== undefined) {
current = branchs!.target! link = stack.value!
branchs = branchs!.linked stack = stack.prev
if (current !== undefined) { if (link !== undefined) {
next = current.nextSub next = link.nextSub
targetFlag = branchDepth
? SubscriberFlags.PendingComputed
: SubscriberFlags.Dirty
continue top continue top
} }
} }
break break
} while (true) } while (true)
if (!batchDepth) {
processEffectNotifications()
}
} }
export function startTracking(sub: Subscriber): void { export function startTracking(sub: ReactiveNode): ReactiveNode | undefined {
sub.depsTail = undefined sub.depsTail = undefined
sub.flags = sub.flags =
(sub.flags & ~(SubscriberFlags.Recursed | SubscriberFlags.Propagated)) | (sub.flags &
SubscriberFlags.Tracking ~(ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending)) |
ReactiveFlags.RecursedCheck
return setActiveSub(sub)
} }
export function endTracking(sub: Subscriber): void { export function endTracking(
const depsTail = sub.depsTail sub: ReactiveNode,
if (depsTail !== undefined) { prevSub: ReactiveNode | undefined,
const nextDep = depsTail.nextDep
if (nextDep !== undefined) {
clearTracking(nextDep)
depsTail.nextDep = undefined
}
} else if (sub.deps !== undefined) {
clearTracking(sub.deps)
sub.deps = undefined
}
sub.flags &= ~SubscriberFlags.Tracking
}
export function updateDirtyFlag(
sub: Subscriber,
flags: SubscriberFlags,
): boolean {
if (checkDirty(sub.deps!)) {
sub.flags = flags | SubscriberFlags.Dirty
return true
} else {
sub.flags = flags & ~SubscriberFlags.PendingComputed
return false
}
}
export function processComputedUpdate(
computed: Computed,
flags: SubscriberFlags,
): void { ): void {
if (flags & SubscriberFlags.Dirty || checkDirty(computed.deps!)) { if (__DEV__ && activeSub !== sub) {
if (computed.update()) { warn(
const subs = computed.subs 'Active effect was not restored correctly - ' +
if (subs !== undefined) { 'this is likely a Vue internal bug.',
shallowPropagate(subs) )
}
}
} else {
computed.flags = flags & ~SubscriberFlags.PendingComputed
} }
activeSub = prevSub
const depsTail = sub.depsTail
let toRemove = depsTail !== undefined ? depsTail.nextDep : sub.deps
while (toRemove !== undefined) {
toRemove = unlink(toRemove, sub)
}
sub.flags &= ~ReactiveFlags.RecursedCheck
} }
export function processEffectNotifications(): void { export function flush(): void {
while (notifyIndex < notifyBufferLength) { while (notifyIndex < notifyBufferLength) {
const effect = notifyBuffer[notifyIndex]! const effect = notifyBuffer[notifyIndex]!
notifyBuffer[notifyIndex++] = undefined notifyBuffer[notifyIndex++] = undefined
@ -224,109 +259,71 @@ export function processEffectNotifications(): void {
notifyBufferLength = 0 notifyBufferLength = 0
} }
function linkNewDep( export function checkDirty(link: Link, sub: ReactiveNode): boolean {
dep: Dependency, let stack: Stack<Link> | undefined
sub: Subscriber,
nextDep: Link | undefined,
depsTail: Link | undefined,
): Link {
const newLink: Link = {
dep,
sub,
nextDep,
prevSub: undefined,
nextSub: undefined,
}
if (depsTail === undefined) {
sub.deps = newLink
} else {
depsTail.nextDep = newLink
}
if (dep.subs === undefined) {
dep.subs = newLink
} else {
const oldTail = dep.subsTail!
newLink.prevSub = oldTail
oldTail.nextSub = newLink
}
sub.depsTail = newLink
dep.subsTail = newLink
return newLink
}
function checkDirty(current: Link): boolean {
let prevLinks: OneWayLink<Link> | undefined
let checkDepth = 0 let checkDepth = 0
let dirty: boolean
top: do { top: do {
dirty = false const dep = link.dep
const dep = current.dep const depFlags = dep.flags
if (current.sub.flags & SubscriberFlags.Dirty) { let dirty = false
if (sub.flags & ReactiveFlags.Dirty) {
dirty = true dirty = true
} else if ('flags' in dep) { } else if (
const depFlags = dep.flags (depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
if ( (ReactiveFlags.Mutable | ReactiveFlags.Dirty)
(depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) === ) {
(SubscriberFlags.Computed | SubscriberFlags.Dirty) if ((dep as Computed).update()) {
) { const subs = dep.subs!
if ((dep as Computed).update()) { if (subs.nextSub !== undefined) {
const subs = dep.subs! shallowPropagate(subs)
if (subs.nextSub !== undefined) {
shallowPropagate(subs)
}
dirty = true
} }
} else if ( dirty = true
(depFlags &
(SubscriberFlags.Computed | SubscriberFlags.PendingComputed)) ===
(SubscriberFlags.Computed | SubscriberFlags.PendingComputed)
) {
if (current.nextSub !== undefined || current.prevSub !== undefined) {
prevLinks = { target: current, linked: prevLinks }
}
current = dep.deps!
++checkDepth
continue
} }
} else if (
(depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Pending)) ===
(ReactiveFlags.Mutable | ReactiveFlags.Pending)
) {
if (link.nextSub !== undefined || link.prevSub !== undefined) {
stack = { value: link, prev: stack }
}
link = dep.deps!
sub = dep
++checkDepth
continue
} }
if (!dirty && current.nextDep !== undefined) { if (!dirty && link.nextDep !== undefined) {
current = current.nextDep link = link.nextDep
continue continue
} }
while (checkDepth) { while (checkDepth) {
--checkDepth --checkDepth
const sub = current.sub as Computed
const firstSub = sub.subs! const firstSub = sub.subs!
const hasMultipleSubs = firstSub.nextSub !== undefined
if (hasMultipleSubs) {
link = stack!.value
stack = stack!.prev
} else {
link = firstSub
}
if (dirty) { if (dirty) {
if (sub.update()) { if ((sub as Computed).update()) {
if (firstSub.nextSub !== undefined) { if (hasMultipleSubs) {
current = prevLinks!.target
prevLinks = prevLinks!.linked
shallowPropagate(firstSub) shallowPropagate(firstSub)
} else {
current = firstSub
} }
sub = link.sub
continue continue
} }
} else { } else {
sub.flags &= ~SubscriberFlags.PendingComputed sub.flags &= ~ReactiveFlags.Pending
} }
if (firstSub.nextSub !== undefined) { sub = link.sub
current = prevLinks!.target if (link.nextDep !== undefined) {
prevLinks = prevLinks!.linked link = link.nextDep
} else {
current = firstSub
}
if (current.nextDep !== undefined) {
current = current.nextDep
continue top continue top
} }
dirty = false dirty = false
@ -336,21 +333,22 @@ function checkDirty(current: Link): boolean {
} while (true) } while (true)
} }
function shallowPropagate(link: Link): void { export function shallowPropagate(link: Link): void {
do { do {
const sub = link.sub const sub = link.sub
const nextSub = link.nextSub
const subFlags = sub.flags const subFlags = sub.flags
if ( if (
(subFlags & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) === (subFlags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) ===
SubscriberFlags.PendingComputed ReactiveFlags.Pending
) { ) {
sub.flags = subFlags | SubscriberFlags.Dirty sub.flags = subFlags | ReactiveFlags.Dirty
} }
link = link.nextSub! link = nextSub!
} while (link !== undefined) } while (link !== undefined)
} }
function isValidLink(checkLink: Link, sub: Subscriber): boolean { function isValidLink(checkLink: Link, sub: ReactiveNode): boolean {
const depsTail = sub.depsTail const depsTail = sub.depsTail
if (depsTail !== undefined) { if (depsTail !== undefined) {
let link = sub.deps! let link = sub.deps!
@ -366,40 +364,3 @@ function isValidLink(checkLink: Link, sub: Subscriber): boolean {
} }
return false return false
} }
function clearTracking(link: Link): void {
do {
const dep = link.dep
const nextDep = link.nextDep
const nextSub = link.nextSub
const prevSub = link.prevSub
if (nextSub !== undefined) {
nextSub.prevSub = prevSub
} else {
dep.subsTail = prevSub
}
if (prevSub !== undefined) {
prevSub.nextSub = nextSub
} else {
dep.subs = nextSub
}
if (dep.subs === undefined && 'deps' in dep) {
const depFlags = dep.flags
if (!(depFlags & SubscriberFlags.Dirty)) {
dep.flags = depFlags | SubscriberFlags.Dirty
}
const depDeps = dep.deps
if (depDeps !== undefined) {
link = depDeps
dep.depsTail!.nextDep = nextDep
dep.deps = undefined
dep.depsTail = undefined
continue
}
}
link = nextDep!
} while (link !== undefined)
}

View File

@ -8,20 +8,13 @@ import {
isObject, isObject,
isPlainObject, isPlainObject,
isSet, isSet,
remove,
} from '@vue/shared' } from '@vue/shared'
import type { ComputedRef } from './computed' import type { ComputedRef } from './computed'
import { ReactiveFlags } from './constants' import { ReactiveFlags } from './constants'
import { import { type DebuggerOptions, ReactiveEffect, cleanup } from './effect'
type DebuggerOptions,
type EffectScheduler,
ReactiveEffect,
pauseTracking,
resetTracking,
} from './effect'
import { getCurrentScope } from './effectScope'
import { isReactive, isShallow } from './reactive' import { isReactive, isShallow } from './reactive'
import { type Ref, isRef } from './ref' import { type Ref, isRef } from './ref'
import { setActiveSub } from './system'
import { warn } from './warning' import { warn } from './warning'
// These errors were transferred from `packages/runtime-core/src/errorHandling.ts` // These errors were transferred from `packages/runtime-core/src/errorHandling.ts`
@ -49,12 +42,7 @@ export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
immediate?: Immediate immediate?: Immediate
deep?: boolean | number deep?: boolean | number
once?: boolean once?: boolean
scheduler?: WatchScheduler
onWarn?: (msg: string, ...args: any[]) => void onWarn?: (msg: string, ...args: any[]) => void
/**
* @internal
*/
augmentJob?: (job: (...args: any[]) => void) => void
/** /**
* @internal * @internal
*/ */
@ -76,10 +64,7 @@ export interface WatchHandle extends WatchStopHandle {
// initial value for watchers to trigger on undefined initial values // initial value for watchers to trigger on undefined initial values
const INITIAL_WATCHER_VALUE = {} const INITIAL_WATCHER_VALUE = {}
export type WatchScheduler = (job: () => void, isFirstRun: boolean) => void let activeWatcher: WatcherEffect | undefined = undefined
const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
let activeWatcher: ReactiveEffect | undefined = undefined
/** /**
* Returns the current active effect if there is one. * Returns the current active effect if there is one.
@ -102,12 +87,16 @@ export function getCurrentWatcher(): ReactiveEffect<any> | undefined {
export function onWatcherCleanup( export function onWatcherCleanup(
cleanupFn: () => void, cleanupFn: () => void,
failSilently = false, failSilently = false,
owner: ReactiveEffect | undefined = activeWatcher, owner: WatcherEffect | undefined = activeWatcher,
): void { ): void {
if (owner) { if (owner) {
let cleanups = cleanupMap.get(owner) const { call } = owner.options
if (!cleanups) cleanupMap.set(owner, (cleanups = [])) if (call) {
cleanups.push(cleanupFn) owner.cleanups[owner.cleanupsLength++] = () =>
call(cleanupFn, WatchErrorCodes.WATCH_CLEANUP)
} else {
owner.cleanups[owner.cleanupsLength++] = cleanupFn
}
} else if (__DEV__ && !failSilently) { } else if (__DEV__ && !failSilently) {
warn( warn(
`onWatcherCleanup() was called when there was no active watcher` + `onWatcherCleanup() was called when there was no active watcher` +
@ -116,212 +105,187 @@ export function onWatcherCleanup(
} }
} }
export class WatcherEffect extends ReactiveEffect {
forceTrigger: boolean
isMultiSource: boolean
oldValue: any
boundCleanup: typeof onWatcherCleanup = fn =>
onWatcherCleanup(fn, false, this)
constructor(
source: WatchSource | WatchSource[] | WatchEffect | object,
public cb?: WatchCallback<any, any> | null | undefined,
public options: WatchOptions = EMPTY_OBJ,
) {
const { deep, once, call, onWarn } = options
let getter: () => any
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => reactiveGetter(source, deep)
forceTrigger = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return reactiveGetter(s, deep)
} else if (isFunction(s)) {
return call ? call(s, WatchErrorCodes.WATCH_GETTER) : s()
} else {
__DEV__ && warnInvalidSource(s, onWarn)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = call
? () => call(source, WatchErrorCodes.WATCH_GETTER)
: (source as () => any)
} else {
// no cb -> simple effect
getter = () => {
if (this.cleanupsLength) {
const prevSub = setActiveSub()
try {
cleanup(this)
} finally {
setActiveSub(prevSub)
}
}
const currentEffect = activeWatcher
activeWatcher = this
try {
return call
? call(source, WatchErrorCodes.WATCH_CALLBACK, [
this.boundCleanup,
])
: source(this.boundCleanup)
} finally {
activeWatcher = currentEffect
}
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source, onWarn)
}
if (cb && deep) {
const baseGetter = getter
const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
}
super(getter)
this.forceTrigger = forceTrigger
this.isMultiSource = isMultiSource
if (once && cb) {
const _cb = cb
cb = (...args) => {
_cb(...args)
this.stop()
}
}
this.cb = cb
this.oldValue = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
if (__DEV__) {
this.onTrack = options.onTrack
this.onTrigger = options.onTrigger
}
}
run(initialRun = false): void {
const oldValue = this.oldValue
const newValue = (this.oldValue = super.run())
if (!this.cb) {
return
}
const { immediate, deep, call } = this.options
if (initialRun && !immediate) {
return
}
if (
deep ||
this.forceTrigger ||
(this.isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
: hasChanged(newValue, oldValue))
) {
// cleanup before running cb again
cleanup(this)
const currentWatcher = activeWatcher
activeWatcher = this
try {
const args = [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? undefined
: this.isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
this.boundCleanup,
]
call
? call(this.cb, WatchErrorCodes.WATCH_CALLBACK, args)
: // @ts-expect-error
this.cb(...args)
} finally {
activeWatcher = currentWatcher
}
}
}
}
function reactiveGetter(source: object, deep: WatchOptions['deep']): unknown {
// traverse will happen in wrapped getter below
if (deep) return source
// for `deep: false | 0` or shallow reactive, only traverse root-level properties
if (isShallow(source) || deep === false || deep === 0)
return traverse(source, 1)
// for `deep: undefined` on a reactive object, deeply traverse all properties
return traverse(source)
}
function warnInvalidSource(s: object, onWarn: WatchOptions['onWarn']): void {
;(onWarn || warn)(
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, ` +
`a reactive object, or an array of these types.`,
)
}
export function watch( export function watch(
source: WatchSource | WatchSource[] | WatchEffect | object, source: WatchSource | WatchSource[] | WatchEffect | object,
cb?: WatchCallback | null, cb?: WatchCallback | null,
options: WatchOptions = EMPTY_OBJ, options: WatchOptions = EMPTY_OBJ,
): WatchHandle { ): WatchHandle {
const { immediate, deep, once, scheduler, augmentJob, call } = options const effect = new WatcherEffect(source, cb, options)
const warnInvalidSource = (s: unknown) => { effect.run(true)
;(options.onWarn || warn)(
`Invalid watch source: `,
s,
`A watch source can only be a getter/effect function, a ref, ` +
`a reactive object, or an array of these types.`,
)
}
const reactiveGetter = (source: object) => { const stop = effect.stop.bind(effect) as WatchHandle
// traverse will happen in wrapped getter below stop.pause = effect.pause.bind(effect)
if (deep) return source stop.resume = effect.resume.bind(effect)
// for `deep: false | 0` or shallow reactive, only traverse root-level properties stop.stop = stop
if (isShallow(source) || deep === false || deep === 0)
return traverse(source, 1)
// for `deep: undefined` on a reactive object, deeply traverse all properties
return traverse(source)
}
let effect: ReactiveEffect return stop
let getter: () => any
let cleanup: (() => void) | undefined
let boundCleanup: typeof onWatcherCleanup
let forceTrigger = false
let isMultiSource = false
if (isRef(source)) {
getter = () => source.value
forceTrigger = isShallow(source)
} else if (isReactive(source)) {
getter = () => reactiveGetter(source)
forceTrigger = true
} else if (isArray(source)) {
isMultiSource = true
forceTrigger = source.some(s => isReactive(s) || isShallow(s))
getter = () =>
source.map(s => {
if (isRef(s)) {
return s.value
} else if (isReactive(s)) {
return reactiveGetter(s)
} else if (isFunction(s)) {
return call ? call(s, WatchErrorCodes.WATCH_GETTER) : s()
} else {
__DEV__ && warnInvalidSource(s)
}
})
} else if (isFunction(source)) {
if (cb) {
// getter with cb
getter = call
? () => call(source, WatchErrorCodes.WATCH_GETTER)
: (source as () => any)
} else {
// no cb -> simple effect
getter = () => {
if (cleanup) {
pauseTracking()
try {
cleanup()
} finally {
resetTracking()
}
}
const currentEffect = activeWatcher
activeWatcher = effect
try {
return call
? call(source, WatchErrorCodes.WATCH_CALLBACK, [boundCleanup])
: source(boundCleanup)
} finally {
activeWatcher = currentEffect
}
}
}
} else {
getter = NOOP
__DEV__ && warnInvalidSource(source)
}
if (cb && deep) {
const baseGetter = getter
const depth = deep === true ? Infinity : deep
getter = () => traverse(baseGetter(), depth)
}
const scope = getCurrentScope()
const watchHandle: WatchHandle = () => {
effect.stop()
if (scope && scope.active) {
remove(scope.effects, effect)
}
}
if (once && cb) {
const _cb = cb
cb = (...args) => {
_cb(...args)
watchHandle()
}
}
let oldValue: any = isMultiSource
? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE)
: INITIAL_WATCHER_VALUE
const job = (immediateFirstRun?: boolean) => {
if (!effect.active || (!immediateFirstRun && !effect.dirty)) {
return
}
if (cb) {
// watch(source, cb)
const newValue = effect.run()
if (
deep ||
forceTrigger ||
(isMultiSource
? (newValue as any[]).some((v, i) => hasChanged(v, oldValue[i]))
: hasChanged(newValue, oldValue))
) {
// cleanup before running cb again
if (cleanup) {
cleanup()
}
const currentWatcher = activeWatcher
activeWatcher = effect
try {
const args = [
newValue,
// pass undefined as the old value when it's changed for the first time
oldValue === INITIAL_WATCHER_VALUE
? undefined
: isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE
? []
: oldValue,
boundCleanup,
]
call
? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
: // @ts-expect-error
cb!(...args)
oldValue = newValue
} finally {
activeWatcher = currentWatcher
}
}
} else {
// watchEffect
effect.run()
}
}
if (augmentJob) {
augmentJob(job)
}
effect = new ReactiveEffect(getter)
effect.scheduler = scheduler
? () => scheduler(job, false)
: (job as EffectScheduler)
boundCleanup = fn => onWatcherCleanup(fn, false, effect)
cleanup = effect.onStop = () => {
const cleanups = cleanupMap.get(effect)
if (cleanups) {
if (call) {
call(cleanups, WatchErrorCodes.WATCH_CLEANUP)
} else {
for (const cleanup of cleanups) cleanup()
}
cleanupMap.delete(effect)
}
}
if (__DEV__) {
effect.onTrack = options.onTrack
effect.onTrigger = options.onTrigger
}
// initial run
if (cb) {
if (immediate) {
job(true)
} else {
oldValue = effect.run()
}
} else if (scheduler) {
scheduler(job.bind(null, true), true)
} else {
effect.run()
}
watchHandle.pause = effect.pause.bind(effect)
watchHandle.resume = effect.resume.bind(effect)
watchHandle.stop = watchHandle
return watchHandle
} }
export function traverse( export function traverse(

View File

@ -25,7 +25,9 @@ import {
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { import {
type DebuggerEvent, type DebuggerEvent,
type EffectScope,
ITERATE_KEY, ITERATE_KEY,
ReactiveEffect,
type Ref, type Ref,
type ShallowRef, type ShallowRef,
TrackOpTypes, TrackOpTypes,
@ -503,6 +505,52 @@ describe('api: watch', () => {
expect(cleanupWatch).toHaveBeenCalledTimes(2) expect(cleanupWatch).toHaveBeenCalledTimes(2)
}) })
it('nested calls to baseWatch and onWatcherCleanup', async () => {
let calls: string[] = []
let source: Ref<number>
let copyist: Ref<number>
const scope = effectScope()
scope.run(() => {
source = ref(0)
copyist = ref(0)
// sync flush
watchEffect(
() => {
const current = (copyist.value = source.value)
onWatcherCleanup(() => calls.push(`sync ${current}`))
},
{ flush: 'sync' },
)
// post flush
watchEffect(
() => {
const current = copyist.value
onWatcherCleanup(() => calls.push(`post ${current}`))
},
{ flush: 'post' },
)
})
await nextTick()
expect(calls).toEqual([])
scope.run(() => source.value++)
expect(calls).toEqual(['sync 0'])
await nextTick()
expect(calls).toEqual(['sync 0', 'post 0'])
calls.length = 0
scope.run(() => source.value++)
expect(calls).toEqual(['sync 1'])
await nextTick()
expect(calls).toEqual(['sync 1', 'post 1'])
calls.length = 0
scope.stop()
expect(calls).toEqual(['sync 2', 'post 2'])
})
it('flush timing: pre (default)', async () => { it('flush timing: pre (default)', async () => {
const count = ref(0) const count = ref(0)
const count2 = ref(0) const count2 = ref(0)
@ -1332,16 +1380,15 @@ describe('api: watch', () => {
render(h(Comp), nodeOps.createElement('div')) render(h(Comp), nodeOps.createElement('div'))
expect(instance!).toBeDefined() expect(instance!).toBeDefined()
expect(instance!.scope.effects).toBeInstanceOf(Array)
// includes the component's own render effect AND the watcher effect // includes the component's own render effect AND the watcher effect
expect(instance!.scope.effects.length).toBe(2) expect(getEffectsCount(instance!.scope)).toBe(2)
_show!.value = false _show!.value = false
await nextTick() await nextTick()
await nextTick() await nextTick()
expect(instance!.scope.effects.length).toBe(0) expect(getEffectsCount(instance!.scope)).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 ', () => {
@ -1489,7 +1536,7 @@ describe('api: watch', () => {
createApp(Comp).mount(root) createApp(Comp).mount(root)
// should not record watcher in detached scope and only the instance's // should not record watcher in detached scope and only the instance's
// own update effect // own update effect
expect(instance!.scope.effects.length).toBe(1) expect(getEffectsCount(instance!.scope)).toBe(1)
}) })
test('watchEffect should keep running if created in a detached scope', async () => { test('watchEffect should keep running if created in a detached scope', async () => {
@ -1595,7 +1642,7 @@ describe('api: watch', () => {
num.value++ num.value++
await nextTick() await nextTick()
// would not be calld when value>1 // would not be called when value>1
expect(spy1).toHaveBeenCalledTimes(1) expect(spy1).toHaveBeenCalledTimes(1)
expect(spy2).toHaveBeenCalledTimes(1) expect(spy2).toHaveBeenCalledTimes(1)
}) })
@ -1796,9 +1843,9 @@ describe('api: watch', () => {
} }
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
createApp(Comp).mount(root) createApp(Comp).mount(root)
expect(instance!.scope.effects.length).toBe(2) expect(getEffectsCount(instance!.scope)).toBe(2)
unwatch!() unwatch!()
expect(instance!.scope.effects.length).toBe(1) expect(getEffectsCount(instance!.scope)).toBe(1)
const scope = effectScope() const scope = effectScope()
scope.run(() => { scope.run(() => {
@ -1806,14 +1853,14 @@ describe('api: watch', () => {
console.log(num.value) console.log(num.value)
}) })
}) })
expect(scope.effects.length).toBe(1) expect(getEffectsCount(scope)).toBe(1)
unwatch!() unwatch!()
expect(scope.effects.length).toBe(0) expect(getEffectsCount(scope)).toBe(0)
scope.run(() => { scope.run(() => {
watch(num, () => {}, { once: true, immediate: true }) watch(num, () => {}, { once: true, immediate: true })
}) })
expect(scope.effects.length).toBe(0) expect(getEffectsCount(scope)).toBe(0)
}) })
// simplified case of VueUse syncRef // simplified case of VueUse syncRef
@ -1874,7 +1921,7 @@ describe('api: watch', () => {
expect(foo.value.a).toBe(2) expect(foo.value.a).toBe(2)
}) })
test('watch immediate error in effect scope should be catched by onErrorCaptured', async () => { test('watch immediate error in effect scope should be caught by onErrorCaptured', async () => {
const warn = vi.spyOn(console, 'warn') const warn = vi.spyOn(console, 'warn')
warn.mockImplementation(() => {}) warn.mockImplementation(() => {})
const ERROR_IN_SCOPE = 'ERROR_IN_SCOPE' const ERROR_IN_SCOPE = 'ERROR_IN_SCOPE'
@ -2011,3 +2058,13 @@ describe('api: watch', () => {
expect(onCleanup).toBeCalledTimes(0) expect(onCleanup).toBeCalledTimes(0)
}) })
}) })
function getEffectsCount(scope: EffectScope): number {
let n = 0
for (let dep = scope.deps; dep !== undefined; dep = dep.nextDep) {
if (dep.dep instanceof ReactiveEffect) {
n++
}
}
return n
}

View File

@ -6,6 +6,7 @@ import {
nodeOps, nodeOps,
ref, ref,
render, render,
useSlots,
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { createBlock, normalizeVNode } from '../src/vnode' import { createBlock, normalizeVNode } from '../src/vnode'
import { createSlots } from '../src/helpers/createSlots' import { createSlots } from '../src/helpers/createSlots'
@ -42,6 +43,29 @@ describe('component: slots', () => {
expect(slots).toMatchObject({}) expect(slots).toMatchObject({})
}) })
test('initSlots: ensure compiler marker non-enumerable', () => {
const Comp = {
render() {
const slots = useSlots()
// Only user-defined slots should be enumerable
expect(Object.keys(slots)).toEqual(['foo'])
// Internal compiler markers must still exist but be non-enumerable
expect(slots).toHaveProperty('_')
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
false,
)
expect(slots).toHaveProperty('__')
expect(Object.getOwnPropertyDescriptor(slots, '__')!.enumerable).toBe(
false,
)
return h('div')
},
}
const slots = { foo: () => {}, _: 1, __: [1] }
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
})
test('initSlots: should normalize object slots (when value is null, string, array)', () => { test('initSlots: should normalize object slots (when value is null, string, array)', () => {
const { slots } = renderWithSlots({ const { slots } = renderWithSlots({
_inner: '_inner', _inner: '_inner',

View File

@ -16,6 +16,7 @@ import {
render, render,
serialize, serialize,
serializeInner, serializeInner,
useModel,
withDirectives, withDirectives,
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { import {
@ -144,6 +145,62 @@ describe('renderer: teleport', () => {
`"<!--teleport start--><!--teleport end--><div>Footer</div><div id="targetId"><div>bar</div></div>"`, `"<!--teleport start--><!--teleport end--><div>Footer</div><div id="targetId"><div>bar</div></div>"`,
) )
}) })
// #13349
test('handle deferred teleport updates before and after mount', async () => {
const root = document.createElement('div')
document.body.appendChild(root)
const show = ref(false)
const data2 = ref('2')
const data3 = ref('3')
const Comp = {
props: {
modelValue: {},
modelModifiers: {},
},
emits: ['update:modelValue'],
setup(props: any) {
const data2 = useModel(props, 'modelValue')
data2.value = '2+'
return () => h('span')
},
}
createDOMApp({
setup() {
setTimeout(() => (show.value = true), 5)
setTimeout(() => (data3.value = '3+'), 10)
},
render() {
return h(Fragment, null, [
h('span', { id: 'targetId001' }),
show.value
? h(Fragment, null, [
h(Teleport, { to: '#targetId001', defer: true }, [
createTextVNode(String(data3.value)),
]),
h(Comp, {
modelValue: data2.value,
'onUpdate:modelValue': (event: any) =>
(data2.value = event),
}),
])
: createCommentVNode('v-if'),
])
},
}).mount(root)
expect(root.innerHTML).toMatchInlineSnapshot(
`"<span id="targetId001"></span><!--v-if-->"`,
)
await new Promise(r => setTimeout(r, 10))
expect(root.innerHTML).toMatchInlineSnapshot(
`"<span id="targetId001">3+</span><!--teleport start--><!--teleport end--><span></span>"`,
)
})
}) })
function runSharedTests(deferMode: boolean) { function runSharedTests(deferMode: boolean) {

View File

@ -1654,6 +1654,29 @@ describe('SSR hydration', () => {
expect(`mismatch`).not.toHaveBeenWarned() expect(`mismatch`).not.toHaveBeenWarned()
}) })
test('transition appear work with pre-existing class', () => {
const { vnode, container } = mountWithHydration(
`<template><div class="foo">foo</div></template>`,
() =>
h(
Transition,
{ appear: true },
{
default: () => h('div', { class: 'foo' }, 'foo'),
},
),
)
expect(container.firstChild).toMatchInlineSnapshot(`
<div
class="foo v-enter-from v-enter-active"
>
foo
</div>
`)
expect(vnode.el).toBe(container.firstChild)
expect(`mismatch`).not.toHaveBeenWarned()
})
test('transition appear with v-if', () => { test('transition appear with v-if', () => {
const show = false const show = false
const { vnode, container } = mountWithHydration( const { vnode, container } = mountWithHydration(

View File

@ -573,7 +573,7 @@ describe('attribute fallthrough', () => {
const Child = { const Child = {
props: [], props: [],
render() { render() {
return openBlock(), createBlock('div') return (openBlock(), createBlock('div'))
}, },
} }

View File

@ -416,7 +416,7 @@ describe('renderer: fragment', () => {
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
const renderFn = () => { const renderFn = () => {
return openBlock(true), createBlock(Fragment, null) return (openBlock(true), createBlock(Fragment, null))
} }
render(renderFn(), root) render(renderFn(), root)

View File

@ -861,6 +861,114 @@ describe('renderer: optimized mode', () => {
expect(inner(root)).toBe('<div><div>true</div></div>') expect(inner(root)).toBe('<div><div>true</div></div>')
}) })
// #13305
test('patch Suspense nested in list nodes in optimized mode', async () => {
const deps: Promise<any>[] = []
const Item = {
props: {
someId: { type: Number, required: true },
},
async setup(props: any) {
const p = new Promise(resolve => setTimeout(resolve, 1))
deps.push(p)
await p
return () => (
openBlock(),
createElementBlock('li', null, [
createElementVNode(
'p',
null,
String(props.someId),
PatchFlags.TEXT,
),
])
)
},
}
const list = ref([1, 2, 3])
const App = {
setup() {
return () => (
openBlock(),
createElementBlock(
Fragment,
null,
[
createElementVNode(
'p',
null,
JSON.stringify(list.value),
PatchFlags.TEXT,
),
createElementVNode('ol', null, [
(openBlock(),
createBlock(SuspenseImpl, null, {
fallback: withCtx(() => [
createElementVNode('li', null, 'Loading…'),
]),
default: withCtx(() => [
(openBlock(true),
createElementBlock(
Fragment,
null,
renderList(list.value, id => {
return (
openBlock(),
createBlock(
Item,
{
key: id,
'some-id': id,
},
null,
PatchFlags.PROPS,
['some-id'],
)
)
}),
PatchFlags.KEYED_FRAGMENT,
)),
]),
_: 1 /* STABLE */,
})),
]),
],
PatchFlags.STABLE_FRAGMENT,
)
)
},
}
const app = createApp(App)
app.mount(root)
expect(inner(root)).toBe(`<p>[1,2,3]</p>` + `<ol><li>Loading…</li></ol>`)
await Promise.all(deps)
await nextTick()
expect(inner(root)).toBe(
`<p>[1,2,3]</p>` +
`<ol>` +
`<li><p>1</p></li>` +
`<li><p>2</p></li>` +
`<li><p>3</p></li>` +
`</ol>`,
)
list.value = [3, 1, 2]
await nextTick()
expect(inner(root)).toBe(
`<p>[3,1,2]</p>` +
`<ol>` +
`<li><p>3</p></li>` +
`<li><p>1</p></li>` +
`<li><p>2</p></li>` +
`</ol>`,
)
})
// #4183 // #4183
test('should not take unmount children fast path /w Suspense', async () => { test('should not take unmount children fast path /w Suspense', async () => {
const show = ref(true) const show = ref(true)

View File

@ -179,6 +179,37 @@ describe('api: template refs', () => {
expect(el.value).toBe(null) expect(el.value).toBe(null)
}) })
it('unset old ref when new ref is absent', async () => {
const root1 = nodeOps.createElement('div')
const root2 = nodeOps.createElement('div')
const el1 = ref(null)
const el2 = ref(null)
const toggle = ref(true)
const Comp1 = {
setup() {
return () => (toggle.value ? h('div', { ref: el1 }) : h('div'))
},
}
const Comp2 = {
setup() {
return () => h('div', { ref: toggle.value ? el2 : undefined })
},
}
render(h(Comp1), root1)
render(h(Comp2), root2)
expect(el1.value).toBe(root1.children[0])
expect(el2.value).toBe(root2.children[0])
toggle.value = false
await nextTick()
expect(el1.value).toBe(null)
expect(el2.value).toBe(null)
})
test('string ref inside slots', async () => { test('string ref inside slots', async () => {
const root = nodeOps.createElement('div') const root = nodeOps.createElement('div')
const spy = vi.fn() const spy = vi.fn()

View File

@ -49,8 +49,8 @@ describe('scheduler', () => {
const job1 = () => { const job1 = () => {
calls.push('job1') calls.push('job1')
queueJob(job2) queueJob(job2, 10)
queueJob(job3) queueJob(job3, 1)
} }
const job2 = () => { const job2 = () => {
@ -58,12 +58,10 @@ describe('scheduler', () => {
queueJob(job4) queueJob(job4)
queueJob(job5) queueJob(job5)
} }
job2.id = 10
const job3 = () => { const job3 = () => {
calls.push('job3') calls.push('job3')
} }
job3.id = 1
const job4 = () => { const job4 = () => {
calls.push('job4') calls.push('job4')
@ -125,9 +123,8 @@ describe('scheduler', () => {
calls.push('cb1') calls.push('cb1')
queueJob(job1) queueJob(job1)
} }
cb1.flags! |= SchedulerJobFlags.PRE
queueJob(cb1) queueJob(cb1, undefined, true)
await nextTick() await nextTick()
expect(calls).toEqual(['cb1', 'job1']) expect(calls).toEqual(['cb1', 'job1'])
}) })
@ -137,30 +134,23 @@ describe('scheduler', () => {
const job1 = () => { const job1 = () => {
calls.push('job1') calls.push('job1')
} }
job1.id = 1
const cb1: SchedulerJob = () => { const cb1: SchedulerJob = () => {
calls.push('cb1') calls.push('cb1')
queueJob(job1) queueJob(job1, 1)
// cb2 should execute before the job // cb2 should execute before the job
queueJob(cb2) queueJob(cb2, 1, true)
queueJob(cb3) queueJob(cb3, 1, true)
} }
cb1.flags! |= SchedulerJobFlags.PRE
const cb2: SchedulerJob = () => { const cb2: SchedulerJob = () => {
calls.push('cb2') calls.push('cb2')
} }
cb2.flags! |= SchedulerJobFlags.PRE
cb2.id = 1
const cb3: SchedulerJob = () => { const cb3: SchedulerJob = () => {
calls.push('cb3') calls.push('cb3')
} }
cb3.flags! |= SchedulerJobFlags.PRE
cb3.id = 1
queueJob(cb1) queueJob(cb1, undefined, true)
await nextTick() await nextTick()
expect(calls).toEqual(['cb1', 'cb2', 'cb3', 'job1']) expect(calls).toEqual(['cb1', 'cb2', 'cb3', 'job1'])
}) })
@ -170,41 +160,30 @@ describe('scheduler', () => {
const job1: SchedulerJob = () => { const job1: SchedulerJob = () => {
calls.push('job1') calls.push('job1')
} }
job1.id = 1
job1.flags! |= SchedulerJobFlags.PRE
const job2: SchedulerJob = () => { const job2: SchedulerJob = () => {
calls.push('job2') calls.push('job2')
queueJob(job5) queueJob(job5, 2)
queueJob(job6) queueJob(job6, 2, true)
} }
job2.id = 2
job2.flags! |= SchedulerJobFlags.PRE
const job3: SchedulerJob = () => { const job3: SchedulerJob = () => {
calls.push('job3') calls.push('job3')
} }
job3.id = 2
job3.flags! |= SchedulerJobFlags.PRE
const job4: SchedulerJob = () => { const job4: SchedulerJob = () => {
calls.push('job4') calls.push('job4')
} }
job4.id = 3
job4.flags! |= SchedulerJobFlags.PRE
const job5: SchedulerJob = () => { const job5: SchedulerJob = () => {
calls.push('job5') calls.push('job5')
} }
job5.id = 2
const job6: SchedulerJob = () => { const job6: SchedulerJob = () => {
calls.push('job6') calls.push('job6')
} }
job6.id = 2
job6.flags! |= SchedulerJobFlags.PRE
// We need several jobs to test this properly, otherwise // We need several jobs to test this properly, otherwise
// findInsertionIndex can yield the correct index by chance // findInsertionIndex can yield the correct index by chance
queueJob(job4) queueJob(job4, 3, true)
queueJob(job2) queueJob(job2, 2, true)
queueJob(job3) queueJob(job3, 2, true)
queueJob(job1) queueJob(job1, 1, true)
await nextTick() await nextTick()
expect(calls).toEqual(['job1', 'job2', 'job3', 'job6', 'job5', 'job4']) expect(calls).toEqual(['job1', 'job2', 'job3', 'job6', 'job5', 'job4'])
@ -217,8 +196,8 @@ describe('scheduler', () => {
// when updating the props of a child component. This is handled // when updating the props of a child component. This is handled
// directly inside `updateComponentPreRender` to avoid non atomic // directly inside `updateComponentPreRender` to avoid non atomic
// cb triggers (#1763) // cb triggers (#1763)
queueJob(cb1) queueJob(cb1, undefined, true)
queueJob(cb2) queueJob(cb2, undefined, true)
flushPreFlushCbs() flushPreFlushCbs()
calls.push('job1') calls.push('job1')
} }
@ -227,11 +206,9 @@ describe('scheduler', () => {
// a cb triggers its parent job, which should be skipped // a cb triggers its parent job, which should be skipped
queueJob(job1) queueJob(job1)
} }
cb1.flags! |= SchedulerJobFlags.PRE
const cb2: SchedulerJob = () => { const cb2: SchedulerJob = () => {
calls.push('cb2') calls.push('cb2')
} }
cb2.flags! |= SchedulerJobFlags.PRE
queueJob(job1) queueJob(job1)
await nextTick() await nextTick()
@ -242,29 +219,24 @@ describe('scheduler', () => {
const calls: string[] = [] const calls: string[] = []
const job1: SchedulerJob = () => { const job1: SchedulerJob = () => {
calls.push('job1') calls.push('job1')
queueJob(job3) queueJob(job3, undefined, true)
queueJob(job4) queueJob(job4, undefined, true)
} }
// job1 has no id // job1 has no id
job1.flags! |= SchedulerJobFlags.PRE
const job2: SchedulerJob = () => { const job2: SchedulerJob = () => {
calls.push('job2') calls.push('job2')
} }
job2.id = 1
job2.flags! |= SchedulerJobFlags.PRE
const job3: SchedulerJob = () => { const job3: SchedulerJob = () => {
calls.push('job3') calls.push('job3')
} }
// job3 has no id // job3 has no id
job3.flags! |= SchedulerJobFlags.PRE
const job4: SchedulerJob = () => { const job4: SchedulerJob = () => {
calls.push('job4') calls.push('job4')
} }
// job4 has no id // job4 has no id
job4.flags! |= SchedulerJobFlags.PRE
queueJob(job1) queueJob(job1, undefined, true)
queueJob(job2) queueJob(job2, 1, true)
await nextTick() await nextTick()
expect(calls).toEqual(['job1', 'job3', 'job4', 'job2']) expect(calls).toEqual(['job1', 'job3', 'job4', 'job2'])
}) })
@ -273,9 +245,8 @@ describe('scheduler', () => {
it('queue preFlushCb inside postFlushCb', async () => { it('queue preFlushCb inside postFlushCb', async () => {
const spy = vi.fn() const spy = vi.fn()
const cb: SchedulerJob = () => spy() const cb: SchedulerJob = () => spy()
cb.flags! |= SchedulerJobFlags.PRE
queuePostFlushCb(() => { queuePostFlushCb(() => {
queueJob(cb) queueJob(cb, undefined, true)
}) })
await nextTick() await nextTick()
expect(spy).toHaveBeenCalled() expect(spy).toHaveBeenCalled()
@ -448,16 +419,13 @@ describe('scheduler', () => {
const job1: SchedulerJob = () => { const job1: SchedulerJob = () => {
calls.push('job1') calls.push('job1')
} }
job1.id = 1
const job2: SchedulerJob = () => { const job2: SchedulerJob = () => {
calls.push('job2') calls.push('job2')
} }
job2.id = 2
queuePostFlushCb(() => { queuePostFlushCb(() => {
queueJob(job2) queueJob(job2, 2)
queueJob(job1) queueJob(job1, 1)
}) })
await nextTick() await nextTick()
@ -471,21 +439,16 @@ describe('scheduler', () => {
const job1 = () => calls.push('job1') const job1 = () => calls.push('job1')
// job1 has no id // job1 has no id
const job2 = () => calls.push('job2') const job2 = () => calls.push('job2')
job2.id = 2
const job3 = () => calls.push('job3') const job3 = () => calls.push('job3')
job3.id = 1
const job4: SchedulerJob = () => calls.push('job4') const job4: SchedulerJob = () => calls.push('job4')
job4.id = 2
job4.flags! |= SchedulerJobFlags.PRE
const job5: SchedulerJob = () => calls.push('job5') const job5: SchedulerJob = () => calls.push('job5')
// job5 has no id // job5 has no id
job5.flags! |= SchedulerJobFlags.PRE
queueJob(job1) queueJob(job1)
queueJob(job2) queueJob(job2, 2)
queueJob(job3) queueJob(job3, 1)
queueJob(job4) queueJob(job4, 2, true)
queueJob(job5) queueJob(job5, undefined, true)
await nextTick() await nextTick()
expect(calls).toEqual(['job5', 'job3', 'job4', 'job2', 'job1']) expect(calls).toEqual(['job5', 'job3', 'job4', 'job2', 'job1'])
}) })
@ -495,13 +458,11 @@ describe('scheduler', () => {
const cb1 = () => calls.push('cb1') const cb1 = () => calls.push('cb1')
// cb1 has no id // cb1 has no id
const cb2 = () => calls.push('cb2') const cb2 = () => calls.push('cb2')
cb2.id = 2
const cb3 = () => calls.push('cb3') const cb3 = () => calls.push('cb3')
cb3.id = 1
queuePostFlushCb(cb1) queuePostFlushCb(cb1)
queuePostFlushCb(cb2) queuePostFlushCb(cb2, 2)
queuePostFlushCb(cb3) queuePostFlushCb(cb3, 1)
await nextTick() await nextTick()
expect(calls).toEqual(['cb3', 'cb2', 'cb1']) expect(calls).toEqual(['cb3', 'cb2', 'cb1'])
}) })
@ -550,13 +511,10 @@ describe('scheduler', () => {
throw err throw err
} }
}) })
job1.id = 1
const job2: SchedulerJob = vi.fn() const job2: SchedulerJob = vi.fn()
job2.id = 2
queueJob(job1) queueJob(job1, 1)
queueJob(job2) queueJob(job2, 2)
try { try {
await nextTick() await nextTick()
@ -570,8 +528,8 @@ describe('scheduler', () => {
expect(job1).toHaveBeenCalledTimes(1) expect(job1).toHaveBeenCalledTimes(1)
expect(job2).toHaveBeenCalledTimes(0) expect(job2).toHaveBeenCalledTimes(0)
queueJob(job1) queueJob(job1, 1)
queueJob(job2) queueJob(job2, 2)
await nextTick() await nextTick()
@ -622,11 +580,10 @@ describe('scheduler', () => {
test('recursive jobs can only be queued once non-recursively', async () => { test('recursive jobs can only be queued once non-recursively', async () => {
const job: SchedulerJob = vi.fn() const job: SchedulerJob = vi.fn()
job.id = 1
job.flags = SchedulerJobFlags.ALLOW_RECURSE job.flags = SchedulerJobFlags.ALLOW_RECURSE
queueJob(job) queueJob(job, 1)
queueJob(job) queueJob(job, 1)
await nextTick() await nextTick()
@ -638,15 +595,14 @@ describe('scheduler', () => {
const job: SchedulerJob = vi.fn(() => { const job: SchedulerJob = vi.fn(() => {
if (recurse) { if (recurse) {
queueJob(job) queueJob(job, 1)
queueJob(job) queueJob(job, 1)
recurse = false recurse = false
} }
}) })
job.id = 1
job.flags = SchedulerJobFlags.ALLOW_RECURSE job.flags = SchedulerJobFlags.ALLOW_RECURSE
queueJob(job) queueJob(job, 1)
await nextTick() await nextTick()
@ -659,22 +615,19 @@ describe('scheduler', () => {
const job1: SchedulerJob = () => { const job1: SchedulerJob = () => {
if (recurse) { if (recurse) {
// job2 is already queued, so this shouldn't do anything // job2 is already queued, so this shouldn't do anything
queueJob(job2) queueJob(job2, 2)
recurse = false recurse = false
} }
} }
job1.id = 1
const job2: SchedulerJob = vi.fn(() => { const job2: SchedulerJob = vi.fn(() => {
if (recurse) { if (recurse) {
queueJob(job1) queueJob(job1, 1)
queueJob(job2) queueJob(job2, 2)
} }
}) })
job2.id = 2
job2.flags = SchedulerJobFlags.ALLOW_RECURSE job2.flags = SchedulerJobFlags.ALLOW_RECURSE
queueJob(job2) queueJob(job2, 2)
await nextTick() await nextTick()
@ -685,40 +638,35 @@ describe('scheduler', () => {
let recurse = true let recurse = true
const job1: SchedulerJob = vi.fn(() => { const job1: SchedulerJob = vi.fn(() => {
queueJob(job3) queueJob(job3, 3, true)
queueJob(job3) queueJob(job3, 3, true)
flushPreFlushCbs() flushPreFlushCbs()
}) })
job1.id = 1
job1.flags = SchedulerJobFlags.PRE
const job2: SchedulerJob = vi.fn(() => { const job2: SchedulerJob = vi.fn(() => {
if (recurse) { if (recurse) {
// job2 does not allow recurse, so this shouldn't do anything // job2 does not allow recurse, so this shouldn't do anything
queueJob(job2) queueJob(job2, 2, true)
// job3 is already queued, so this shouldn't do anything // job3 is already queued, so this shouldn't do anything
queueJob(job3) queueJob(job3, 3, true)
recurse = false recurse = false
} }
}) })
job2.id = 2
job2.flags = SchedulerJobFlags.PRE
const job3: SchedulerJob = vi.fn(() => { const job3: SchedulerJob = vi.fn(() => {
if (recurse) { if (recurse) {
queueJob(job2) queueJob(job2, 2, true)
queueJob(job3) queueJob(job3, 3, true)
// The jobs are already queued, so these should have no effect // The jobs are already queued, so these should have no effect
queueJob(job2) queueJob(job2, 2, true)
queueJob(job3) queueJob(job3, 3, true)
} }
}) })
job3.id = 3 job3.flags = SchedulerJobFlags.ALLOW_RECURSE
job3.flags = SchedulerJobFlags.ALLOW_RECURSE | SchedulerJobFlags.PRE
queueJob(job1) queueJob(job1, 1, true)
await nextTick() await nextTick()
@ -775,8 +723,7 @@ describe('scheduler', () => {
spy() spy()
flushPreFlushCbs() flushPreFlushCbs()
} }
job.flags! |= SchedulerJobFlags.PRE queueJob(job, undefined, true)
queueJob(job)
await nextTick() await nextTick()
expect(spy).toHaveBeenCalledTimes(1) expect(spy).toHaveBeenCalledTimes(1)
}) })
@ -788,18 +735,14 @@ describe('scheduler', () => {
const job1: SchedulerJob = () => { const job1: SchedulerJob = () => {
calls.push('job1') calls.push('job1')
} }
job1.id = 1
job1.flags! |= SchedulerJobFlags.PRE
const job2: SchedulerJob = () => { const job2: SchedulerJob = () => {
calls.push('job2') calls.push('job2')
} }
job2.id = 2
job2.flags! |= SchedulerJobFlags.PRE
queuePostFlushCb(() => { queuePostFlushCb(() => {
queueJob(job2) queueJob(job2, 2, true)
queueJob(job1) queueJob(job1, 1, true)
// e.g. nested app.mount() call // e.g. nested app.mount() call
flushPreFlushCbs() flushPreFlushCbs()
@ -830,14 +773,14 @@ describe('scheduler', () => {
const cb1 = () => calls.push('cb1') const cb1 = () => calls.push('cb1')
// cb1 has no id // cb1 has no id
const cb2 = () => calls.push('cb2') const cb2 = () => calls.push('cb2')
cb2.id = -1
const queueAndFlush = (hook: Function) => { const queueAndFlush = (hook: Function) => {
queuePostFlushCb(hook) queuePostFlushCb(hook)
flushPostFlushCbs() flushPostFlushCbs()
} }
queueAndFlush(() => { queueAndFlush(() => {
queuePostFlushCb([cb1, cb2]) queuePostFlushCb(cb1)
queuePostFlushCb(cb2, -1)
flushPostFlushCbs() flushPostFlushCbs()
}) })

View File

@ -1,6 +1,6 @@
{ {
"name": "@vue/runtime-core", "name": "@vue/runtime-core",
"version": "3.5.14", "version": "3.6.0-alpha.1",
"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",

View File

@ -5,6 +5,7 @@ import {
type ConcreteComponent, type ConcreteComponent,
type GenericComponentInstance, type GenericComponentInstance,
currentInstance, currentInstance,
getComponentName,
isInSSRComponentSetup, isInSSRComponentSetup,
} from './component' } from './component'
import { isFunction, isObject } from '@vue/shared' import { isFunction, isObject } from '@vue/shared'
@ -122,14 +123,27 @@ export function defineAsyncComponent<
__asyncLoader: load, __asyncLoader: load,
__asyncHydrate(el, instance, hydrate) { __asyncHydrate(el, instance, hydrate) {
let patched = false
const doHydrate = hydrateStrategy const doHydrate = hydrateStrategy
? () => { ? () => {
const teardown = hydrateStrategy(hydrate, cb => const performHydrate = () => {
// skip hydration if the component has been patched
if (__DEV__ && patched) {
warn(
`Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
`it was updated before lazy hydration performed.`,
)
return
}
hydrate()
}
const teardown = hydrateStrategy(performHydrate, cb =>
forEachElement(el, cb), forEachElement(el, cb),
) )
if (teardown) { if (teardown) {
;(instance.bum || (instance.bum = [])).push(teardown) ;(instance.bum || (instance.bum = [])).push(teardown)
} }
;(instance.u || (instance.u = [])).push(() => (patched = true))
} }
: hydrate : hydrate
if (resolvedComp) { if (resolvedComp) {

View File

@ -26,7 +26,7 @@ import type { InjectionKey } from './apiInject'
import { warn } from './warning' import { warn } from './warning'
import type { VNode } from './vnode' import type { VNode } from './vnode'
import { devtoolsInitApp, devtoolsUnmountApp } from './devtools' import { devtoolsInitApp, devtoolsUnmountApp } from './devtools'
import { NO, extend, isFunction, isObject } from '@vue/shared' import { NO, extend, hasOwn, isFunction, isObject } from '@vue/shared'
import { version } from '.' import { version } from '.'
import { installAppCompatProperties } from './compat/global' import { installAppCompatProperties } from './compat/global'
import type { NormalizedPropsOptions } from './componentProps' import type { NormalizedPropsOptions } from './componentProps'
@ -35,6 +35,7 @@ import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
import type { DefineComponent } from './apiDefineComponent' import type { DefineComponent } from './apiDefineComponent'
export interface App<HostElement = any> { export interface App<HostElement = any> {
vapor?: boolean
version: string version: string
config: AppConfig config: AppConfig
@ -485,10 +486,18 @@ export function createAppAPI<HostElement, Comp = Component>(
provide(key, value) { provide(key, value) {
if (__DEV__ && (key as string | symbol) in context.provides) { if (__DEV__ && (key as string | symbol) in context.provides) {
warn( if (hasOwn(context.provides, key as string | symbol)) {
`App already provides property with key "${String(key)}". ` + warn(
`It will be overwritten with the new value.`, `App already provides property with key "${String(key)}". ` +
) `It will be overwritten with the new value.`,
)
} else {
// #13212, context.provides can inherit the provides object from parent on custom elements
warn(
`App already provides property with key "${String(key)}" inherited from its parent element. ` +
`It will be overwritten with the new value.`,
)
}
} }
context.provides[key as string | symbol] = value context.provides[key as string | symbol] = value

View File

@ -59,10 +59,12 @@ export function inject(
// to support `app.use` plugins, // to support `app.use` plugins,
// fallback to appContext's `provides` if the instance is at root // fallback to appContext's `provides` if the instance is at root
// #11488, in a nested createApp, prioritize using the provides from currentApp // #11488, in a nested createApp, prioritize using the provides from currentApp
const provides = currentApp // #13212, for custom elements we must get injected values from its appContext
// as it already inherits the provides object from the parent element
let provides = currentApp
? currentApp._context.provides ? currentApp._context.provides
: instance : instance
? instance.parent == null ? instance.parent == null || instance.ce
? instance.appContext && instance.appContext.provides ? instance.appContext && instance.appContext.provides
: instance.parent.provides : instance.parent.provides
: undefined : undefined

View File

@ -8,11 +8,7 @@ import type { ComponentPublicInstance } from './componentPublicInstance'
import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling' import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling'
import { warn } from './warning' import { warn } from './warning'
import { toHandlerKey } from '@vue/shared' import { toHandlerKey } from '@vue/shared'
import { import { type DebuggerEvent, setActiveSub } from '@vue/reactivity'
type DebuggerEvent,
pauseTracking,
resetTracking,
} from '@vue/reactivity'
import { LifecycleHooks } from './enums' import { LifecycleHooks } from './enums'
export { onActivated, onDeactivated } from './components/KeepAlive' export { onActivated, onDeactivated } from './components/KeepAlive'
@ -33,16 +29,16 @@ export function injectHook(
(hook.__weh = (...args: unknown[]) => { (hook.__weh = (...args: unknown[]) => {
// disable tracking inside all lifecycle hooks // disable tracking inside all lifecycle hooks
// since they can potentially be called inside effects. // since they can potentially be called inside effects.
pauseTracking() const prevSub = setActiveSub()
// Set currentInstance during hook invocation. // Set currentInstance during hook invocation.
// This assumes the hook does not synchronously trigger other hooks, which // This assumes the hook does not synchronously trigger other hooks, which
// can only be false when the user does something really funky. // can only be false when the user does something really funky.
const reset = setCurrentInstance(target) const prev = setCurrentInstance(target)
try { try {
return callWithAsyncErrorHandling(hook, target, type, args) return callWithAsyncErrorHandling(hook, target, type, args)
} finally { } finally {
reset() setCurrentInstance(...prev)
resetTracking() setActiveSub(prevSub)
} }
}) })
if (prepend) { if (prepend) {

View File

@ -14,7 +14,6 @@ import {
createSetupContext, createSetupContext,
getCurrentGenericInstance, getCurrentGenericInstance,
setCurrentInstance, setCurrentInstance,
unsetCurrentInstance,
} from './component' } from './component'
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits' import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
import type { import type {
@ -511,7 +510,7 @@ export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
) )
} }
let awaitable = getAwaitable() let awaitable = getAwaitable()
unsetCurrentInstance() setCurrentInstance(null, undefined)
if (isPromise(awaitable)) { if (isPromise(awaitable)) {
awaitable = awaitable.catch(e => { awaitable = awaitable.catch(e => {
setCurrentInstance(ctx) setCurrentInstance(ctx)

View File

@ -1,17 +1,19 @@
import { import {
type WatchOptions as BaseWatchOptions, type WatchOptions as BaseWatchOptions,
type DebuggerOptions, type DebuggerOptions,
EffectFlags,
type ReactiveMarker, type ReactiveMarker,
type WatchCallback, type WatchCallback,
type WatchEffect, type WatchEffect,
type WatchHandle, type WatchHandle,
type WatchSource, type WatchSource,
watch as baseWatch, WatcherEffect,
} from '@vue/reactivity' } from '@vue/reactivity'
import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler' import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler'
import { EMPTY_OBJ, NOOP, extend, isFunction, isString } from '@vue/shared' import { EMPTY_OBJ, NOOP, extend, isFunction, isString } from '@vue/shared'
import { import {
type ComponentInternalInstance, type ComponentInternalInstance,
type GenericComponentInstance,
currentInstance, currentInstance,
isInSSRComponentSetup, isInSSRComponentSetup,
setCurrentInstance, setCurrentInstance,
@ -125,7 +127,7 @@ export function watch<
// implementation // implementation
export function watch<T = any, Immediate extends Readonly<boolean> = false>( export function watch<T = any, Immediate extends Readonly<boolean> = false>(
source: T | WatchSource<T>, source: T | WatchSource<T>,
cb: any, cb: WatchCallback,
options?: WatchOptions<Immediate>, options?: WatchOptions<Immediate>,
): WatchHandle { ): WatchHandle {
if (__DEV__ && !isFunction(cb)) { if (__DEV__ && !isFunction(cb)) {
@ -138,12 +140,57 @@ export function watch<T = any, Immediate extends Readonly<boolean> = false>(
return doWatch(source as any, cb, options) return doWatch(source as any, cb, options)
} }
class RenderWatcherEffect extends WatcherEffect {
job: SchedulerJob
constructor(
instance: GenericComponentInstance | null,
source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null,
options: BaseWatchOptions,
private flush: 'pre' | 'post' | 'sync',
) {
super(source, cb, options)
const job: SchedulerJob = () => {
if (this.dirty) {
this.run()
}
}
// important: mark the job as a watcher callback so that scheduler knows
// it is allowed to self-trigger (#1727)
if (cb) {
this.flags |= EffectFlags.ALLOW_RECURSE
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
}
if (instance) {
job.i = instance
}
this.job = job
}
notify(): void {
const flags = this.flags
if (!(flags & EffectFlags.PAUSED)) {
const flush = this.flush
const job = this.job
if (flush === 'post') {
queuePostRenderEffect(job, undefined, job.i ? job.i.suspense : null)
} else if (flush === 'pre') {
queueJob(job, job.i ? job.i.uid : undefined, true)
} else {
job()
}
}
}
}
function doWatch( function doWatch(
source: WatchSource | WatchSource[] | WatchEffect | object, source: WatchSource | WatchSource[] | WatchEffect | object,
cb: WatchCallback | null, cb: WatchCallback | null,
options: WatchOptions = EMPTY_OBJ, options: WatchOptions = EMPTY_OBJ,
): WatchHandle { ): WatchHandle {
const { immediate, deep, flush, once } = options const { immediate, deep, flush = 'pre', once } = options
if (__DEV__ && !cb) { if (__DEV__ && !cb) {
if (immediate !== undefined) { if (immediate !== undefined) {
@ -190,50 +237,37 @@ function doWatch(
baseWatchOptions.call = (fn, type, args) => baseWatchOptions.call = (fn, type, args) =>
callWithAsyncErrorHandling(fn, instance, type, args) callWithAsyncErrorHandling(fn, instance, type, args)
// scheduler const effect = new RenderWatcherEffect(
let isPre = false instance,
if (flush === 'post') { source,
baseWatchOptions.scheduler = job => { cb,
queuePostRenderEffect(job, instance && instance.suspense) baseWatchOptions,
} flush,
} else if (flush !== 'sync') { )
// default: 'pre'
isPre = true // initial run
baseWatchOptions.scheduler = (job, isFirstRun) => { if (cb) {
if (isFirstRun) { effect.run(true)
job() } else if (flush === 'post') {
} else { queuePostRenderEffect(effect.job, undefined, instance && instance.suspense)
queueJob(job) } else {
} effect.run(true)
}
} }
baseWatchOptions.augmentJob = (job: SchedulerJob) => { const stop = effect.stop.bind(effect) as WatchHandle
// important: mark the job as a watcher callback so that scheduler knows stop.pause = effect.pause.bind(effect)
// it is allowed to self-trigger (#1727) stop.resume = effect.resume.bind(effect)
if (cb) { stop.stop = stop
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
}
if (isPre) {
job.flags! |= SchedulerJobFlags.PRE
if (instance) {
job.id = instance.uid
;(job as SchedulerJob).i = instance
}
}
}
const watchHandle = baseWatch(source, cb, baseWatchOptions)
if (__SSR__ && isInSSRComponentSetup) { if (__SSR__ && isInSSRComponentSetup) {
if (ssrCleanup) { if (ssrCleanup) {
ssrCleanup.push(watchHandle) ssrCleanup.push(stop)
} else if (runsImmediately) { } else if (runsImmediately) {
watchHandle() stop()
} }
} }
return watchHandle return stop
} }
// this.$watch // this.$watch
@ -256,9 +290,9 @@ export function instanceWatch(
cb = value.handler as Function cb = value.handler as Function
options = value options = value
} }
const reset = setCurrentInstance(this) const prev = setCurrentInstance(this)
const res = doWatch(getter, cb.bind(publicThis), options) const res = doWatch(getter, cb.bind(publicThis), options)
reset() setCurrentInstance(...prev)
return res return res
} }

View File

@ -35,7 +35,7 @@ export function convertLegacyAsyncComponent(
let resolve: (res: LegacyAsyncReturnValue) => void let resolve: (res: LegacyAsyncReturnValue) => void
let reject: (reason?: any) => void let reject: (reason?: any) => void
const fallbackPromise = new Promise<Component>((r, rj) => { const fallbackPromise = new Promise<Component>((r, rj) => {
;(resolve = r), (reject = rj) ;((resolve = r), (reject = rj))
}) })
const res = comp(resolve!, reject!) const res = comp(resolve!, reject!)

View File

@ -5,9 +5,8 @@ import {
TrackOpTypes, TrackOpTypes,
isRef, isRef,
markRaw, markRaw,
pauseTracking,
proxyRefs, proxyRefs,
resetTracking, setActiveSub,
shallowReadonly, shallowReadonly,
track, track,
} from '@vue/reactivity' } from '@vue/reactivity'
@ -97,7 +96,6 @@ import type { RendererElement } from './renderer'
import { import {
setCurrentInstance, setCurrentInstance,
setInSSRSetupState, setInSSRSetupState,
unsetCurrentInstance,
} from './componentCurrentInstance' } from './componentCurrentInstance'
export * from './componentCurrentInstance' export * from './componentCurrentInstance'
@ -121,20 +119,23 @@ export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
: T extends FunctionalComponent<infer Props, infer Emits> : T extends FunctionalComponent<infer Props, infer Emits>
? ComponentPublicInstance<Props, {}, {}, {}, {}, ShortEmitsToObject<Emits>> ? ComponentPublicInstance<Props, {}, {}, {}, {}, ShortEmitsToObject<Emits>>
: T extends Component< : T extends Component<
infer Props, infer PropsOrInstance,
infer RawBindings, infer RawBindings,
infer D, infer D,
infer C, infer C,
infer M infer M
> >
? // NOTE we override Props/RawBindings/D to make sure is not `unknown` ? PropsOrInstance extends { $props: unknown }
ComponentPublicInstance< ? // T is returned by `defineComponent()`
unknown extends Props ? {} : Props, PropsOrInstance
unknown extends RawBindings ? {} : RawBindings, : // NOTE we override Props/RawBindings/D to make sure is not `unknown`
unknown extends D ? {} : D, ComponentPublicInstance<
C, unknown extends PropsOrInstance ? {} : PropsOrInstance,
M unknown extends RawBindings ? {} : RawBindings,
> unknown extends D ? {} : D,
C,
M
>
: never // not a vue Component : never // not a vue Component
/** /**
@ -280,7 +281,7 @@ export type ConcreteComponent<
* The constructor type is an artificial type returned by defineComponent(). * The constructor type is an artificial type returned by defineComponent().
*/ */
export type Component< export type Component<
Props = any, PropsOrInstance = any,
RawBindings = any, RawBindings = any,
D = any, D = any,
C extends ComputedOptions = ComputedOptions, C extends ComputedOptions = ComputedOptions,
@ -288,8 +289,8 @@ export type Component<
E extends EmitsOptions | Record<string, any[]> = {}, E extends EmitsOptions | Record<string, any[]> = {},
S extends Record<string, any> = any, S extends Record<string, any> = any,
> = > =
| ConcreteComponent<Props, RawBindings, D, C, M, E, S> | ConcreteComponent<PropsOrInstance, RawBindings, D, C, M, E, S>
| ComponentPublicInstanceConstructor<Props> | ComponentPublicInstanceConstructor<PropsOrInstance>
export type { ComponentOptions } export type { ComponentOptions }
@ -367,9 +368,6 @@ export interface GenericComponentInstance {
// state // state
props: Data props: Data
attrs: Data attrs: Data
/**
* @internal
*/
refs: Data refs: Data
emit: EmitFn emit: EmitFn
/** /**
@ -672,13 +670,13 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
* For updating css vars on contained teleports * For updating css vars on contained teleports
* @internal * @internal
*/ */
ut?: (vars?: Record<string, string>) => void ut?: (vars?: Record<string, unknown>) => void
/** /**
* dev only. For style v-bind hydration mismatch checks * dev only. For style v-bind hydration mismatch checks
* @internal * @internal
*/ */
getCssVars?: () => Record<string, string> getCssVars?: () => Record<string, unknown>
/** /**
* v2 compat only, for caching mutated $options * v2 compat only, for caching mutated $options
@ -888,10 +886,10 @@ function setupStatefulComponent(
// 2. call setup() // 2. call setup()
const { setup } = Component const { setup } = Component
if (setup) { if (setup) {
pauseTracking() const prevSub = setActiveSub()
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 prev = setCurrentInstance(instance)
const setupResult = callWithErrorHandling( const setupResult = callWithErrorHandling(
setup, setup,
instance, instance,
@ -902,8 +900,8 @@ function setupStatefulComponent(
], ],
) )
const isAsyncSetup = isPromise(setupResult) const isAsyncSetup = isPromise(setupResult)
resetTracking() setActiveSub(prevSub)
reset() setCurrentInstance(...prev)
if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) { if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) {
// async setup / serverPrefetch, mark as async boundary for useId() // async setup / serverPrefetch, mark as async boundary for useId()
@ -911,6 +909,9 @@ function setupStatefulComponent(
} }
if (isAsyncSetup) { if (isAsyncSetup) {
const unsetCurrentInstance = (): void => {
setCurrentInstance(null, undefined)
}
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
@ -1083,13 +1084,13 @@ export function finishComponentSetup(
// support for 2.x options // support for 2.x options
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) { if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
const reset = setCurrentInstance(instance) const prevInstance = setCurrentInstance(instance)
pauseTracking() const prevSub = setActiveSub()
try { try {
applyOptions(instance) applyOptions(instance)
} finally { } finally {
resetTracking() setActiveSub(prevSub)
reset() setCurrentInstance(...prevInstance)
} }
} }

View File

@ -4,6 +4,7 @@ import type {
GenericComponentInstance, GenericComponentInstance,
} from './component' } from './component'
import { currentRenderingInstance } from './componentRenderContext' import { currentRenderingInstance } from './componentRenderContext'
import { type EffectScope, setCurrentScope } from '@vue/reactivity'
/** /**
* @internal * @internal
@ -25,7 +26,10 @@ export let isInSSRComponentSetup = false
export let setInSSRSetupState: (state: boolean) => void export let setInSSRSetupState: (state: boolean) => void
let internalSetCurrentInstance: ( /**
* @internal
*/
export let simpleSetCurrentInstance: (
instance: GenericComponentInstance | null, instance: GenericComponentInstance | null,
) => void ) => void
@ -53,7 +57,7 @@ if (__SSR__) {
else setters[0](v) else setters[0](v)
} }
} }
internalSetCurrentInstance = registerGlobalSetter( simpleSetCurrentInstance = registerGlobalSetter(
`__VUE_INSTANCE_SETTERS__`, `__VUE_INSTANCE_SETTERS__`,
v => (currentInstance = v), v => (currentInstance = v),
) )
@ -66,7 +70,7 @@ if (__SSR__) {
v => (isInSSRComponentSetup = v), v => (isInSSRComponentSetup = v),
) )
} else { } else {
internalSetCurrentInstance = i => { simpleSetCurrentInstance = i => {
currentInstance = i currentInstance = i
} }
setInSSRSetupState = v => { setInSSRSetupState = v => {
@ -74,34 +78,15 @@ if (__SSR__) {
} }
} }
export const setCurrentInstance = (instance: GenericComponentInstance) => { export const setCurrentInstance = (
const prev = currentInstance instance: GenericComponentInstance | null,
internalSetCurrentInstance(instance) scope: EffectScope | undefined = instance !== null
instance.scope.on() ? instance.scope
return (): void => { : undefined,
instance.scope.off() ): [GenericComponentInstance | null, EffectScope | undefined] => {
internalSetCurrentInstance(prev) try {
} return [currentInstance, setCurrentScope(scope)]
} } finally {
simpleSetCurrentInstance(instance)
export const unsetCurrentInstance = (): void => {
currentInstance && currentInstance.scope.off()
internalSetCurrentInstance(null)
}
/**
* Exposed for vapor only. Vapor never runs during SSR so we don't want to pay
* for the extra overhead
* @internal
*/
export const simpleSetCurrentInstance = (
i: GenericComponentInstance | null,
unset?: GenericComponentInstance | null,
): void => {
currentInstance = i
if (unset) {
unset.scope.off()
} else if (i) {
i.scope.on()
} }
} }

View File

@ -170,13 +170,15 @@ export function baseEmit(
} }
let args = rawArgs let args = rawArgs
const isModelListener = event.startsWith('update:') const isCompatModelListener =
__COMPAT__ && compatModelEventPrefix + event in props
const isModelListener = isCompatModelListener || event.startsWith('update:')
// for v-model update:xxx events, apply modifiers on args // for v-model update:xxx events, apply modifiers on args
// it's ok to use static get because modelModifiers can only be in the static // it's ok to use static get because modelModifiers can only be in the static
// part of the props // part of the props
const modifiers = const modifiers = isCompatModelListener
isModelListener && getModelModifiers(props, event.slice(7), getter) ? props.modelModifiers
: isModelListener && getModelModifiers(props, event.slice(7), getter)
if (modifiers) { if (modifiers) {
if (modifiers.trim) { if (modifiers.trim) {
args = rawArgs.map(a => (isString(a) ? a.trim() : a)) args = rawArgs.map(a => (isString(a) ? a.trim() : a))

View File

@ -144,7 +144,9 @@ type InferPropType<T, NullAsAny = true> = [T] extends [null]
export type ExtractPropTypes<O> = { export type ExtractPropTypes<O> = {
// use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to // use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to
// support IDE features // support IDE features
[K in keyof Pick<O, RequiredKeys<O>>]: InferPropType<O[K]> [K in keyof Pick<O, RequiredKeys<O>>]: O[K] extends { default: any }
? Exclude<InferPropType<O[K]>, undefined>
: InferPropType<O[K]>
} & { } & {
// use `keyof Pick<O, OptionalKeys<O>>` instead of `OptionalKeys<O>` to // use `keyof Pick<O, OptionalKeys<O>>` instead of `OptionalKeys<O>` to
// support IDE features // support IDE features
@ -522,7 +524,7 @@ function baseResolveDefault(
key: string, key: string,
) { ) {
let value let value
const reset = setCurrentInstance(instance) const prev = setCurrentInstance(instance)
const props = toRaw(instance.props) const props = toRaw(instance.props)
value = factory.call( value = factory.call(
__COMPAT__ && isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance) __COMPAT__ && isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
@ -530,7 +532,7 @@ function baseResolveDefault(
: null, : null,
props, props,
) )
reset() setCurrentInstance(...prev)
return value return value
} }

View File

@ -194,6 +194,10 @@ export const initSlots = (
): void => { ): void => {
const slots = (instance.slots = createInternalObject()) const slots = (instance.slots = createInternalObject())
if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) {
const cacheIndexes = (children as RawSlots).__
// make cache indexes marker non-enumerable
if (cacheIndexes) def(slots, '__', cacheIndexes, true)
const type = (children as RawSlots)._ const type = (children as RawSlots)._
if (type) { if (type) {
assignSlots(slots, children as Slots, optimized) assignSlots(slots, children as Slots, optimized)

Some files were not shown because too many files have changed in this diff Show More