mirror of https://github.com/vuejs/core.git
chore: Merge branch 'minor' into edison/feat/vaporHydration
This commit is contained in:
commit
cb7779b0d9
|
@ -38,7 +38,6 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
|
|||
### Pull Request Checklist
|
||||
|
||||
- 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.
|
||||
|
||||
- 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.
|
||||
|
||||
- If adding a new feature:
|
||||
|
||||
- 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.
|
||||
|
||||
- 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)`.
|
||||
- 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`.
|
||||
|
@ -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.
|
||||
|
||||
- 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?)
|
||||
|
||||
- If the branch is dev-only, performance is less of a concern.
|
||||
|
||||
- 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.
|
||||
|
||||
- Private utility packages:
|
||||
|
||||
- `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).
|
||||
|
|
|
@ -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.
|
||||
|
||||
- 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.
|
||||
|
||||
### 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.
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
|
@ -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.
|
||||
|
||||
- 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.
|
||||
|
||||
- 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`.
|
||||
|
||||
- Performance
|
||||
|
||||
- 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
|
||||
|
|
|
@ -31,4 +31,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
|
||||
|
|
|
@ -130,5 +130,8 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format-check
|
||||
|
||||
- name: Run tsc
|
||||
run: pnpm run check
|
||||
|
||||
- name: Run type declaration tests
|
||||
run: pnpm run test-dts
|
||||
|
|
571
CHANGELOG.md
571
CHANGELOG.md
|
@ -1,541 +1,128 @@
|
|||
## [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))
|
||||
|
||||
# [3.6.0-alpha.1](https://github.com/vuejs/core/compare/v3.5.17...v3.6.0-alpha.1) (2025-07-12)
|
||||
|
||||
### 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)
|
||||
- **vapor mode** ([#12359](https://github.com/vuejs/core/issues/12359)) ([bfe5ce3](https://github.com/vuejs/core/commit/bfe5ce309c6fc16bb49cca78e141862bc12708ac))
|
||||
|
||||
Please see [About Vapor Mode](#about-vapor-mode) section below for details.
|
||||
|
||||
### 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)
|
||||
|
||||
- **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))
|
||||
|
||||
### 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)
|
||||
- **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)
|
||||
- **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))
|
||||
- **reactivity:** queuing effects in an array ([#13078](https://github.com/vuejs/core/issues/13078)) ([826550c](https://github.com/vuejs/core/commit/826550cd629c59dd91aeb5abdbe101a483497358))
|
||||
- **reactivity:** toRefs should be allowed on plain objects ([ac43b11](https://github.com/vuejs/core/commit/ac43b118975b17d7ce7d9e6886f8806af11bee55))
|
||||
- **scheduler:** improve error handling in job flushing ([#13587](https://github.com/vuejs/core/issues/13587)) ([94b2ddc](https://github.com/vuejs/core/commit/94b2ddc6f97170f4169d9d81b963c6bcaab08be2))
|
||||
- **scheduler:** recover nextTick from error in post flush cb ([2bbb6d2](https://github.com/vuejs/core/commit/2bbb6d2fc56896e64a32b4421822d12bde2bb6e8))
|
||||
|
||||
### 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)
|
||||
* **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))
|
||||
Things that do not work in this version yet:
|
||||
|
||||
- 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)
|
||||
* **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)
|
||||
Vapor Mode components are usable in two scenarios:
|
||||
|
||||
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)
|
||||
* **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))
|
||||
#### VDOM Interop Limitations
|
||||
|
||||
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
|
||||
|
||||
* **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)
|
||||
|
||||
#### Behavior Consistency
|
||||
|
||||
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
|
||||
|
||||
### 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)
|
||||
|
||||
See [3.4 changelog](./changelogs/CHANGELOG-3.4.md)
|
||||
|
|
|
@ -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)
|
||||
- **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 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)
|
||||
- **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:** 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 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:** 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))
|
||||
|
@ -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))
|
||||
- **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)
|
||||
- **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)
|
||||
- **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)
|
||||
|
@ -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:** 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))
|
||||
- **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 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 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:** 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)
|
||||
|
@ -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))
|
||||
- **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:** 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)
|
||||
|
||||
### 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)
|
||||
- **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:** 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
|
||||
|
||||
|
@ -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:** 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))
|
||||
- **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:** 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)
|
||||
|
@ -863,7 +863,7 @@ may cause build issues in projects still using TS 3.x.
|
|||
|
||||
### 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))
|
||||
|
||||
### Features
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
|
||||
- **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)
|
||||
- **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))
|
||||
- **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))
|
||||
|
@ -114,7 +114,7 @@
|
|||
### Performance Improvements
|
||||
|
||||
- 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))
|
||||
|
||||
### 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:** 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-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-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))
|
||||
|
@ -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:** 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-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-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))
|
||||
|
@ -317,4 +317,4 @@
|
|||
### Performance Improvements
|
||||
|
||||
- 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))
|
||||
|
|
|
@ -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:** 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:** 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)
|
||||
* **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)
|
||||
|
@ -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)
|
||||
* **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:** 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:** 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)
|
||||
|
@ -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:** 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)
|
||||
* **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)
|
||||
|
||||
|
||||
|
@ -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:** 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))
|
||||
* **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
|
||||
|
||||
* **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)
|
||||
|
||||
|
||||
|
|
|
@ -259,7 +259,7 @@
|
|||
|
||||
* **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))
|
||||
* **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))
|
||||
* **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))
|
||||
|
@ -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)
|
||||
* **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:** 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))
|
||||
|
||||
|
||||
|
@ -544,7 +544,7 @@
|
|||
|
||||
### 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))
|
||||
|
||||
|
||||
|
|
|
@ -167,7 +167,7 @@
|
|||
### 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:** 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)
|
||||
* **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)
|
||||
|
@ -218,7 +218,7 @@
|
|||
### 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)
|
||||
* **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-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)
|
||||
|
@ -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)
|
||||
* 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))
|
||||
* **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)
|
||||
|
||||
|
|
|
@ -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)
|
32
package.json
32
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.14",
|
||||
"packageManager": "pnpm@10.9.0",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"packageManager": "pnpm@10.12.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -65,28 +65,28 @@
|
|||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@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-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.11.24",
|
||||
"@swc/core": "^1.12.9",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/node": "^22.16.0",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitest/ui": "^3.0.2",
|
||||
"@vitest/coverage-v8": "^3.1.3",
|
||||
"@vitest/eslint-plugin": "^1.1.44",
|
||||
"@vitest/coverage-v8": "^3.1.4",
|
||||
"@vitest/eslint-plugin": "^1.2.1",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.25.4",
|
||||
"esbuild": "^0.25.5",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.25.1",
|
||||
"eslint-plugin-import-x": "^4.11.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-import-x": "^4.13.1",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^15.5.1",
|
||||
"lint-staged": "^16.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.17",
|
||||
"markdown-table": "^3.0.4",
|
||||
|
@ -96,21 +96,21 @@
|
|||
"prettier": "^3.5.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~24.8.2",
|
||||
"puppeteer": "~24.9.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.40.2",
|
||||
"rollup": "^4.44.1",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.7.1",
|
||||
"semver": "^7.7.2",
|
||||
"serve": "^14.2.4",
|
||||
"serve-handler": "^6.1.6",
|
||||
"simple-git-hooks": "^2.13.0",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.31.1",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^3.1.3"
|
||||
"vitest": "^3.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -137,3 +137,18 @@ describe('Generic component', () => {
|
|||
expectType<string | number>(comp.msg)
|
||||
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']
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ import { type IsAny, type IsUnion, describe, expectType } from './utils'
|
|||
describe('with object props', () => {
|
||||
interface ExpectedProps {
|
||||
a?: number | undefined
|
||||
aa: number
|
||||
aaa: number | null
|
||||
aaaa: number | undefined
|
||||
b: string
|
||||
e?: Function
|
||||
h: boolean
|
||||
|
@ -53,6 +56,19 @@ describe('with object props', () => {
|
|||
|
||||
const props = {
|
||||
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
|
||||
b: {
|
||||
type: String,
|
||||
|
@ -146,6 +162,13 @@ describe('with object props', () => {
|
|||
setup(props) {
|
||||
// type assertion. See https://github.com/SamVerschueren/tsd
|
||||
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['e']>(props.e)
|
||||
expectType<ExpectedProps['h']>(props.h)
|
||||
|
@ -198,6 +221,8 @@ describe('with object props', () => {
|
|||
render() {
|
||||
const props = this.$props
|
||||
expectType<ExpectedProps['a']>(props.a)
|
||||
expectType<ExpectedProps['aa']>(props.aa)
|
||||
expectType<ExpectedProps['aaa']>(props.aaa)
|
||||
expectType<ExpectedProps['b']>(props.b)
|
||||
expectType<ExpectedProps['e']>(props.e)
|
||||
expectType<ExpectedProps['h']>(props.h)
|
||||
|
@ -225,6 +250,8 @@ describe('with object props', () => {
|
|||
|
||||
// should also expose declared props on `this`
|
||||
expectType<ExpectedProps['a']>(this.a)
|
||||
expectType<ExpectedProps['aa']>(this.aa)
|
||||
expectType<ExpectedProps['aaa']>(this.aaa)
|
||||
expectType<ExpectedProps['b']>(this.b)
|
||||
expectType<ExpectedProps['e']>(this.e)
|
||||
expectType<ExpectedProps['h']>(this.h)
|
||||
|
@ -269,6 +296,7 @@ describe('with object props', () => {
|
|||
expectType<JSX.Element>(
|
||||
<MyComponent
|
||||
a={1}
|
||||
aaaa={1}
|
||||
b="b"
|
||||
bb="bb"
|
||||
e={() => {}}
|
||||
|
@ -295,6 +323,7 @@ describe('with object props', () => {
|
|||
|
||||
expectType<Component>(
|
||||
<MyComponent
|
||||
aaaa={1}
|
||||
b="b"
|
||||
dd={{ n: 1 }}
|
||||
ddd={['ddd']}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.5.1",
|
||||
"@vue/repl": "^4.6.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
@ -156,6 +156,8 @@ onMounted(() => {
|
|||
:editorOptions="{ autoSaveText: false }"
|
||||
:store="store"
|
||||
:showCompileOutput="true"
|
||||
:showSsrOutput="useSSRMode"
|
||||
:showOpenSourceMap="true"
|
||||
:autoResize="true"
|
||||
:clearConsole="false"
|
||||
:preview-options="{
|
||||
|
|
|
@ -44,7 +44,7 @@ const App = {
|
|||
h(
|
||||
'a',
|
||||
{
|
||||
href: `https://github.com/vuejs/vue/tree/${__COMMIT__}`,
|
||||
href: `https://github.com/vuejs/core/tree/${__COMMIT__}`,
|
||||
target: `_blank`,
|
||||
},
|
||||
`@${__COMMIT__}`,
|
||||
|
|
|
@ -2271,6 +2271,11 @@ describe('compiler: parse', () => {
|
|||
expect(span.loc.start.offset).toBe(0)
|
||||
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', () => {
|
||||
|
|
|
@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
|
|||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
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("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* HOISTED */),
|
||||
], -1 /* CACHED */),
|
||||
_createElementVNode("p", null, [
|
||||
_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] = [
|
||||
_createElementVNode("div", null, [
|
||||
_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
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* HOISTED */),
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||
_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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
|
||||
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 */))
|
||||
]))
|
||||
|
@ -216,7 +216,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_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]
|
||||
])
|
||||
|
@ -402,7 +402,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
ok
|
||||
? (_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)
|
||||
]))
|
||||
|
@ -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`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
@ -423,7 +449,7 @@ return function render(_ctx, _cache) {
|
|||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
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 */))
|
||||
]))
|
||||
|
|
|
@ -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`] = `
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock } = Vue
|
||||
|
||||
|
|
|
@ -543,6 +543,32 @@ describe('compiler: cacheStatic transform', () => {
|
|||
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', () => {
|
||||
test('cache nested static tree with static interpolation', () => {
|
||||
const root = transformWithCache(
|
||||
|
|
|
@ -988,5 +988,19 @@ describe('compiler: transform component slots', () => {
|
|||
|
||||
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()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.14",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
@ -204,7 +204,9 @@ function createCodegenContext(
|
|||
name = content
|
||||
}
|
||||
}
|
||||
addMapping(node.loc.start, name)
|
||||
if (node.loc.source) {
|
||||
addMapping(node.loc.start, name)
|
||||
}
|
||||
}
|
||||
if (newlineIndex === NewlineType.Unknown) {
|
||||
// multiple newlines, full iteration
|
||||
|
@ -241,7 +243,7 @@ function createCodegenContext(
|
|||
context.column = code.length - newlineIndex
|
||||
}
|
||||
}
|
||||
if (node && node.loc !== locStub) {
|
||||
if (node && node.loc !== locStub && node.loc.source) {
|
||||
addMapping(node.loc.end)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -647,7 +647,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
|||
|
||||
// whitespace management
|
||||
if (!tokenizer.inRCDATA) {
|
||||
el.children = condenseWhitespace(children, tag)
|
||||
el.children = condenseWhitespace(children)
|
||||
}
|
||||
|
||||
if (ns === Namespaces.HTML && currentOptions.isIgnoreNewlineTag(tag)) {
|
||||
|
@ -832,10 +832,7 @@ function isUpperCase(c: number) {
|
|||
}
|
||||
|
||||
const windowsNewlineRE = /\r\n/g
|
||||
function condenseWhitespace(
|
||||
nodes: TemplateChildNode[],
|
||||
tag?: string,
|
||||
): TemplateChildNode[] {
|
||||
function condenseWhitespace(nodes: TemplateChildNode[]): TemplateChildNode[] {
|
||||
const shouldCondense = currentOptions.whitespace !== 'preserve'
|
||||
let removedWhitespace = false
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
|
|
|
@ -929,7 +929,7 @@ export default class Tokenizer {
|
|||
this.buffer = input
|
||||
while (this.index < this.buffer.length) {
|
||||
const c = this.buffer.charCodeAt(this.index)
|
||||
if (c === CharCodes.NewLine) {
|
||||
if (c === CharCodes.NewLine && this.state !== State.InEntity) {
|
||||
this.newlines.push(this.index)
|
||||
}
|
||||
switch (this.state) {
|
||||
|
|
|
@ -37,7 +37,7 @@ import {
|
|||
helperNameMap,
|
||||
} from './runtimeHelpers'
|
||||
import { isVSlot } from './utils'
|
||||
import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
|
||||
import { cacheStatic, getSingleElementRoot } from './transforms/cacheStatic'
|
||||
import type { CompilerCompatOptions } from './compat/compatConfig'
|
||||
|
||||
// There are two types of transforms:
|
||||
|
@ -360,12 +360,12 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
const { helper } = context
|
||||
const { children } = root
|
||||
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 (isSingleElementRoot(root, child) && child.codegenNode) {
|
||||
if (singleElementRootChild && singleElementRootChild.codegenNode) {
|
||||
// single element root is never hoisted so codegenNode will never be
|
||||
// SimpleExpressionNode
|
||||
const codegenNode = child.codegenNode
|
||||
const codegenNode = singleElementRootChild.codegenNode
|
||||
if (codegenNode.type === NodeTypes.VNODE_CALL) {
|
||||
convertToBlock(codegenNode, context)
|
||||
}
|
||||
|
@ -374,7 +374,7 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
// - single <slot/>, IfNode, ForNode: already blocks.
|
||||
// - single text node: always patched.
|
||||
// root codegen falls through via genNode()
|
||||
root.codegenNode = child
|
||||
root.codegenNode = children[0]
|
||||
}
|
||||
} else if (children.length > 1) {
|
||||
// root has multiple nodes - return a fragment block.
|
||||
|
|
|
@ -41,20 +41,19 @@ export function cacheStatic(root: RootNode, context: TransformContext): void {
|
|||
context,
|
||||
// Root node is unfortunately non-hoistable due to potential parent
|
||||
// fallthrough attributes.
|
||||
isSingleElementRoot(root, root.children[0]),
|
||||
!!getSingleElementRoot(root),
|
||||
)
|
||||
}
|
||||
|
||||
export function isSingleElementRoot(
|
||||
export function getSingleElementRoot(
|
||||
root: RootNode,
|
||||
child: TemplateChildNode,
|
||||
): child is PlainElementNode | ComponentNode | TemplateNode {
|
||||
const { children } = root
|
||||
return (
|
||||
children.length === 1 &&
|
||||
child.type === NodeTypes.ELEMENT &&
|
||||
!isSlotOutlet(child)
|
||||
)
|
||||
): PlainElementNode | ComponentNode | TemplateNode | null {
|
||||
const children = root.children.filter(x => x.type !== NodeTypes.COMMENT)
|
||||
return children.length === 1 &&
|
||||
children[0].type === NodeTypes.ELEMENT &&
|
||||
!isSlotOutlet(children[0])
|
||||
? children[0]
|
||||
: null
|
||||
}
|
||||
|
||||
function walk(
|
||||
|
|
|
@ -594,11 +594,9 @@ export function buildProps(
|
|||
hasDynamicKeys = true
|
||||
if (exp) {
|
||||
if (isVBind) {
|
||||
// #10696 in case a v-bind object contains ref
|
||||
pushRefVForMarker()
|
||||
// have to merge early for compat build check
|
||||
pushMergeArg()
|
||||
if (__COMPAT__) {
|
||||
// have to merge early for compat build check
|
||||
pushMergeArg()
|
||||
// 2.x v-bind object order compat
|
||||
if (__DEV__) {
|
||||
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)
|
||||
} else {
|
||||
// v-on="obj" -> toHandlers(obj)
|
||||
|
|
|
@ -263,7 +263,7 @@ export function processFor(
|
|||
dir: DirectiveNode,
|
||||
context: TransformContext,
|
||||
processCodegen?: (forNode: ForNode) => (() => void) | undefined,
|
||||
) {
|
||||
): (() => void) | undefined {
|
||||
if (!dir.exp) {
|
||||
context.onError(
|
||||
createCompilerError(ErrorCodes.X_V_FOR_NO_EXPRESSION, dir.loc),
|
||||
|
|
|
@ -222,7 +222,7 @@ export function buildSlots(
|
|||
let prev
|
||||
while (j--) {
|
||||
prev = children[j]
|
||||
if (prev.type !== NodeTypes.COMMENT) {
|
||||
if (prev.type !== NodeTypes.COMMENT && isNonWhitespaceContent(prev)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible
|
|||
return function render(_ctx, _cache) {
|
||||
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),
|
||||
_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)
|
||||
])))
|
||||
}"
|
||||
|
@ -54,7 +54,7 @@ return function render(_ctx, _cache) {
|
|||
_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 })
|
||||
], -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`] = `
|
||||
"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("img", { src: _imports_0_ })
|
||||
], -1 /* HOISTED */)
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -491,6 +491,16 @@ describe('stringify static html', () => {
|
|||
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', () => {
|
||||
const { ast, code } = compileWithStringify(
|
||||
`<div><select><option :value="null" />${repeat(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { type CompilerError, compile } from '../../src'
|
||||
import { isValidHTMLNesting } from '../../src/htmlNesting'
|
||||
|
||||
describe('validate html nesting', () => {
|
||||
it('should warn with p > div', () => {
|
||||
|
@ -17,4 +18,185 @@ describe('validate html nesting', () => {
|
|||
})
|
||||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.14",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -11,6 +11,11 @@
|
|||
* returns true if given parent-child nesting is valid HTML
|
||||
*/
|
||||
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 (parent in onlyValidChildren) {
|
||||
return onlyValidChildren[parent].has(child)
|
||||
|
|
|
@ -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:
|
||||
|
||||
- Separate hot-module replacement (HMR) for script, template and styles
|
||||
|
||||
- template updates should not reset component state
|
||||
- style updates should be performed without component re-render
|
||||
|
||||
|
|
|
@ -861,7 +861,7 @@ export default {
|
|||
return (_ctx, _cache) => {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_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 */))
|
||||
}
|
||||
}
|
||||
|
@ -884,9 +884,9 @@ export default {
|
|||
|
||||
return (_ctx, _push, _parent, _attrs) => {
|
||||
const _cssVars = { style: {
|
||||
"--xxxxxxxx-count": (count.value),
|
||||
"--xxxxxxxx-style\\\\.color": (style.color),
|
||||
"--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
||||
":--xxxxxxxx-count": (count.value),
|
||||
":--xxxxxxxx-style\\\\.color": (style.color),
|
||||
":--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
||||
}}
|
||||
_push(\`<!--[--><div\${
|
||||
_ssrRenderAttrs(_cssVars)
|
||||
|
|
|
@ -41,8 +41,8 @@ const _hoisted_1 = _imports_0 + '#fragment'
|
|||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _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 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -10,8 +10,8 @@ const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x'
|
|||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, 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 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
@ -35,51 +35,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* HOISTED */))
|
||||
}, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
@ -92,51 +92,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* HOISTED */))
|
||||
}, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
@ -162,51 +162,51 @@ export function render(_ctx, _cache) {
|
|||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_9
|
||||
}, null, -1 /* HOISTED */)),
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* HOISTED */))
|
||||
}, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
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>', () => {
|
||||
test('should compile JS syntax', () => {
|
||||
|
@ -646,10 +652,10 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(content).toMatch(`return (_ctx, _push`)
|
||||
expect(content).toMatch(`ssrInterpolate`)
|
||||
expect(content).not.toMatch(`useCssVars`)
|
||||
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
|
||||
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
|
||||
expect(content).toMatch(`":--${mockId}-count": (count.value)`)
|
||||
expect(content).toMatch(`":--${mockId}-style\\\\.color": (style.color)`)
|
||||
expect(content).toMatch(
|
||||
`"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
||||
`":--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
@ -690,6 +696,27 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(content).toMatch(`new (_unref(Foo)).Bar()`)
|
||||
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', () => {
|
||||
|
|
|
@ -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', () => {
|
||||
expect(
|
||||
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', () => {
|
||||
test('generic with type literal', () => {
|
||||
expect(
|
||||
|
@ -1434,6 +1483,29 @@ describe('resolveType', () => {
|
|||
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'],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -39,6 +39,24 @@ describe('SFC scoped CSS', () => {
|
|||
expect(compileScoped(`h1 .foo { color: red; }`)).toMatch(
|
||||
`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', () => {
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
} from '../src/compileTemplate'
|
||||
import { type SFCTemplateBlock, parse } from '../src/parse'
|
||||
import { compileScript } from '../src'
|
||||
import { getPositionInCode } from './utils'
|
||||
|
||||
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
|
||||
return compileTemplate({
|
||||
|
@ -157,6 +158,35 @@ test('source map', () => {
|
|||
).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', () => {
|
||||
const source = `
|
||||
<template>
|
||||
|
@ -482,36 +512,3 @@ test('non-identifier expression in legacy filter syntax', () => {
|
|||
babelParse(compilationResult.code, { sourceType: 'module' })
|
||||
}).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
|
||||
}
|
||||
|
|
|
@ -40,3 +40,36 @@ export function assertCode(code: string): void {
|
|||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.14",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -50,7 +50,7 @@
|
|||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.5.3",
|
||||
"postcss": "^8.5.6",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -59,10 +59,10 @@
|
|||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "~10.0.1",
|
||||
"minimatch": "~10.0.3",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-selector-parser": "^7.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.86.3"
|
||||
"sass": "^1.89.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,12 +19,17 @@ import type {
|
|||
Declaration,
|
||||
ExportSpecifier,
|
||||
Identifier,
|
||||
LVal,
|
||||
Node,
|
||||
ObjectPattern,
|
||||
Statement,
|
||||
} from '@babel/types'
|
||||
import { walk } from 'estree-walker'
|
||||
import type { RawSourceMap } from 'source-map-js'
|
||||
import {
|
||||
type RawSourceMap,
|
||||
SourceMapConsumer,
|
||||
SourceMapGenerator,
|
||||
} from 'source-map-js'
|
||||
import {
|
||||
normalScriptDefaultVar,
|
||||
processNormalScript,
|
||||
|
@ -543,7 +548,7 @@ export function compileScript(
|
|||
}
|
||||
|
||||
// defineProps
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
|
||||
if (ctx.propsDestructureRestId) {
|
||||
setupBindings[ctx.propsDestructureRestId] =
|
||||
BindingTypes.SETUP_REACTIVE_CONST
|
||||
|
@ -551,10 +556,10 @@ export function compileScript(
|
|||
|
||||
// defineEmits
|
||||
const isDefineEmits =
|
||||
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
||||
!isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
|
||||
!isDefineEmits &&
|
||||
(processDefineSlots(ctx, init, decl.id) ||
|
||||
processDefineModel(ctx, init, decl.id))
|
||||
(processDefineSlots(ctx, init, decl.id as LVal) ||
|
||||
processDefineModel(ctx, init, decl.id as LVal))
|
||||
|
||||
if (
|
||||
isDefineProps &&
|
||||
|
@ -816,6 +821,7 @@ export function compileScript(
|
|||
args += `, { ${destructureElements.join(', ')} }`
|
||||
}
|
||||
|
||||
let templateMap
|
||||
// 9. generate return statement
|
||||
let returned
|
||||
if (
|
||||
|
@ -865,7 +871,7 @@ export function compileScript(
|
|||
}
|
||||
// inline render function mode - we are going to compile the template and
|
||||
// inline it right here
|
||||
const { code, preamble, tips, errors, helpers } = compileTemplate({
|
||||
const { code, preamble, tips, errors, helpers, map } = compileTemplate({
|
||||
filename,
|
||||
ast: sfc.template.ast,
|
||||
source: sfc.template.content,
|
||||
|
@ -884,6 +890,7 @@ export function compileScript(
|
|||
bindingMetadata: ctx.bindingMetadata,
|
||||
},
|
||||
})
|
||||
templateMap = map
|
||||
if (tips.length) {
|
||||
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 {
|
||||
...scriptSetup,
|
||||
bindings: ctx.bindingMetadata,
|
||||
imports: ctx.userImports,
|
||||
content: ctx.s.toString(),
|
||||
map:
|
||||
options.sourceMap !== false
|
||||
? (ctx.s.generateMap({
|
||||
source: filename,
|
||||
hires: true,
|
||||
includeContent: true,
|
||||
}) as unknown as RawSourceMap)
|
||||
: undefined,
|
||||
content,
|
||||
map,
|
||||
scriptAst: scriptAst?.body,
|
||||
scriptSetupAst: scriptSetupAst?.body,
|
||||
deps: ctx.deps ? [...ctx.deps] : undefined,
|
||||
|
@ -1263,3 +1279,42 @@ function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
|
|||
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()
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ export interface SFCDescriptor {
|
|||
*/
|
||||
slotted: boolean
|
||||
|
||||
vapor: boolean
|
||||
vapor?: boolean
|
||||
|
||||
/**
|
||||
* compare with an existing descriptor to determine whether HMR should perform
|
||||
|
|
|
@ -546,26 +546,43 @@ function resolveStringType(
|
|||
ctx: TypeResolveContext,
|
||||
node: Node,
|
||||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>,
|
||||
): string[] {
|
||||
switch (node.type) {
|
||||
case 'StringLiteral':
|
||||
return [node.value]
|
||||
case 'TSLiteralType':
|
||||
return resolveStringType(ctx, node.literal, scope)
|
||||
return resolveStringType(ctx, node.literal, scope, typeParameters)
|
||||
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': {
|
||||
return resolveTemplateKeys(ctx, node, scope)
|
||||
}
|
||||
case 'TSTypeReference': {
|
||||
const resolved = resolveTypeReference(ctx, node, scope)
|
||||
if (resolved) {
|
||||
return resolveStringType(ctx, resolved, scope)
|
||||
return resolveStringType(ctx, resolved, scope, typeParameters)
|
||||
}
|
||||
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) =>
|
||||
resolveStringType(ctx, node.typeParameters!.params[index], scope)
|
||||
switch (node.typeName.name) {
|
||||
resolveStringType(
|
||||
ctx,
|
||||
node.typeParameters!.params[index],
|
||||
scope,
|
||||
typeParameters,
|
||||
)
|
||||
switch (name) {
|
||||
case 'Extract':
|
||||
return getParam(1)
|
||||
case 'Exclude': {
|
||||
|
@ -671,6 +688,7 @@ function resolveBuiltin(
|
|||
ctx,
|
||||
node.typeParameters!.params[1],
|
||||
scope,
|
||||
typeParameters,
|
||||
)
|
||||
const res: ResolvedElements = { props: {}, calls: t.calls }
|
||||
for (const key of picked) {
|
||||
|
@ -683,6 +701,7 @@ function resolveBuiltin(
|
|||
ctx,
|
||||
node.typeParameters!.params[1],
|
||||
scope,
|
||||
typeParameters,
|
||||
)
|
||||
const res: ResolvedElements = { props: {}, calls: t.calls }
|
||||
for (const key in t.props) {
|
||||
|
@ -860,13 +879,13 @@ function resolveFS(ctx: TypeResolveContext): FS | undefined {
|
|||
}
|
||||
return (ctx.fs = {
|
||||
fileExists(file) {
|
||||
if (file.endsWith('.vue.ts')) {
|
||||
if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) {
|
||||
file = file.replace(/\.ts$/, '')
|
||||
}
|
||||
return fs.fileExists(file)
|
||||
},
|
||||
readFile(file) {
|
||||
if (file.endsWith('.vue.ts')) {
|
||||
if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) {
|
||||
file = file.replace(/\.ts$/, '')
|
||||
}
|
||||
return fs.readFile(file)
|
||||
|
@ -1059,7 +1078,7 @@ function resolveWithTS(
|
|||
|
||||
if (res.resolvedModule) {
|
||||
let filename = res.resolvedModule.resolvedFileName
|
||||
if (filename.endsWith('.vue.ts')) {
|
||||
if (filename.endsWith('.vue.ts') && !filename.endsWith('.d.vue.ts')) {
|
||||
filename = filename.replace(/\.ts$/, '')
|
||||
}
|
||||
return fs.realpath ? fs.realpath(filename) : filename
|
||||
|
@ -1129,7 +1148,7 @@ export function fileToScope(
|
|||
// fs should be guaranteed to exist here
|
||||
const fs = resolveFS(ctx)!
|
||||
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))
|
||||
recordTypes(ctx, body, scope, asGlobal)
|
||||
fileToScopeCache.set(filename, scope)
|
||||
|
@ -1139,6 +1158,7 @@ export function fileToScope(
|
|||
function parseFile(
|
||||
filename: string,
|
||||
content: string,
|
||||
fs: FS,
|
||||
parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
|
||||
): Statement[] {
|
||||
const ext = extname(filename)
|
||||
|
@ -1151,7 +1171,21 @@ function parseFile(
|
|||
),
|
||||
sourceType: 'module',
|
||||
}).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 {
|
||||
descriptor: { script, scriptSetup },
|
||||
} = parse(content)
|
||||
|
@ -1554,6 +1588,15 @@ export function inferRuntimeType(
|
|||
case 'TSTypeReference': {
|
||||
const resolved = resolveTypeReference(ctx, node, scope)
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,7 +23,12 @@ export function genCssVarsFromList(
|
|||
return `{\n ${vars
|
||||
.map(
|
||||
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}`
|
||||
}
|
||||
|
|
|
@ -167,6 +167,135 @@ describe('ssr: v-model', () => {
|
|||
_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">', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.14",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -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) {
|
||||
if (plainNode.tag === 'option') {
|
||||
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') {
|
||||
plainNode.children.forEach(option =>
|
||||
processOption(option as PlainElementNode),
|
||||
)
|
||||
processSelectChildren(plainNode.children)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,18 +173,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
checkDuplicatedValue()
|
||||
node.children = [createInterpolation(model, model.loc)]
|
||||
} else if (node.tag === 'select') {
|
||||
const processChildren = (children: TemplateChildNode[]) => {
|
||||
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)
|
||||
processSelectChildren(node.children)
|
||||
} else {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
|
|
|
@ -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`] = `
|
||||
"import { setProp as _setProp, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
|
||||
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`] = `
|
||||
"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)
|
||||
|
|
|
@ -67,6 +67,73 @@ describe('compiler: v-for', () => {
|
|||
).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', () => {
|
||||
const { code } = compileWithVFor(
|
||||
`<div v-for="(item, index) of items" :item="item" :index="index" />`,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-vapor",
|
||||
"version": "3.5.13",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"description": "@vue/compiler-vapor",
|
||||
"main": "dist/compiler-vapor.cjs.js",
|
||||
"module": "dist/compiler-vapor.esm-bundler.js",
|
||||
|
@ -42,8 +42,10 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-vapor#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "catalog:",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "catalog:",
|
||||
"source-map-js": "catalog:"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,14 +19,13 @@ export function genBlock(
|
|||
context: CodegenContext,
|
||||
args: CodeFragment[] = [],
|
||||
root?: boolean,
|
||||
customReturns?: (returns: CodeFragment[]) => CodeFragment[],
|
||||
): CodeFragment[] {
|
||||
return [
|
||||
'(',
|
||||
...args,
|
||||
') => {',
|
||||
INDENT_START,
|
||||
...genBlockContent(oper, context, root, customReturns),
|
||||
...genBlockContent(oper, context, root),
|
||||
INDENT_END,
|
||||
NEWLINE,
|
||||
'}',
|
||||
|
@ -37,7 +36,7 @@ export function genBlockContent(
|
|||
block: BlockIRNode,
|
||||
context: CodegenContext,
|
||||
root?: boolean,
|
||||
customReturns?: (returns: CodeFragment[]) => CodeFragment[],
|
||||
genEffectsExtraFrag?: () => CodeFragment[],
|
||||
): CodeFragment[] {
|
||||
const [frag, push] = buildCodeFragment()
|
||||
const { dynamic, effect, operation, returns } = block
|
||||
|
@ -70,7 +69,7 @@ export function genBlockContent(
|
|||
}
|
||||
|
||||
push(...genOperations(operation, context))
|
||||
push(...genEffects(effect, context))
|
||||
push(...genEffects(effect, context, genEffectsExtraFrag))
|
||||
|
||||
push(NEWLINE, `return `)
|
||||
|
||||
|
@ -79,7 +78,7 @@ export function genBlockContent(
|
|||
returnNodes.length > 1
|
||||
? genMulti(DELIMITERS_ARRAY, ...returnNodes)
|
||||
: [returnNodes[0] || 'null']
|
||||
push(...(customReturns ? customReturns(returnsCode) : returnsCode))
|
||||
push(...returnsCode)
|
||||
|
||||
resetBlock()
|
||||
return frag
|
||||
|
|
|
@ -233,6 +233,7 @@ function canPrefix(name: string) {
|
|||
type DeclarationResult = {
|
||||
ids: Record<string, string>
|
||||
frag: CodeFragment[]
|
||||
varNames: string[]
|
||||
}
|
||||
type DeclarationValue = {
|
||||
name: string
|
||||
|
@ -246,6 +247,7 @@ type DeclarationValue = {
|
|||
export function processExpressions(
|
||||
context: CodegenContext,
|
||||
expressions: SimpleExpressionNode[],
|
||||
shouldDeclare: boolean,
|
||||
): DeclarationResult {
|
||||
// analyze variables
|
||||
const {
|
||||
|
@ -277,7 +279,11 @@ export function processExpressions(
|
|||
expToVariableMap,
|
||||
)
|
||||
|
||||
return genDeclarations([...varDeclarations, ...expDeclarations], context)
|
||||
return genDeclarations(
|
||||
[...varDeclarations, ...expDeclarations],
|
||||
context,
|
||||
shouldDeclare,
|
||||
)
|
||||
}
|
||||
|
||||
function analyzeExpressions(expressions: SimpleExpressionNode[]) {
|
||||
|
@ -592,15 +598,21 @@ function processRepeatedExpressions(
|
|||
function genDeclarations(
|
||||
declarations: DeclarationValue[],
|
||||
context: CodegenContext,
|
||||
shouldDeclare: boolean,
|
||||
): DeclarationResult {
|
||||
const [frag, push] = buildCodeFragment()
|
||||
const ids: Record<string, string> = Object.create(null)
|
||||
const varNames = new Set<string>()
|
||||
|
||||
// process identifiers first as expressions may rely on them
|
||||
declarations.forEach(({ name, isIdentifier, value }) => {
|
||||
if (isIdentifier) {
|
||||
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 }) => {
|
||||
if (!isIdentifier) {
|
||||
const varName = (ids[name] = `_${name}`)
|
||||
varNames.add(varName)
|
||||
if (shouldDeclare) {
|
||||
push(`const `)
|
||||
}
|
||||
push(
|
||||
`const ${varName} = `,
|
||||
`${varName} = `,
|
||||
...context.withId(() => genExpression(value, context), ids),
|
||||
NEWLINE,
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return { ids, frag }
|
||||
return { ids, frag, varNames: [...varNames] }
|
||||
}
|
||||
|
||||
function escapeRegExp(string: string) {
|
||||
|
|
|
@ -1,16 +1,32 @@
|
|||
import {
|
||||
type SimpleExpressionNode,
|
||||
createSimpleExpression,
|
||||
isStaticNode,
|
||||
walkIdentifiers,
|
||||
} from '@vue/compiler-dom'
|
||||
import { genBlock } from './block'
|
||||
import { genBlockContent } from './block'
|
||||
import { genExpression } from './expression'
|
||||
import type { CodegenContext } from '../generate'
|
||||
import type { ForIRNode } from '../ir'
|
||||
import { type CodeFragment, NEWLINE, genCall, genMulti } from './utils'
|
||||
import type { Identifier } from '@babel/types'
|
||||
import type { BlockIRNode, ForIRNode, IREffect } from '../ir'
|
||||
import {
|
||||
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 { VaporVForFlags } from '../../../shared/src/vaporFlags'
|
||||
import { walk } from 'estree-walker'
|
||||
import { genOperation } from './operation'
|
||||
import { extend, isGloballyAllowed } from '@vue/shared'
|
||||
|
||||
export function genFor(
|
||||
oper: ForIRNode,
|
||||
|
@ -78,7 +94,62 @@ export function genFor(
|
|||
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()
|
||||
|
||||
let flags = 0
|
||||
|
@ -103,6 +174,7 @@ export function genFor(
|
|||
flags ? String(flags) : undefined,
|
||||
// todo: hydrationNode
|
||||
),
|
||||
...patternFrag,
|
||||
]
|
||||
|
||||
// construct a id -> accessor path map.
|
||||
|
@ -234,3 +306,223 @@ export function genFor(
|
|||
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
|
||||
}
|
||||
|
|
|
@ -98,15 +98,18 @@ export function genOperation(
|
|||
export function genEffects(
|
||||
effects: IREffect[],
|
||||
context: CodegenContext,
|
||||
genExtraFrag?: () => CodeFragment[],
|
||||
): CodeFragment[] {
|
||||
const { helper } = context
|
||||
const expressions = effects.flatMap(effect => effect.expressions)
|
||||
const [frag, push, unshift] = buildCodeFragment()
|
||||
const shouldDeclare = genExtraFrag === undefined
|
||||
let operationsCount = 0
|
||||
const { ids, frag: declarationFrags } = processExpressions(
|
||||
context,
|
||||
expressions,
|
||||
)
|
||||
const {
|
||||
ids,
|
||||
frag: declarationFrags,
|
||||
varNames,
|
||||
} = processExpressions(context, expressions, shouldDeclare)
|
||||
push(...declarationFrags)
|
||||
for (let i = 0; i < effects.length; i++) {
|
||||
const effect = effects[i]
|
||||
|
@ -123,6 +126,9 @@ export function genEffects(
|
|||
if (newLineCount > 1 || operationsCount > 1 || declarationFrags.length > 0) {
|
||||
unshift(`{`, INDENT_START, NEWLINE)
|
||||
push(INDENT_END, NEWLINE, '}')
|
||||
if (!effects.length) {
|
||||
unshift(NEWLINE)
|
||||
}
|
||||
}
|
||||
|
||||
if (effects.length) {
|
||||
|
@ -130,6 +136,14 @@ export function genEffects(
|
|||
push(`)`)
|
||||
}
|
||||
|
||||
if (!shouldDeclare && varNames.length) {
|
||||
unshift(NEWLINE, `let `, varNames.join(', '))
|
||||
}
|
||||
|
||||
if (genExtraFrag) {
|
||||
push(...context.withId(genExtraFrag, ids))
|
||||
}
|
||||
|
||||
return frag
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import {
|
|||
} from '../src'
|
||||
import type { ComputedRef, ComputedRefImpl } from '../src/computed'
|
||||
import { pauseTracking, resetTracking } from '../src/effect'
|
||||
import { SubscriberFlags } from '../src/system'
|
||||
import { ReactiveFlags } from '../src/system'
|
||||
|
||||
describe('reactivity/computed', () => {
|
||||
it('should return updated value', () => {
|
||||
|
@ -467,12 +467,8 @@ describe('reactivity/computed', () => {
|
|||
const c2 = computed(() => c1.value) as unknown as ComputedRefImpl
|
||||
|
||||
c2.value
|
||||
expect(
|
||||
c1.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed),
|
||||
).toBe(0)
|
||||
expect(
|
||||
c2.flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed),
|
||||
).toBe(0)
|
||||
expect(c1.flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)).toBe(0)
|
||||
expect(c2.flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)).toBe(0)
|
||||
})
|
||||
|
||||
it('should chained computeds dirtyLevel update with first computed effect', () => {
|
||||
|
|
|
@ -22,7 +22,7 @@ import {
|
|||
stop,
|
||||
toRaw,
|
||||
} from '../src/index'
|
||||
import { type Dependency, endBatch, startBatch } from '../src/system'
|
||||
import { type ReactiveNode, endBatch, startBatch } from '../src/system'
|
||||
|
||||
describe('reactivity/effect', () => {
|
||||
it('should run the passed function once (wrapped by a effect)', () => {
|
||||
|
@ -1178,7 +1178,7 @@ describe('reactivity/effect', () => {
|
|||
})
|
||||
|
||||
describe('dep unsubscribe', () => {
|
||||
function getSubCount(dep: Dependency | undefined) {
|
||||
function getSubCount(dep: ReactiveNode | undefined) {
|
||||
let count = 0
|
||||
let sub = dep!.subs
|
||||
while (sub) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { nextTick, watch, watchEffect } from '@vue/runtime-core'
|
|||
import {
|
||||
type ComputedRef,
|
||||
EffectScope,
|
||||
ReactiveEffect,
|
||||
computed,
|
||||
effect,
|
||||
effectScope,
|
||||
|
@ -9,6 +10,7 @@ import {
|
|||
onScopeDispose,
|
||||
reactive,
|
||||
ref,
|
||||
setCurrentScope,
|
||||
} from '../src'
|
||||
|
||||
describe('reactivity/effect/scope', () => {
|
||||
|
@ -20,7 +22,7 @@ describe('reactivity/effect/scope', () => {
|
|||
|
||||
it('should accept zero argument', () => {
|
||||
const scope = effectScope()
|
||||
expect(scope.effects.length).toBe(0)
|
||||
expect(getEffectsCount(scope)).toBe(0)
|
||||
})
|
||||
|
||||
it('should return run value', () => {
|
||||
|
@ -29,7 +31,8 @@ describe('reactivity/effect/scope', () => {
|
|||
|
||||
it('should work w/ active property', () => {
|
||||
const scope = effectScope()
|
||||
scope.run(() => 1)
|
||||
const src = computed(() => 1)
|
||||
scope.run(() => src.value)
|
||||
expect(scope.active).toBe(true)
|
||||
scope.stop()
|
||||
expect(scope.active).toBe(false)
|
||||
|
@ -47,7 +50,7 @@ describe('reactivity/effect/scope', () => {
|
|||
expect(dummy).toBe(7)
|
||||
})
|
||||
|
||||
expect(scope.effects.length).toBe(1)
|
||||
expect(getEffectsCount(scope)).toBe(1)
|
||||
})
|
||||
|
||||
it('stop', () => {
|
||||
|
@ -60,7 +63,7 @@ describe('reactivity/effect/scope', () => {
|
|||
effect(() => (doubled = counter.num * 2))
|
||||
})
|
||||
|
||||
expect(scope.effects.length).toBe(2)
|
||||
expect(getEffectsCount(scope)).toBe(2)
|
||||
|
||||
expect(dummy).toBe(0)
|
||||
counter.num = 7
|
||||
|
@ -87,9 +90,8 @@ describe('reactivity/effect/scope', () => {
|
|||
})
|
||||
})
|
||||
|
||||
expect(scope.effects.length).toBe(1)
|
||||
expect(scope.scopes!.length).toBe(1)
|
||||
expect(scope.scopes![0]).toBeInstanceOf(EffectScope)
|
||||
expect(getEffectsCount(scope)).toBe(1)
|
||||
expect(scope.deps?.nextDep?.dep).toBeInstanceOf(EffectScope)
|
||||
|
||||
expect(dummy).toBe(0)
|
||||
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)
|
||||
counter.num = 7
|
||||
|
@ -142,13 +144,13 @@ describe('reactivity/effect/scope', () => {
|
|||
effect(() => (dummy = counter.num))
|
||||
})
|
||||
|
||||
expect(scope.effects.length).toBe(1)
|
||||
expect(getEffectsCount(scope)).toBe(1)
|
||||
|
||||
scope.run(() => {
|
||||
effect(() => (doubled = counter.num * 2))
|
||||
})
|
||||
|
||||
expect(scope.effects.length).toBe(2)
|
||||
expect(getEffectsCount(scope)).toBe(2)
|
||||
|
||||
counter.num = 7
|
||||
expect(dummy).toBe(7)
|
||||
|
@ -166,21 +168,21 @@ describe('reactivity/effect/scope', () => {
|
|||
effect(() => (dummy = counter.num))
|
||||
})
|
||||
|
||||
expect(scope.effects.length).toBe(1)
|
||||
expect(getEffectsCount(scope)).toBe(1)
|
||||
|
||||
scope.stop()
|
||||
|
||||
expect(getEffectsCount(scope)).toBe(0)
|
||||
|
||||
scope.run(() => {
|
||||
effect(() => (doubled = counter.num * 2))
|
||||
})
|
||||
|
||||
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
||||
|
||||
expect(scope.effects.length).toBe(0)
|
||||
expect(getEffectsCount(scope)).toBe(1)
|
||||
|
||||
counter.num = 7
|
||||
expect(dummy).toBe(0)
|
||||
expect(doubled).toBe(undefined)
|
||||
expect(doubled).toBe(14)
|
||||
})
|
||||
|
||||
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)', () => {
|
||||
const parent = effectScope()
|
||||
const child = parent.run(() => effectScope())!
|
||||
expect(parent.scopes!.includes(child)).toBe(true)
|
||||
expect(parent.deps?.dep).toBe(child)
|
||||
child.stop()
|
||||
expect(parent.scopes!.includes(child)).toBe(false)
|
||||
expect(parent.deps).toBeUndefined()
|
||||
})
|
||||
|
||||
it('test with higher level APIs', async () => {
|
||||
|
@ -290,21 +292,7 @@ describe('reactivity/effect/scope', () => {
|
|||
|
||||
parentScope.run(() => {
|
||||
const childScope = effectScope(true)
|
||||
childScope.on()
|
||||
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()
|
||||
setCurrentScope(setCurrentScope(childScope))
|
||||
expect(getCurrentScope()).toBe(parentScope)
|
||||
})
|
||||
})
|
||||
|
@ -372,7 +360,17 @@ describe('reactivity/effect/scope', () => {
|
|||
expect(watcherCalls).toBe(3)
|
||||
expect(cleanupCalls).toBe(1)
|
||||
|
||||
expect(scope.effects.length).toBe(0)
|
||||
expect(scope.cleanups.length).toBe(0)
|
||||
expect(getEffectsCount(scope)).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
|
||||
}
|
||||
|
|
|
@ -195,8 +195,8 @@ describe('reactivity/reactive', () => {
|
|||
test('toRaw on object using reactive as prototype', () => {
|
||||
const original = { foo: 1 }
|
||||
const observed = reactive(original)
|
||||
const inherted = Object.create(observed)
|
||||
expect(toRaw(inherted)).toBe(inherted)
|
||||
const inherited = Object.create(observed)
|
||||
expect(toRaw(inherited)).toBe(inherited)
|
||||
})
|
||||
|
||||
test('toRaw on user Proxy wrapping reactive', () => {
|
||||
|
|
|
@ -8,7 +8,9 @@ import {
|
|||
reactive,
|
||||
readonly,
|
||||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
triggerRef,
|
||||
} from '../src'
|
||||
|
||||
/**
|
||||
|
@ -520,3 +522,16 @@ describe('reactivity/readonly', () => {
|
|||
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)
|
||||
})
|
||||
|
|
|
@ -3,40 +3,12 @@ import {
|
|||
type Ref,
|
||||
WatchErrorCodes,
|
||||
type WatchOptions,
|
||||
type WatchScheduler,
|
||||
computed,
|
||||
onWatcherCleanup,
|
||||
ref,
|
||||
watch,
|
||||
} 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', () => {
|
||||
test('effect', () => {
|
||||
let dummy: any
|
||||
|
@ -147,54 +119,6 @@ describe('watch', () => {
|
|||
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 () => {
|
||||
let dummy: any
|
||||
const source = ref(0)
|
||||
|
@ -277,4 +201,16 @@ describe('watch', () => {
|
|||
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.14",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { isArray } from '@vue/shared'
|
||||
import { TrackOpTypes } from './constants'
|
||||
import { ARRAY_ITERATE_KEY, track } from './dep'
|
||||
import { pauseTracking, resetTracking } from './effect'
|
||||
import { isProxy, isShallow, toRaw, toReactive } from './reactive'
|
||||
import { endBatch, startBatch } from './system'
|
||||
import { endBatch, setActiveSub, startBatch } from './system'
|
||||
|
||||
/**
|
||||
* Track array iteration and return:
|
||||
|
@ -320,10 +319,10 @@ function noTracking(
|
|||
method: keyof Array<any>,
|
||||
args: unknown[] = [],
|
||||
) {
|
||||
pauseTracking()
|
||||
startBatch()
|
||||
const prevSub = setActiveSub()
|
||||
const res = (toRaw(self) as any)[method].apply(self, args)
|
||||
setActiveSub(prevSub)
|
||||
endBatch()
|
||||
resetTracking()
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -96,15 +96,20 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
}
|
||||
}
|
||||
|
||||
const wasRef = isRef(target)
|
||||
const res = Reflect.get(
|
||||
target,
|
||||
key,
|
||||
// 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
|
||||
// its class methods
|
||||
isRef(target) ? target : receiver,
|
||||
wasRef ? target : receiver,
|
||||
)
|
||||
|
||||
if (wasRef && key !== 'value') {
|
||||
return res
|
||||
}
|
||||
|
||||
if (isSymbol(key) ? builtInSymbols.has(key) : isNonTrackableKeys(key)) {
|
||||
return res
|
||||
}
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
import { hasChanged, isFunction } from '@vue/shared'
|
||||
import { ReactiveFlags, TrackOpTypes } from './constants'
|
||||
import { onTrack, setupOnTrigger } from './debug'
|
||||
import {
|
||||
type DebuggerEvent,
|
||||
type DebuggerOptions,
|
||||
activeSub,
|
||||
setActiveSub,
|
||||
} from './effect'
|
||||
import type { DebuggerEvent, DebuggerOptions } from './effect'
|
||||
import { activeEffectScope } from './effectScope'
|
||||
import type { Ref } from './ref'
|
||||
import {
|
||||
type Dependency,
|
||||
type Link,
|
||||
type Subscriber,
|
||||
SubscriberFlags,
|
||||
type ReactiveNode,
|
||||
ReactiveFlags as SystemReactiveFlags,
|
||||
activeSub,
|
||||
checkDirty,
|
||||
endTracking,
|
||||
link,
|
||||
processComputedUpdate,
|
||||
shallowPropagate,
|
||||
startTracking,
|
||||
updateDirtyFlag,
|
||||
} from './system'
|
||||
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
|
||||
* the main vue package
|
||||
*/
|
||||
export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
||||
export class ComputedRefImpl<T = any> implements ReactiveNode {
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
_value: T | undefined = undefined
|
||||
|
||||
// Dependency
|
||||
subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
|
||||
// Subscriber
|
||||
deps: Link | undefined = undefined
|
||||
depsTail: Link | undefined = undefined
|
||||
flags: SubscriberFlags = SubscriberFlags.Computed | SubscriberFlags.Dirty
|
||||
flags: SystemReactiveFlags =
|
||||
SystemReactiveFlags.Mutable | SystemReactiveFlags.Dirty
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -84,7 +77,7 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
|||
return this
|
||||
}
|
||||
// for backwards compat
|
||||
get dep(): Dependency {
|
||||
get dep(): ReactiveNode {
|
||||
return this
|
||||
}
|
||||
/**
|
||||
|
@ -93,13 +86,17 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
|||
*/
|
||||
get _dirty(): boolean {
|
||||
const flags = this.flags
|
||||
if (
|
||||
flags & SubscriberFlags.Dirty ||
|
||||
(flags & SubscriberFlags.PendingComputed &&
|
||||
updateDirtyFlag(this, this.flags))
|
||||
) {
|
||||
if (flags & SystemReactiveFlags.Dirty) {
|
||||
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
|
||||
}
|
||||
/**
|
||||
|
@ -108,9 +105,9 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
|||
*/
|
||||
set _dirty(v: boolean) {
|
||||
if (v) {
|
||||
this.flags |= SubscriberFlags.Dirty
|
||||
this.flags |= SystemReactiveFlags.Dirty
|
||||
} 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 {
|
||||
const flags = this.flags
|
||||
if (flags & (SubscriberFlags.Dirty | SubscriberFlags.PendingComputed)) {
|
||||
processComputedUpdate(this, flags)
|
||||
if (
|
||||
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 (__DEV__) {
|
||||
|
@ -155,9 +162,7 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
|||
}
|
||||
|
||||
update(): boolean {
|
||||
const prevSub = activeSub
|
||||
setActiveSub(this)
|
||||
startTracking(this)
|
||||
const prevSub = startTracking(this)
|
||||
try {
|
||||
const oldValue = this._value
|
||||
const newValue = this.fn(oldValue)
|
||||
|
@ -167,8 +172,7 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
|||
}
|
||||
return false
|
||||
} finally {
|
||||
setActiveSub(prevSub)
|
||||
endTracking(this)
|
||||
endTracking(this, prevSub)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { extend } from '@vue/shared'
|
||||
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[] = []
|
||||
|
||||
|
@ -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
|
||||
Object.defineProperty(target, 'flags', {
|
||||
get() {
|
||||
|
@ -69,8 +69,11 @@ function setupFlagsHandler(target: Subscriber): void {
|
|||
},
|
||||
set(value) {
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -1,19 +1,22 @@
|
|||
import { isArray, isIntegerKey, isMap, isSymbol } from '@vue/shared'
|
||||
import { type TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { onTrack, triggerEventInfos } from './debug'
|
||||
import { activeSub } from './effect'
|
||||
import {
|
||||
type Dependency,
|
||||
type Link,
|
||||
ReactiveFlags,
|
||||
type ReactiveNode,
|
||||
activeSub,
|
||||
endBatch,
|
||||
link,
|
||||
propagate,
|
||||
shallowPropagate,
|
||||
startBatch,
|
||||
} from './system'
|
||||
|
||||
class Dep implements Dependency {
|
||||
class Dep implements ReactiveNode {
|
||||
_subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
flags: ReactiveFlags = ReactiveFlags.None
|
||||
|
||||
constructor(
|
||||
private map: KeyToDepMap,
|
||||
|
@ -103,7 +106,7 @@ export function trigger(
|
|||
return
|
||||
}
|
||||
|
||||
const run = (dep: Dependency | undefined) => {
|
||||
const run = (dep: ReactiveNode | undefined) => {
|
||||
if (dep !== undefined && dep.subs !== undefined) {
|
||||
if (__DEV__) {
|
||||
triggerEventInfos.push({
|
||||
|
@ -116,6 +119,7 @@ export function trigger(
|
|||
})
|
||||
}
|
||||
propagate(dep.subs)
|
||||
shallowPropagate(dep.subs)
|
||||
if (__DEV__) {
|
||||
triggerEventInfos.pop()
|
||||
}
|
||||
|
@ -190,7 +194,7 @@ export function trigger(
|
|||
export function getDepFromReactive(
|
||||
object: any,
|
||||
key: string | number | symbol,
|
||||
): Dependency | undefined {
|
||||
): ReactiveNode | undefined {
|
||||
const depMap = targetMap.get(object)
|
||||
return depMap && depMap.get(key)
|
||||
}
|
||||
|
|
|
@ -4,18 +4,22 @@ import { setupOnTrigger } from './debug'
|
|||
import { activeEffectScope } from './effectScope'
|
||||
import {
|
||||
type Link,
|
||||
type Subscriber,
|
||||
SubscriberFlags,
|
||||
ReactiveFlags,
|
||||
type ReactiveNode,
|
||||
activeSub,
|
||||
checkDirty,
|
||||
endTracking,
|
||||
link,
|
||||
setActiveSub,
|
||||
startTracking,
|
||||
updateDirtyFlag,
|
||||
unlink,
|
||||
} from './system'
|
||||
import { warn } from './warning'
|
||||
|
||||
export type EffectScheduler = (...args: any[]) => any
|
||||
|
||||
export type DebuggerEvent = {
|
||||
effect: Subscriber
|
||||
effect: ReactiveNode
|
||||
} & DebuggerEventExtraInfo
|
||||
|
||||
export type DebuggerEventExtraInfo = {
|
||||
|
@ -48,28 +52,41 @@ export enum EffectFlags {
|
|||
*/
|
||||
ALLOW_RECURSE = 1 << 7,
|
||||
PAUSED = 1 << 8,
|
||||
NOTIFIED = 1 << 9,
|
||||
STOP = 1 << 10,
|
||||
}
|
||||
|
||||
export class ReactiveEffect<T = any> implements ReactiveEffectOptions {
|
||||
// Subscriber
|
||||
export class ReactiveEffect<T = any>
|
||||
implements ReactiveEffectOptions, ReactiveNode
|
||||
{
|
||||
deps: 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
|
||||
*/
|
||||
cleanup?: () => void = undefined
|
||||
cleanups: (() => void)[] = []
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
cleanupsLength = 0
|
||||
|
||||
onStop?: () => void
|
||||
// dev only
|
||||
onTrack?: (event: DebuggerEvent) => void
|
||||
// dev only
|
||||
onTrigger?: (event: DebuggerEvent) => void
|
||||
|
||||
constructor(public fn: () => T) {
|
||||
if (activeEffectScope && activeEffectScope.active) {
|
||||
activeEffectScope.effects.push(this)
|
||||
// @ts-expect-error
|
||||
fn(): T {}
|
||||
|
||||
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 {
|
||||
if (!(this.flags & EffectFlags.PAUSED)) {
|
||||
this.flags |= EffectFlags.PAUSED
|
||||
}
|
||||
this.flags |= EffectFlags.PAUSED
|
||||
}
|
||||
|
||||
resume(): void {
|
||||
const flags = this.flags
|
||||
if (flags & EffectFlags.PAUSED) {
|
||||
this.flags &= ~EffectFlags.PAUSED
|
||||
}
|
||||
if (flags & EffectFlags.NOTIFIED) {
|
||||
this.flags &= ~EffectFlags.NOTIFIED
|
||||
const flags = (this.flags &= ~EffectFlags.PAUSED)
|
||||
if (flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) {
|
||||
this.notify()
|
||||
}
|
||||
}
|
||||
|
||||
notify(): void {
|
||||
const flags = this.flags
|
||||
if (!(flags & EffectFlags.PAUSED)) {
|
||||
this.scheduler()
|
||||
} else {
|
||||
this.flags |= EffectFlags.NOTIFIED
|
||||
}
|
||||
}
|
||||
|
||||
scheduler(): void {
|
||||
if (this.dirty) {
|
||||
if (!(this.flags & EffectFlags.PAUSED) && this.dirty) {
|
||||
this.run()
|
||||
}
|
||||
}
|
||||
|
||||
run(): T {
|
||||
// TODO cleanupEffect
|
||||
|
||||
if (!this.active) {
|
||||
// stopped during cleanup
|
||||
return this.fn()
|
||||
}
|
||||
cleanupEffect(this)
|
||||
const prevSub = activeSub
|
||||
setActiveSub(this)
|
||||
startTracking(this)
|
||||
|
||||
cleanup(this)
|
||||
const prevSub = startTracking(this)
|
||||
try {
|
||||
return this.fn()
|
||||
} finally {
|
||||
if (__DEV__ && activeSub !== this) {
|
||||
warn(
|
||||
'Active effect was not restored correctly - ' +
|
||||
'this is likely a Vue internal bug.',
|
||||
)
|
||||
}
|
||||
setActiveSub(prevSub)
|
||||
endTracking(this)
|
||||
endTracking(this, prevSub)
|
||||
const flags = this.flags
|
||||
if (
|
||||
this.flags & SubscriberFlags.Recursed &&
|
||||
this.flags & EffectFlags.ALLOW_RECURSE
|
||||
(flags & (ReactiveFlags.Recursed | EffectFlags.ALLOW_RECURSE)) ===
|
||||
(ReactiveFlags.Recursed | EffectFlags.ALLOW_RECURSE)
|
||||
) {
|
||||
this.flags &= ~SubscriberFlags.Recursed
|
||||
this.flags = flags & ~ReactiveFlags.Recursed
|
||||
this.notify()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stop(): void {
|
||||
if (this.active) {
|
||||
startTracking(this)
|
||||
endTracking(this)
|
||||
cleanupEffect(this)
|
||||
this.onStop && this.onStop()
|
||||
this.flags |= EffectFlags.STOP
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
const flags = this.flags
|
||||
if (
|
||||
flags & SubscriberFlags.Dirty ||
|
||||
(flags & SubscriberFlags.PendingComputed && updateDirtyFlag(this, flags))
|
||||
) {
|
||||
if (flags & ReactiveFlags.Dirty) {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -183,6 +184,23 @@ export function effect<T = any>(
|
|||
|
||||
const e = new ReactiveEffect(fn)
|
||||
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)
|
||||
}
|
||||
try {
|
||||
|
@ -205,14 +223,14 @@ export function stop(runner: ReactiveEffectRunner): void {
|
|||
runner.effect.stop()
|
||||
}
|
||||
|
||||
const resetTrackingStack: (Subscriber | undefined)[] = []
|
||||
const resetTrackingStack: (ReactiveNode | undefined)[] = []
|
||||
|
||||
/**
|
||||
* Temporarily pauses tracking.
|
||||
*/
|
||||
export function pauseTracking(): void {
|
||||
resetTrackingStack.push(activeSub)
|
||||
activeSub = undefined
|
||||
setActiveSub()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -230,7 +248,7 @@ export function enableTracking(): void {
|
|||
resetTrackingStack.push(undefined)
|
||||
for (let i = resetTrackingStack.length - 1; i >= 0; i--) {
|
||||
if (resetTrackingStack[i] !== undefined) {
|
||||
activeSub = resetTrackingStack[i]
|
||||
setActiveSub(resetTrackingStack[i])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -248,9 +266,21 @@ export function resetTracking(): void {
|
|||
)
|
||||
}
|
||||
if (resetTrackingStack.length) {
|
||||
activeSub = resetTrackingStack.pop()!
|
||||
setActiveSub(resetTrackingStack.pop()!)
|
||||
} 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 {
|
||||
if (activeSub instanceof ReactiveEffect) {
|
||||
activeSub.cleanup = fn
|
||||
activeSub.cleanups[activeSub.cleanupsLength++] = () => cleanupEffect(fn)
|
||||
} else if (__DEV__ && !failSilently) {
|
||||
warn(
|
||||
`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) {
|
||||
const { cleanup } = e
|
||||
e.cleanup = undefined
|
||||
if (cleanup !== undefined) {
|
||||
// run cleanup without active effect
|
||||
const prevSub = activeSub
|
||||
activeSub = undefined
|
||||
try {
|
||||
cleanup()
|
||||
} finally {
|
||||
activeSub = prevSub
|
||||
}
|
||||
function cleanupEffect(fn: () => void) {
|
||||
// run cleanup without active effect
|
||||
const prevSub = setActiveSub()
|
||||
try {
|
||||
fn()
|
||||
} finally {
|
||||
setActiveSub(prevSub)
|
||||
}
|
||||
}
|
||||
|
||||
export let activeSub: Subscriber | undefined = undefined
|
||||
|
||||
export function setActiveSub(sub: Subscriber | undefined): void {
|
||||
activeSub = sub
|
||||
}
|
||||
|
|
|
@ -1,57 +1,34 @@
|
|||
import { EffectFlags, type ReactiveEffect } from './effect'
|
||||
import { EffectFlags, cleanup } from './effect'
|
||||
import {
|
||||
type Link,
|
||||
type Subscriber,
|
||||
endTracking,
|
||||
startTracking,
|
||||
type ReactiveNode,
|
||||
link,
|
||||
setActiveSub,
|
||||
unlink,
|
||||
} from './system'
|
||||
import { warn } from './warning'
|
||||
|
||||
export let activeEffectScope: EffectScope | undefined
|
||||
|
||||
export class EffectScope implements Subscriber {
|
||||
// Subscriber: In order to collect orphans computeds
|
||||
export class EffectScope implements ReactiveNode {
|
||||
deps: Link | undefined = undefined
|
||||
depsTail: Link | undefined = undefined
|
||||
subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
flags: number = 0
|
||||
|
||||
/**
|
||||
* @internal track `on` calls, allow `on` call multiple times
|
||||
*/
|
||||
private _on = 0
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
effects: ReactiveEffect[] = []
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
cleanups: (() => void)[] = []
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
cleanupsLength = 0
|
||||
|
||||
/**
|
||||
* only assigned by undetached scope
|
||||
* @internal
|
||||
*/
|
||||
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
|
||||
constructor(detached = false) {
|
||||
if (!detached && activeEffectScope) {
|
||||
link(this, activeEffectScope)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -62,15 +39,12 @@ export class EffectScope implements Subscriber {
|
|||
pause(): void {
|
||||
if (!(this.flags & EffectFlags.PAUSED)) {
|
||||
this.flags |= EffectFlags.PAUSED
|
||||
let i, l
|
||||
if (this.scopes) {
|
||||
for (i = 0, l = this.scopes.length; i < l; i++) {
|
||||
this.scopes[i].pause()
|
||||
for (let link = this.deps; link !== undefined; link = link.nextDep) {
|
||||
const dep = link.dep
|
||||
if ('pause' in dep) {
|
||||
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.
|
||||
*/
|
||||
resume(): void {
|
||||
if (this.flags & EffectFlags.PAUSED) {
|
||||
this.flags &= ~EffectFlags.PAUSED
|
||||
let i, l
|
||||
if (this.scopes) {
|
||||
for (i = 0, l = this.scopes.length; i < l; i++) {
|
||||
this.scopes[i].resume()
|
||||
const flags = this.flags
|
||||
if (flags & EffectFlags.PAUSED) {
|
||||
this.flags = flags & ~EffectFlags.PAUSED
|
||||
for (let link = this.deps; link !== undefined; link = link.nextDep) {
|
||||
const dep = link.dep
|
||||
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 {
|
||||
if (this.active) {
|
||||
const prevEffectScope = activeEffectScope
|
||||
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
|
||||
const prevSub = setActiveSub()
|
||||
const prevScope = activeEffectScope
|
||||
try {
|
||||
activeEffectScope = this
|
||||
return fn()
|
||||
} finally {
|
||||
activeEffectScope = prevScope
|
||||
setActiveSub(prevSub)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be called on non-detached scopes
|
||||
* @internal
|
||||
*/
|
||||
off(): void {
|
||||
if (this._on > 0 && --this._on === 0) {
|
||||
activeEffectScope = this.prevScope
|
||||
this.prevScope = undefined
|
||||
stop(): void {
|
||||
if (!this.active) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
stop(fromParent?: boolean): void {
|
||||
if (this.active) {
|
||||
this.flags |= EffectFlags.STOP
|
||||
startTracking(this)
|
||||
endTracking(this)
|
||||
let i, l
|
||||
for (i = 0, l = this.effects.length; i < l; i++) {
|
||||
this.effects[i].stop()
|
||||
this.flags = EffectFlags.STOP
|
||||
let dep = this.deps
|
||||
while (dep !== undefined) {
|
||||
const node = dep.dep
|
||||
if ('stop' in node) {
|
||||
dep = dep.nextDep
|
||||
node.stop()
|
||||
} else {
|
||||
dep = unlink(dep, this)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
export function setCurrentScope(scope?: EffectScope): EffectScope | undefined {
|
||||
try {
|
||||
return activeEffectScope
|
||||
} finally {
|
||||
activeEffectScope = scope
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a dispose callback on the current active effect scope. The
|
||||
* 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}
|
||||
*/
|
||||
export function onScopeDispose(fn: () => void, failSilently = false): void {
|
||||
if (activeEffectScope) {
|
||||
activeEffectScope.cleanups.push(fn)
|
||||
if (activeEffectScope !== undefined) {
|
||||
activeEffectScope.cleanups[activeEffectScope.cleanupsLength++] = fn
|
||||
} else if (__DEV__ && !failSilently) {
|
||||
warn(
|
||||
`onScopeDispose() is called when there is no active effect scope` +
|
||||
|
|
|
@ -76,6 +76,10 @@ export {
|
|||
effectScope,
|
||||
EffectScope,
|
||||
getCurrentScope,
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
setCurrentScope,
|
||||
onScopeDispose,
|
||||
} from './effectScope'
|
||||
export { reactiveReadArray, shallowReadArray } from './arrayInstrumentations'
|
||||
|
@ -86,8 +90,11 @@ export {
|
|||
traverse,
|
||||
onWatcherCleanup,
|
||||
WatchErrorCodes,
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
WatcherEffect,
|
||||
type WatchOptions,
|
||||
type WatchScheduler,
|
||||
type WatchStopHandle,
|
||||
type WatchHandle,
|
||||
type WatchEffect,
|
||||
|
@ -95,3 +102,7 @@ export {
|
|||
type WatchCallback,
|
||||
type OnCleanup,
|
||||
} from './watch'
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export { setActiveSub } from './system'
|
||||
|
|
|
@ -9,7 +9,6 @@ import type { ComputedRef, WritableComputedRef } from './computed'
|
|||
import { ReactiveFlags, TrackOpTypes, TriggerOpTypes } from './constants'
|
||||
import { onTrack, triggerEventInfos } from './debug'
|
||||
import { getDepFromReactive } from './dep'
|
||||
import { activeSub } from './effect'
|
||||
import {
|
||||
type Builtin,
|
||||
type ShallowReactiveMarker,
|
||||
|
@ -19,7 +18,17 @@ import {
|
|||
toRaw,
|
||||
toReactive,
|
||||
} 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
|
||||
export declare const RawSymbol: unique symbol
|
||||
|
@ -106,31 +115,46 @@ function createRef(rawValue: unknown, wrap?: <T>(v: T) => T) {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
class RefImpl<T = any> implements Dependency {
|
||||
// Dependency
|
||||
class RefImpl<T = any> implements ReactiveNode {
|
||||
subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
flags: _ReactiveFlags = _ReactiveFlags.Mutable
|
||||
|
||||
_value: T
|
||||
_wrap?: <T>(v: T) => T
|
||||
private _oldValue: 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) {
|
||||
this._rawValue = wrap ? toRaw(value) : value
|
||||
this._oldValue = this._rawValue = wrap ? toRaw(value) : value
|
||||
this._value = wrap ? wrap(value) : value
|
||||
this._wrap = wrap
|
||||
this[ReactiveFlags.IS_SHALLOW] = !wrap
|
||||
}
|
||||
|
||||
get dep() {
|
||||
get dep(): this {
|
||||
return this
|
||||
}
|
||||
|
||||
get value() {
|
||||
get value(): T {
|
||||
trackRef(this)
|
||||
if (this.flags & _ReactiveFlags.Dirty && this.update()) {
|
||||
const subs = this.subs
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs)
|
||||
}
|
||||
}
|
||||
return this._value
|
||||
}
|
||||
|
||||
|
@ -142,24 +166,36 @@ class RefImpl<T = any> implements Dependency {
|
|||
isReadonly(newValue)
|
||||
newValue = useDirectValue ? newValue : toRaw(newValue)
|
||||
if (hasChanged(newValue, oldValue)) {
|
||||
this.flags |= _ReactiveFlags.Dirty
|
||||
this._rawValue = newValue
|
||||
this._value =
|
||||
this._wrap && !useDirectValue ? this._wrap(newValue) : newValue
|
||||
if (__DEV__) {
|
||||
triggerEventInfos.push({
|
||||
target: this,
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
newValue,
|
||||
oldValue,
|
||||
})
|
||||
}
|
||||
triggerRef(this as unknown as Ref)
|
||||
if (__DEV__) {
|
||||
triggerEventInfos.pop()
|
||||
!useDirectValue && this._wrap ? this._wrap(newValue) : newValue
|
||||
const subs = this.subs
|
||||
if (subs !== undefined) {
|
||||
if (__DEV__) {
|
||||
triggerEventInfos.push({
|
||||
target: this,
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
newValue,
|
||||
oldValue,
|
||||
})
|
||||
}
|
||||
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
|
||||
if (dep !== undefined && dep.subs !== undefined) {
|
||||
propagate(dep.subs)
|
||||
shallowPropagate(dep.subs)
|
||||
if (!batchDepth) {
|
||||
flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function trackRef(dep: Dependency) {
|
||||
function trackRef(dep: ReactiveNode) {
|
||||
if (activeSub !== undefined) {
|
||||
if (__DEV__) {
|
||||
onTrack(activeSub!, {
|
||||
|
@ -296,10 +336,10 @@ export type CustomRefFactory<T> = (
|
|||
set: (value: T) => void
|
||||
}
|
||||
|
||||
class CustomRefImpl<T> implements Dependency {
|
||||
// Dependency
|
||||
class CustomRefImpl<T> implements ReactiveNode {
|
||||
subs: Link | undefined = undefined
|
||||
subsTail: Link | undefined = undefined
|
||||
flags: _ReactiveFlags = _ReactiveFlags.None
|
||||
|
||||
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
|
||||
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
|
||||
}
|
||||
|
||||
get dep(): Dependency | undefined {
|
||||
get dep(): ReactiveNode | undefined {
|
||||
return getDepFromReactive(toRaw(this._object), this._key)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,220 +1,255 @@
|
|||
/* 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 { ReactiveEffect as Effect } from './effect.js'
|
||||
import type { EffectScope } from './effectScope.js'
|
||||
import { warn } from './warning.js'
|
||||
|
||||
export interface Dependency {
|
||||
subs: Link | undefined
|
||||
subsTail: Link | undefined
|
||||
}
|
||||
|
||||
export interface Subscriber {
|
||||
flags: SubscriberFlags
|
||||
deps: Link | undefined
|
||||
depsTail: Link | undefined
|
||||
export interface ReactiveNode {
|
||||
deps?: Link
|
||||
depsTail?: Link
|
||||
subs?: Link
|
||||
subsTail?: Link
|
||||
flags: ReactiveFlags
|
||||
}
|
||||
|
||||
export interface Link {
|
||||
dep: Dependency | Computed
|
||||
sub: Subscriber | Computed | Effect
|
||||
dep: ReactiveNode | Computed | Effect | EffectScope
|
||||
sub: ReactiveNode | Computed | Effect | EffectScope
|
||||
prevSub: Link | undefined
|
||||
nextSub: Link | undefined
|
||||
prevDep: Link | undefined
|
||||
nextDep: Link | undefined
|
||||
}
|
||||
|
||||
export const enum SubscriberFlags {
|
||||
Computed = 1 << 0,
|
||||
Effect = 1 << 1,
|
||||
Tracking = 1 << 2,
|
||||
Recursed = 1 << 4,
|
||||
Dirty = 1 << 5,
|
||||
PendingComputed = 1 << 6,
|
||||
Propagated = Dirty | PendingComputed,
|
||||
interface Stack<T> {
|
||||
value: T
|
||||
prev: Stack<T> | undefined
|
||||
}
|
||||
|
||||
interface OneWayLink<T> {
|
||||
target: T
|
||||
linked: OneWayLink<T> | undefined
|
||||
export const enum ReactiveFlags {
|
||||
None = 0,
|
||||
Mutable = 1 << 0,
|
||||
Watching = 1 << 1,
|
||||
RecursedCheck = 1 << 2,
|
||||
Recursed = 1 << 3,
|
||||
Dirty = 1 << 4,
|
||||
Pending = 1 << 5,
|
||||
}
|
||||
|
||||
const notifyBuffer: (Effect | undefined)[] = []
|
||||
|
||||
let batchDepth = 0
|
||||
export let batchDepth = 0
|
||||
export let activeSub: ReactiveNode | undefined = undefined
|
||||
|
||||
let notifyIndex = 0
|
||||
let notifyBufferLength = 0
|
||||
|
||||
export function setActiveSub(sub?: ReactiveNode): ReactiveNode | undefined {
|
||||
try {
|
||||
return activeSub
|
||||
} finally {
|
||||
activeSub = sub
|
||||
}
|
||||
}
|
||||
|
||||
export function startBatch(): void {
|
||||
++batchDepth
|
||||
}
|
||||
|
||||
export function endBatch(): void {
|
||||
if (!--batchDepth) {
|
||||
processEffectNotifications()
|
||||
if (!--batchDepth && notifyBufferLength) {
|
||||
flush()
|
||||
}
|
||||
}
|
||||
|
||||
export function link(dep: Dependency, sub: Subscriber): Link | undefined {
|
||||
const currentDep = sub.depsTail
|
||||
if (currentDep !== undefined && currentDep.dep === dep) {
|
||||
export function link(dep: ReactiveNode, sub: ReactiveNode): void {
|
||||
const prevDep = sub.depsTail
|
||||
if (prevDep !== undefined && prevDep.dep === dep) {
|
||||
return
|
||||
}
|
||||
const nextDep = currentDep !== undefined ? currentDep.nextDep : sub.deps
|
||||
if (nextDep !== undefined && nextDep.dep === dep) {
|
||||
sub.depsTail = nextDep
|
||||
return
|
||||
let nextDep: Link | undefined = undefined
|
||||
const recursedCheck = sub.flags & ReactiveFlags.RecursedCheck
|
||||
if (recursedCheck) {
|
||||
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 (
|
||||
depLastSub !== undefined &&
|
||||
depLastSub.sub === sub &&
|
||||
isValidLink(depLastSub, sub)
|
||||
prevSub !== undefined &&
|
||||
prevSub.sub === sub &&
|
||||
(!recursedCheck || isValidLink(prevSub, sub))
|
||||
) {
|
||||
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 {
|
||||
let next = current.nextSub
|
||||
let branchs: OneWayLink<Link | undefined> | undefined
|
||||
let branchDepth = 0
|
||||
let targetFlag = SubscriberFlags.Dirty
|
||||
export function unlink(
|
||||
link: Link,
|
||||
sub: ReactiveNode = link.sub,
|
||||
): Link | undefined {
|
||||
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 {
|
||||
const sub = current.sub
|
||||
const subFlags = sub.flags
|
||||
const sub = link.sub
|
||||
|
||||
let shouldNotify = false
|
||||
let flags = sub.flags
|
||||
|
||||
if (
|
||||
!(
|
||||
subFlags &
|
||||
(SubscriberFlags.Tracking |
|
||||
SubscriberFlags.Recursed |
|
||||
SubscriberFlags.Propagated)
|
||||
)
|
||||
) {
|
||||
sub.flags = subFlags | targetFlag
|
||||
shouldNotify = true
|
||||
} else if (
|
||||
subFlags & SubscriberFlags.Recursed &&
|
||||
!(subFlags & SubscriberFlags.Tracking)
|
||||
) {
|
||||
sub.flags = (subFlags & ~SubscriberFlags.Recursed) | targetFlag
|
||||
shouldNotify = true
|
||||
} else if (
|
||||
!(subFlags & SubscriberFlags.Propagated) &&
|
||||
isValidLink(current, sub)
|
||||
) {
|
||||
sub.flags = subFlags | SubscriberFlags.Recursed | targetFlag
|
||||
shouldNotify = (sub as Dependency).subs !== undefined
|
||||
}
|
||||
|
||||
if (shouldNotify) {
|
||||
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 (flags & (ReactiveFlags.Mutable | ReactiveFlags.Watching)) {
|
||||
if (
|
||||
!(
|
||||
flags &
|
||||
(ReactiveFlags.RecursedCheck |
|
||||
ReactiveFlags.Recursed |
|
||||
ReactiveFlags.Dirty |
|
||||
ReactiveFlags.Pending)
|
||||
)
|
||||
) {
|
||||
sub.flags = flags | ReactiveFlags.Pending
|
||||
} else if (
|
||||
!(flags & (ReactiveFlags.RecursedCheck | ReactiveFlags.Recursed))
|
||||
) {
|
||||
flags = ReactiveFlags.None
|
||||
} else if (!(flags & ReactiveFlags.RecursedCheck)) {
|
||||
sub.flags = (flags & ~ReactiveFlags.Recursed) | ReactiveFlags.Pending
|
||||
} else if (
|
||||
!(flags & (ReactiveFlags.Dirty | ReactiveFlags.Pending)) &&
|
||||
isValidLink(link, sub)
|
||||
) {
|
||||
sub.flags = flags | ReactiveFlags.Recursed | ReactiveFlags.Pending
|
||||
flags &= ReactiveFlags.Mutable
|
||||
} else {
|
||||
flags = ReactiveFlags.None
|
||||
}
|
||||
if (subFlags & SubscriberFlags.Effect) {
|
||||
|
||||
if (flags & ReactiveFlags.Watching) {
|
||||
notifyBuffer[notifyBufferLength++] = sub as Effect
|
||||
}
|
||||
} else if (!(subFlags & (SubscriberFlags.Tracking | targetFlag))) {
|
||||
sub.flags = subFlags | targetFlag
|
||||
} else if (
|
||||
!(subFlags & targetFlag) &&
|
||||
subFlags & SubscriberFlags.Propagated &&
|
||||
isValidLink(current, sub)
|
||||
) {
|
||||
sub.flags = subFlags | targetFlag
|
||||
|
||||
if (flags & ReactiveFlags.Mutable) {
|
||||
const subSubs = sub.subs
|
||||
if (subSubs !== undefined) {
|
||||
link = subSubs
|
||||
if (subSubs.nextSub !== undefined) {
|
||||
stack = { value: next, prev: stack }
|
||||
next = link.nextSub
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((current = next!) !== undefined) {
|
||||
next = current.nextSub
|
||||
targetFlag = branchDepth
|
||||
? SubscriberFlags.PendingComputed
|
||||
: SubscriberFlags.Dirty
|
||||
if ((link = next!) !== undefined) {
|
||||
next = link.nextSub
|
||||
continue
|
||||
}
|
||||
|
||||
while (branchDepth--) {
|
||||
current = branchs!.target!
|
||||
branchs = branchs!.linked
|
||||
if (current !== undefined) {
|
||||
next = current.nextSub
|
||||
targetFlag = branchDepth
|
||||
? SubscriberFlags.PendingComputed
|
||||
: SubscriberFlags.Dirty
|
||||
while (stack !== undefined) {
|
||||
link = stack.value!
|
||||
stack = stack.prev
|
||||
if (link !== undefined) {
|
||||
next = link.nextSub
|
||||
continue top
|
||||
}
|
||||
}
|
||||
|
||||
break
|
||||
} while (true)
|
||||
|
||||
if (!batchDepth) {
|
||||
processEffectNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
export function startTracking(sub: Subscriber): void {
|
||||
export function startTracking(sub: ReactiveNode): ReactiveNode | undefined {
|
||||
sub.depsTail = undefined
|
||||
sub.flags =
|
||||
(sub.flags & ~(SubscriberFlags.Recursed | SubscriberFlags.Propagated)) |
|
||||
SubscriberFlags.Tracking
|
||||
(sub.flags &
|
||||
~(ReactiveFlags.Recursed | ReactiveFlags.Dirty | ReactiveFlags.Pending)) |
|
||||
ReactiveFlags.RecursedCheck
|
||||
return setActiveSub(sub)
|
||||
}
|
||||
|
||||
export function endTracking(sub: Subscriber): void {
|
||||
const depsTail = sub.depsTail
|
||||
if (depsTail !== 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,
|
||||
export function endTracking(
|
||||
sub: ReactiveNode,
|
||||
prevSub: ReactiveNode | undefined,
|
||||
): void {
|
||||
if (flags & SubscriberFlags.Dirty || checkDirty(computed.deps!)) {
|
||||
if (computed.update()) {
|
||||
const subs = computed.subs
|
||||
if (subs !== undefined) {
|
||||
shallowPropagate(subs)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
computed.flags = flags & ~SubscriberFlags.PendingComputed
|
||||
if (__DEV__ && activeSub !== sub) {
|
||||
warn(
|
||||
'Active effect was not restored correctly - ' +
|
||||
'this is likely a Vue internal bug.',
|
||||
)
|
||||
}
|
||||
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) {
|
||||
const effect = notifyBuffer[notifyIndex]!
|
||||
notifyBuffer[notifyIndex++] = undefined
|
||||
|
@ -224,109 +259,71 @@ export function processEffectNotifications(): void {
|
|||
notifyBufferLength = 0
|
||||
}
|
||||
|
||||
function linkNewDep(
|
||||
dep: Dependency,
|
||||
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
|
||||
export function checkDirty(link: Link, sub: ReactiveNode): boolean {
|
||||
let stack: Stack<Link> | undefined
|
||||
let checkDepth = 0
|
||||
let dirty: boolean
|
||||
|
||||
top: do {
|
||||
dirty = false
|
||||
const dep = current.dep
|
||||
const dep = link.dep
|
||||
const depFlags = dep.flags
|
||||
|
||||
if (current.sub.flags & SubscriberFlags.Dirty) {
|
||||
let dirty = false
|
||||
|
||||
if (sub.flags & ReactiveFlags.Dirty) {
|
||||
dirty = true
|
||||
} else if ('flags' in dep) {
|
||||
const depFlags = dep.flags
|
||||
if (
|
||||
(depFlags & (SubscriberFlags.Computed | SubscriberFlags.Dirty)) ===
|
||||
(SubscriberFlags.Computed | SubscriberFlags.Dirty)
|
||||
) {
|
||||
if ((dep as Computed).update()) {
|
||||
const subs = dep.subs!
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs)
|
||||
}
|
||||
dirty = true
|
||||
} else if (
|
||||
(depFlags & (ReactiveFlags.Mutable | ReactiveFlags.Dirty)) ===
|
||||
(ReactiveFlags.Mutable | ReactiveFlags.Dirty)
|
||||
) {
|
||||
if ((dep as Computed).update()) {
|
||||
const subs = dep.subs!
|
||||
if (subs.nextSub !== undefined) {
|
||||
shallowPropagate(subs)
|
||||
}
|
||||
} else if (
|
||||
(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
|
||||
dirty = true
|
||||
}
|
||||
} 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) {
|
||||
current = current.nextDep
|
||||
if (!dirty && link.nextDep !== undefined) {
|
||||
link = link.nextDep
|
||||
continue
|
||||
}
|
||||
|
||||
while (checkDepth) {
|
||||
--checkDepth
|
||||
const sub = current.sub as Computed
|
||||
const firstSub = sub.subs!
|
||||
const hasMultipleSubs = firstSub.nextSub !== undefined
|
||||
if (hasMultipleSubs) {
|
||||
link = stack!.value
|
||||
stack = stack!.prev
|
||||
} else {
|
||||
link = firstSub
|
||||
}
|
||||
if (dirty) {
|
||||
if (sub.update()) {
|
||||
if (firstSub.nextSub !== undefined) {
|
||||
current = prevLinks!.target
|
||||
prevLinks = prevLinks!.linked
|
||||
if ((sub as Computed).update()) {
|
||||
if (hasMultipleSubs) {
|
||||
shallowPropagate(firstSub)
|
||||
} else {
|
||||
current = firstSub
|
||||
}
|
||||
sub = link.sub
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
sub.flags &= ~SubscriberFlags.PendingComputed
|
||||
sub.flags &= ~ReactiveFlags.Pending
|
||||
}
|
||||
if (firstSub.nextSub !== undefined) {
|
||||
current = prevLinks!.target
|
||||
prevLinks = prevLinks!.linked
|
||||
} else {
|
||||
current = firstSub
|
||||
}
|
||||
if (current.nextDep !== undefined) {
|
||||
current = current.nextDep
|
||||
sub = link.sub
|
||||
if (link.nextDep !== undefined) {
|
||||
link = link.nextDep
|
||||
continue top
|
||||
}
|
||||
dirty = false
|
||||
|
@ -336,21 +333,22 @@ function checkDirty(current: Link): boolean {
|
|||
} while (true)
|
||||
}
|
||||
|
||||
function shallowPropagate(link: Link): void {
|
||||
export function shallowPropagate(link: Link): void {
|
||||
do {
|
||||
const sub = link.sub
|
||||
const nextSub = link.nextSub
|
||||
const subFlags = sub.flags
|
||||
if (
|
||||
(subFlags & (SubscriberFlags.PendingComputed | SubscriberFlags.Dirty)) ===
|
||||
SubscriberFlags.PendingComputed
|
||||
(subFlags & (ReactiveFlags.Pending | ReactiveFlags.Dirty)) ===
|
||||
ReactiveFlags.Pending
|
||||
) {
|
||||
sub.flags = subFlags | SubscriberFlags.Dirty
|
||||
sub.flags = subFlags | ReactiveFlags.Dirty
|
||||
}
|
||||
link = link.nextSub!
|
||||
link = nextSub!
|
||||
} while (link !== undefined)
|
||||
}
|
||||
|
||||
function isValidLink(checkLink: Link, sub: Subscriber): boolean {
|
||||
function isValidLink(checkLink: Link, sub: ReactiveNode): boolean {
|
||||
const depsTail = sub.depsTail
|
||||
if (depsTail !== undefined) {
|
||||
let link = sub.deps!
|
||||
|
@ -366,40 +364,3 @@ function isValidLink(checkLink: Link, sub: Subscriber): boolean {
|
|||
}
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -8,20 +8,13 @@ import {
|
|||
isObject,
|
||||
isPlainObject,
|
||||
isSet,
|
||||
remove,
|
||||
} from '@vue/shared'
|
||||
import type { ComputedRef } from './computed'
|
||||
import { ReactiveFlags } from './constants'
|
||||
import {
|
||||
type DebuggerOptions,
|
||||
type EffectScheduler,
|
||||
ReactiveEffect,
|
||||
pauseTracking,
|
||||
resetTracking,
|
||||
} from './effect'
|
||||
import { getCurrentScope } from './effectScope'
|
||||
import { type DebuggerOptions, ReactiveEffect, cleanup } from './effect'
|
||||
import { isReactive, isShallow } from './reactive'
|
||||
import { type Ref, isRef } from './ref'
|
||||
import { setActiveSub } from './system'
|
||||
import { warn } from './warning'
|
||||
|
||||
// These errors were transferred from `packages/runtime-core/src/errorHandling.ts`
|
||||
|
@ -49,12 +42,7 @@ export interface WatchOptions<Immediate = boolean> extends DebuggerOptions {
|
|||
immediate?: Immediate
|
||||
deep?: boolean | number
|
||||
once?: boolean
|
||||
scheduler?: WatchScheduler
|
||||
onWarn?: (msg: string, ...args: any[]) => void
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
augmentJob?: (job: (...args: any[]) => void) => void
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -76,10 +64,7 @@ export interface WatchHandle extends WatchStopHandle {
|
|||
// initial value for watchers to trigger on undefined initial values
|
||||
const INITIAL_WATCHER_VALUE = {}
|
||||
|
||||
export type WatchScheduler = (job: () => void, isFirstRun: boolean) => void
|
||||
|
||||
const cleanupMap: WeakMap<ReactiveEffect, (() => void)[]> = new WeakMap()
|
||||
let activeWatcher: ReactiveEffect | undefined = undefined
|
||||
let activeWatcher: WatcherEffect | undefined = undefined
|
||||
|
||||
/**
|
||||
* Returns the current active effect if there is one.
|
||||
|
@ -102,12 +87,16 @@ export function getCurrentWatcher(): ReactiveEffect<any> | undefined {
|
|||
export function onWatcherCleanup(
|
||||
cleanupFn: () => void,
|
||||
failSilently = false,
|
||||
owner: ReactiveEffect | undefined = activeWatcher,
|
||||
owner: WatcherEffect | undefined = activeWatcher,
|
||||
): void {
|
||||
if (owner) {
|
||||
let cleanups = cleanupMap.get(owner)
|
||||
if (!cleanups) cleanupMap.set(owner, (cleanups = []))
|
||||
cleanups.push(cleanupFn)
|
||||
const { call } = owner.options
|
||||
if (call) {
|
||||
owner.cleanups[owner.cleanupsLength++] = () =>
|
||||
call(cleanupFn, WatchErrorCodes.WATCH_CLEANUP)
|
||||
} else {
|
||||
owner.cleanups[owner.cleanupsLength++] = cleanupFn
|
||||
}
|
||||
} else if (__DEV__ && !failSilently) {
|
||||
warn(
|
||||
`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(
|
||||
source: WatchSource | WatchSource[] | WatchEffect | object,
|
||||
cb?: WatchCallback | null,
|
||||
options: WatchOptions = EMPTY_OBJ,
|
||||
): WatchHandle {
|
||||
const { immediate, deep, once, scheduler, augmentJob, call } = options
|
||||
const effect = new WatcherEffect(source, cb, options)
|
||||
|
||||
const warnInvalidSource = (s: unknown) => {
|
||||
;(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.`,
|
||||
)
|
||||
}
|
||||
effect.run(true)
|
||||
|
||||
const reactiveGetter = (source: object) => {
|
||||
// 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)
|
||||
}
|
||||
const stop = effect.stop.bind(effect) as WatchHandle
|
||||
stop.pause = effect.pause.bind(effect)
|
||||
stop.resume = effect.resume.bind(effect)
|
||||
stop.stop = stop
|
||||
|
||||
let effect: ReactiveEffect
|
||||
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
|
||||
return stop
|
||||
}
|
||||
|
||||
export function traverse(
|
||||
|
|
|
@ -25,7 +25,9 @@ import {
|
|||
} from '@vue/runtime-test'
|
||||
import {
|
||||
type DebuggerEvent,
|
||||
type EffectScope,
|
||||
ITERATE_KEY,
|
||||
ReactiveEffect,
|
||||
type Ref,
|
||||
type ShallowRef,
|
||||
TrackOpTypes,
|
||||
|
@ -503,6 +505,52 @@ describe('api: watch', () => {
|
|||
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 () => {
|
||||
const count = ref(0)
|
||||
const count2 = ref(0)
|
||||
|
@ -1332,16 +1380,15 @@ describe('api: watch', () => {
|
|||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
expect(instance!).toBeDefined()
|
||||
expect(instance!.scope.effects).toBeInstanceOf(Array)
|
||||
// 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
|
||||
|
||||
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 ', () => {
|
||||
|
@ -1489,7 +1536,7 @@ describe('api: watch', () => {
|
|||
createApp(Comp).mount(root)
|
||||
// should not record watcher in detached scope and only the instance's
|
||||
// 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 () => {
|
||||
|
@ -1595,7 +1642,7 @@ describe('api: watch', () => {
|
|||
|
||||
num.value++
|
||||
await nextTick()
|
||||
// would not be calld when value>1
|
||||
// would not be called when value>1
|
||||
expect(spy1).toHaveBeenCalledTimes(1)
|
||||
expect(spy2).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
@ -1796,9 +1843,9 @@ describe('api: watch', () => {
|
|||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
createApp(Comp).mount(root)
|
||||
expect(instance!.scope.effects.length).toBe(2)
|
||||
expect(getEffectsCount(instance!.scope)).toBe(2)
|
||||
unwatch!()
|
||||
expect(instance!.scope.effects.length).toBe(1)
|
||||
expect(getEffectsCount(instance!.scope)).toBe(1)
|
||||
|
||||
const scope = effectScope()
|
||||
scope.run(() => {
|
||||
|
@ -1806,14 +1853,14 @@ describe('api: watch', () => {
|
|||
console.log(num.value)
|
||||
})
|
||||
})
|
||||
expect(scope.effects.length).toBe(1)
|
||||
expect(getEffectsCount(scope)).toBe(1)
|
||||
unwatch!()
|
||||
expect(scope.effects.length).toBe(0)
|
||||
expect(getEffectsCount(scope)).toBe(0)
|
||||
|
||||
scope.run(() => {
|
||||
watch(num, () => {}, { once: true, immediate: true })
|
||||
})
|
||||
expect(scope.effects.length).toBe(0)
|
||||
expect(getEffectsCount(scope)).toBe(0)
|
||||
})
|
||||
|
||||
// simplified case of VueUse syncRef
|
||||
|
@ -1874,7 +1921,7 @@ describe('api: watch', () => {
|
|||
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')
|
||||
warn.mockImplementation(() => {})
|
||||
const ERROR_IN_SCOPE = 'ERROR_IN_SCOPE'
|
||||
|
@ -2011,3 +2058,13 @@ describe('api: watch', () => {
|
|||
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
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import {
|
|||
nodeOps,
|
||||
ref,
|
||||
render,
|
||||
useSlots,
|
||||
} from '@vue/runtime-test'
|
||||
import { createBlock, normalizeVNode } from '../src/vnode'
|
||||
import { createSlots } from '../src/helpers/createSlots'
|
||||
|
@ -42,6 +43,29 @@ describe('component: slots', () => {
|
|||
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)', () => {
|
||||
const { slots } = renderWithSlots({
|
||||
_inner: '_inner',
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
render,
|
||||
serialize,
|
||||
serializeInner,
|
||||
useModel,
|
||||
withDirectives,
|
||||
} from '@vue/runtime-test'
|
||||
import {
|
||||
|
@ -144,6 +145,62 @@ describe('renderer: teleport', () => {
|
|||
`"<!--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) {
|
||||
|
|
|
@ -1654,6 +1654,29 @@ describe('SSR hydration', () => {
|
|||
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', () => {
|
||||
const show = false
|
||||
const { vnode, container } = mountWithHydration(
|
||||
|
|
|
@ -573,7 +573,7 @@ describe('attribute fallthrough', () => {
|
|||
const Child = {
|
||||
props: [],
|
||||
render() {
|
||||
return openBlock(), createBlock('div')
|
||||
return (openBlock(), createBlock('div'))
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -416,7 +416,7 @@ describe('renderer: fragment', () => {
|
|||
const root = nodeOps.createElement('div')
|
||||
|
||||
const renderFn = () => {
|
||||
return openBlock(true), createBlock(Fragment, null)
|
||||
return (openBlock(true), createBlock(Fragment, null))
|
||||
}
|
||||
|
||||
render(renderFn(), root)
|
||||
|
|
|
@ -861,6 +861,114 @@ describe('renderer: optimized mode', () => {
|
|||
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
|
||||
test('should not take unmount children fast path /w Suspense', async () => {
|
||||
const show = ref(true)
|
||||
|
|
|
@ -179,6 +179,37 @@ describe('api: template refs', () => {
|
|||
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 () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const spy = vi.fn()
|
||||
|
|
|
@ -49,8 +49,8 @@ describe('scheduler', () => {
|
|||
const job1 = () => {
|
||||
calls.push('job1')
|
||||
|
||||
queueJob(job2)
|
||||
queueJob(job3)
|
||||
queueJob(job2, 10)
|
||||
queueJob(job3, 1)
|
||||
}
|
||||
|
||||
const job2 = () => {
|
||||
|
@ -58,12 +58,10 @@ describe('scheduler', () => {
|
|||
queueJob(job4)
|
||||
queueJob(job5)
|
||||
}
|
||||
job2.id = 10
|
||||
|
||||
const job3 = () => {
|
||||
calls.push('job3')
|
||||
}
|
||||
job3.id = 1
|
||||
|
||||
const job4 = () => {
|
||||
calls.push('job4')
|
||||
|
@ -125,9 +123,8 @@ describe('scheduler', () => {
|
|||
calls.push('cb1')
|
||||
queueJob(job1)
|
||||
}
|
||||
cb1.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
queueJob(cb1)
|
||||
queueJob(cb1, undefined, true)
|
||||
await nextTick()
|
||||
expect(calls).toEqual(['cb1', 'job1'])
|
||||
})
|
||||
|
@ -137,30 +134,23 @@ describe('scheduler', () => {
|
|||
const job1 = () => {
|
||||
calls.push('job1')
|
||||
}
|
||||
job1.id = 1
|
||||
|
||||
const cb1: SchedulerJob = () => {
|
||||
calls.push('cb1')
|
||||
queueJob(job1)
|
||||
queueJob(job1, 1)
|
||||
// cb2 should execute before the job
|
||||
queueJob(cb2)
|
||||
queueJob(cb3)
|
||||
queueJob(cb2, 1, true)
|
||||
queueJob(cb3, 1, true)
|
||||
}
|
||||
cb1.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
const cb2: SchedulerJob = () => {
|
||||
calls.push('cb2')
|
||||
}
|
||||
cb2.flags! |= SchedulerJobFlags.PRE
|
||||
cb2.id = 1
|
||||
|
||||
const cb3: SchedulerJob = () => {
|
||||
calls.push('cb3')
|
||||
}
|
||||
cb3.flags! |= SchedulerJobFlags.PRE
|
||||
cb3.id = 1
|
||||
|
||||
queueJob(cb1)
|
||||
queueJob(cb1, undefined, true)
|
||||
await nextTick()
|
||||
expect(calls).toEqual(['cb1', 'cb2', 'cb3', 'job1'])
|
||||
})
|
||||
|
@ -170,41 +160,30 @@ describe('scheduler', () => {
|
|||
const job1: SchedulerJob = () => {
|
||||
calls.push('job1')
|
||||
}
|
||||
job1.id = 1
|
||||
job1.flags! |= SchedulerJobFlags.PRE
|
||||
const job2: SchedulerJob = () => {
|
||||
calls.push('job2')
|
||||
queueJob(job5)
|
||||
queueJob(job6)
|
||||
queueJob(job5, 2)
|
||||
queueJob(job6, 2, true)
|
||||
}
|
||||
job2.id = 2
|
||||
job2.flags! |= SchedulerJobFlags.PRE
|
||||
const job3: SchedulerJob = () => {
|
||||
calls.push('job3')
|
||||
}
|
||||
job3.id = 2
|
||||
job3.flags! |= SchedulerJobFlags.PRE
|
||||
const job4: SchedulerJob = () => {
|
||||
calls.push('job4')
|
||||
}
|
||||
job4.id = 3
|
||||
job4.flags! |= SchedulerJobFlags.PRE
|
||||
const job5: SchedulerJob = () => {
|
||||
calls.push('job5')
|
||||
}
|
||||
job5.id = 2
|
||||
const job6: SchedulerJob = () => {
|
||||
calls.push('job6')
|
||||
}
|
||||
job6.id = 2
|
||||
job6.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
// We need several jobs to test this properly, otherwise
|
||||
// findInsertionIndex can yield the correct index by chance
|
||||
queueJob(job4)
|
||||
queueJob(job2)
|
||||
queueJob(job3)
|
||||
queueJob(job1)
|
||||
queueJob(job4, 3, true)
|
||||
queueJob(job2, 2, true)
|
||||
queueJob(job3, 2, true)
|
||||
queueJob(job1, 1, true)
|
||||
|
||||
await nextTick()
|
||||
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
|
||||
// directly inside `updateComponentPreRender` to avoid non atomic
|
||||
// cb triggers (#1763)
|
||||
queueJob(cb1)
|
||||
queueJob(cb2)
|
||||
queueJob(cb1, undefined, true)
|
||||
queueJob(cb2, undefined, true)
|
||||
flushPreFlushCbs()
|
||||
calls.push('job1')
|
||||
}
|
||||
|
@ -227,11 +206,9 @@ describe('scheduler', () => {
|
|||
// a cb triggers its parent job, which should be skipped
|
||||
queueJob(job1)
|
||||
}
|
||||
cb1.flags! |= SchedulerJobFlags.PRE
|
||||
const cb2: SchedulerJob = () => {
|
||||
calls.push('cb2')
|
||||
}
|
||||
cb2.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
queueJob(job1)
|
||||
await nextTick()
|
||||
|
@ -242,29 +219,24 @@ describe('scheduler', () => {
|
|||
const calls: string[] = []
|
||||
const job1: SchedulerJob = () => {
|
||||
calls.push('job1')
|
||||
queueJob(job3)
|
||||
queueJob(job4)
|
||||
queueJob(job3, undefined, true)
|
||||
queueJob(job4, undefined, true)
|
||||
}
|
||||
// job1 has no id
|
||||
job1.flags! |= SchedulerJobFlags.PRE
|
||||
const job2: SchedulerJob = () => {
|
||||
calls.push('job2')
|
||||
}
|
||||
job2.id = 1
|
||||
job2.flags! |= SchedulerJobFlags.PRE
|
||||
const job3: SchedulerJob = () => {
|
||||
calls.push('job3')
|
||||
}
|
||||
// job3 has no id
|
||||
job3.flags! |= SchedulerJobFlags.PRE
|
||||
const job4: SchedulerJob = () => {
|
||||
calls.push('job4')
|
||||
}
|
||||
// job4 has no id
|
||||
job4.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
queueJob(job1)
|
||||
queueJob(job2)
|
||||
queueJob(job1, undefined, true)
|
||||
queueJob(job2, 1, true)
|
||||
await nextTick()
|
||||
expect(calls).toEqual(['job1', 'job3', 'job4', 'job2'])
|
||||
})
|
||||
|
@ -273,9 +245,8 @@ describe('scheduler', () => {
|
|||
it('queue preFlushCb inside postFlushCb', async () => {
|
||||
const spy = vi.fn()
|
||||
const cb: SchedulerJob = () => spy()
|
||||
cb.flags! |= SchedulerJobFlags.PRE
|
||||
queuePostFlushCb(() => {
|
||||
queueJob(cb)
|
||||
queueJob(cb, undefined, true)
|
||||
})
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalled()
|
||||
|
@ -448,16 +419,13 @@ describe('scheduler', () => {
|
|||
const job1: SchedulerJob = () => {
|
||||
calls.push('job1')
|
||||
}
|
||||
job1.id = 1
|
||||
|
||||
const job2: SchedulerJob = () => {
|
||||
calls.push('job2')
|
||||
}
|
||||
job2.id = 2
|
||||
|
||||
queuePostFlushCb(() => {
|
||||
queueJob(job2)
|
||||
queueJob(job1)
|
||||
queueJob(job2, 2)
|
||||
queueJob(job1, 1)
|
||||
})
|
||||
|
||||
await nextTick()
|
||||
|
@ -471,21 +439,16 @@ describe('scheduler', () => {
|
|||
const job1 = () => calls.push('job1')
|
||||
// job1 has no id
|
||||
const job2 = () => calls.push('job2')
|
||||
job2.id = 2
|
||||
const job3 = () => calls.push('job3')
|
||||
job3.id = 1
|
||||
const job4: SchedulerJob = () => calls.push('job4')
|
||||
job4.id = 2
|
||||
job4.flags! |= SchedulerJobFlags.PRE
|
||||
const job5: SchedulerJob = () => calls.push('job5')
|
||||
// job5 has no id
|
||||
job5.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
queueJob(job1)
|
||||
queueJob(job2)
|
||||
queueJob(job3)
|
||||
queueJob(job4)
|
||||
queueJob(job5)
|
||||
queueJob(job2, 2)
|
||||
queueJob(job3, 1)
|
||||
queueJob(job4, 2, true)
|
||||
queueJob(job5, undefined, true)
|
||||
await nextTick()
|
||||
expect(calls).toEqual(['job5', 'job3', 'job4', 'job2', 'job1'])
|
||||
})
|
||||
|
@ -495,13 +458,11 @@ describe('scheduler', () => {
|
|||
const cb1 = () => calls.push('cb1')
|
||||
// cb1 has no id
|
||||
const cb2 = () => calls.push('cb2')
|
||||
cb2.id = 2
|
||||
const cb3 = () => calls.push('cb3')
|
||||
cb3.id = 1
|
||||
|
||||
queuePostFlushCb(cb1)
|
||||
queuePostFlushCb(cb2)
|
||||
queuePostFlushCb(cb3)
|
||||
queuePostFlushCb(cb2, 2)
|
||||
queuePostFlushCb(cb3, 1)
|
||||
await nextTick()
|
||||
expect(calls).toEqual(['cb3', 'cb2', 'cb1'])
|
||||
})
|
||||
|
@ -550,13 +511,10 @@ describe('scheduler', () => {
|
|||
throw err
|
||||
}
|
||||
})
|
||||
job1.id = 1
|
||||
|
||||
const job2: SchedulerJob = vi.fn()
|
||||
job2.id = 2
|
||||
|
||||
queueJob(job1)
|
||||
queueJob(job2)
|
||||
queueJob(job1, 1)
|
||||
queueJob(job2, 2)
|
||||
|
||||
try {
|
||||
await nextTick()
|
||||
|
@ -570,8 +528,8 @@ describe('scheduler', () => {
|
|||
expect(job1).toHaveBeenCalledTimes(1)
|
||||
expect(job2).toHaveBeenCalledTimes(0)
|
||||
|
||||
queueJob(job1)
|
||||
queueJob(job2)
|
||||
queueJob(job1, 1)
|
||||
queueJob(job2, 2)
|
||||
|
||||
await nextTick()
|
||||
|
||||
|
@ -622,11 +580,10 @@ describe('scheduler', () => {
|
|||
|
||||
test('recursive jobs can only be queued once non-recursively', async () => {
|
||||
const job: SchedulerJob = vi.fn()
|
||||
job.id = 1
|
||||
job.flags = SchedulerJobFlags.ALLOW_RECURSE
|
||||
|
||||
queueJob(job)
|
||||
queueJob(job)
|
||||
queueJob(job, 1)
|
||||
queueJob(job, 1)
|
||||
|
||||
await nextTick()
|
||||
|
||||
|
@ -638,15 +595,14 @@ describe('scheduler', () => {
|
|||
|
||||
const job: SchedulerJob = vi.fn(() => {
|
||||
if (recurse) {
|
||||
queueJob(job)
|
||||
queueJob(job)
|
||||
queueJob(job, 1)
|
||||
queueJob(job, 1)
|
||||
recurse = false
|
||||
}
|
||||
})
|
||||
job.id = 1
|
||||
job.flags = SchedulerJobFlags.ALLOW_RECURSE
|
||||
|
||||
queueJob(job)
|
||||
queueJob(job, 1)
|
||||
|
||||
await nextTick()
|
||||
|
||||
|
@ -659,22 +615,19 @@ describe('scheduler', () => {
|
|||
const job1: SchedulerJob = () => {
|
||||
if (recurse) {
|
||||
// job2 is already queued, so this shouldn't do anything
|
||||
queueJob(job2)
|
||||
queueJob(job2, 2)
|
||||
recurse = false
|
||||
}
|
||||
}
|
||||
job1.id = 1
|
||||
|
||||
const job2: SchedulerJob = vi.fn(() => {
|
||||
if (recurse) {
|
||||
queueJob(job1)
|
||||
queueJob(job2)
|
||||
queueJob(job1, 1)
|
||||
queueJob(job2, 2)
|
||||
}
|
||||
})
|
||||
job2.id = 2
|
||||
job2.flags = SchedulerJobFlags.ALLOW_RECURSE
|
||||
|
||||
queueJob(job2)
|
||||
queueJob(job2, 2)
|
||||
|
||||
await nextTick()
|
||||
|
||||
|
@ -685,40 +638,35 @@ describe('scheduler', () => {
|
|||
let recurse = true
|
||||
|
||||
const job1: SchedulerJob = vi.fn(() => {
|
||||
queueJob(job3)
|
||||
queueJob(job3)
|
||||
queueJob(job3, 3, true)
|
||||
queueJob(job3, 3, true)
|
||||
flushPreFlushCbs()
|
||||
})
|
||||
job1.id = 1
|
||||
job1.flags = SchedulerJobFlags.PRE
|
||||
|
||||
const job2: SchedulerJob = vi.fn(() => {
|
||||
if (recurse) {
|
||||
// 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
|
||||
queueJob(job3)
|
||||
queueJob(job3, 3, true)
|
||||
recurse = false
|
||||
}
|
||||
})
|
||||
job2.id = 2
|
||||
job2.flags = SchedulerJobFlags.PRE
|
||||
|
||||
const job3: SchedulerJob = vi.fn(() => {
|
||||
if (recurse) {
|
||||
queueJob(job2)
|
||||
queueJob(job3)
|
||||
queueJob(job2, 2, true)
|
||||
queueJob(job3, 3, true)
|
||||
|
||||
// The jobs are already queued, so these should have no effect
|
||||
queueJob(job2)
|
||||
queueJob(job3)
|
||||
queueJob(job2, 2, true)
|
||||
queueJob(job3, 3, true)
|
||||
}
|
||||
})
|
||||
job3.id = 3
|
||||
job3.flags = SchedulerJobFlags.ALLOW_RECURSE | SchedulerJobFlags.PRE
|
||||
job3.flags = SchedulerJobFlags.ALLOW_RECURSE
|
||||
|
||||
queueJob(job1)
|
||||
queueJob(job1, 1, true)
|
||||
|
||||
await nextTick()
|
||||
|
||||
|
@ -775,8 +723,7 @@ describe('scheduler', () => {
|
|||
spy()
|
||||
flushPreFlushCbs()
|
||||
}
|
||||
job.flags! |= SchedulerJobFlags.PRE
|
||||
queueJob(job)
|
||||
queueJob(job, undefined, true)
|
||||
await nextTick()
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
@ -788,18 +735,14 @@ describe('scheduler', () => {
|
|||
const job1: SchedulerJob = () => {
|
||||
calls.push('job1')
|
||||
}
|
||||
job1.id = 1
|
||||
job1.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
const job2: SchedulerJob = () => {
|
||||
calls.push('job2')
|
||||
}
|
||||
job2.id = 2
|
||||
job2.flags! |= SchedulerJobFlags.PRE
|
||||
|
||||
queuePostFlushCb(() => {
|
||||
queueJob(job2)
|
||||
queueJob(job1)
|
||||
queueJob(job2, 2, true)
|
||||
queueJob(job1, 1, true)
|
||||
|
||||
// e.g. nested app.mount() call
|
||||
flushPreFlushCbs()
|
||||
|
@ -830,14 +773,14 @@ describe('scheduler', () => {
|
|||
const cb1 = () => calls.push('cb1')
|
||||
// cb1 has no id
|
||||
const cb2 = () => calls.push('cb2')
|
||||
cb2.id = -1
|
||||
const queueAndFlush = (hook: Function) => {
|
||||
queuePostFlushCb(hook)
|
||||
flushPostFlushCbs()
|
||||
}
|
||||
|
||||
queueAndFlush(() => {
|
||||
queuePostFlushCb([cb1, cb2])
|
||||
queuePostFlushCb(cb1)
|
||||
queuePostFlushCb(cb2, -1)
|
||||
flushPostFlushCbs()
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.14",
|
||||
"version": "3.6.0-alpha.1",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
type ConcreteComponent,
|
||||
type GenericComponentInstance,
|
||||
currentInstance,
|
||||
getComponentName,
|
||||
isInSSRComponentSetup,
|
||||
} from './component'
|
||||
import { isFunction, isObject } from '@vue/shared'
|
||||
|
@ -122,14 +123,27 @@ export function defineAsyncComponent<
|
|||
__asyncLoader: load,
|
||||
|
||||
__asyncHydrate(el, instance, hydrate) {
|
||||
let patched = false
|
||||
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),
|
||||
)
|
||||
if (teardown) {
|
||||
;(instance.bum || (instance.bum = [])).push(teardown)
|
||||
}
|
||||
;(instance.u || (instance.u = [])).push(() => (patched = true))
|
||||
}
|
||||
: hydrate
|
||||
if (resolvedComp) {
|
||||
|
|
|
@ -26,7 +26,7 @@ import type { InjectionKey } from './apiInject'
|
|||
import { warn } from './warning'
|
||||
import type { VNode } from './vnode'
|
||||
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 { installAppCompatProperties } from './compat/global'
|
||||
import type { NormalizedPropsOptions } from './componentProps'
|
||||
|
@ -35,6 +35,7 @@ import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
|||
import type { DefineComponent } from './apiDefineComponent'
|
||||
|
||||
export interface App<HostElement = any> {
|
||||
vapor?: boolean
|
||||
version: string
|
||||
config: AppConfig
|
||||
|
||||
|
@ -485,10 +486,18 @@ export function createAppAPI<HostElement, Comp = Component>(
|
|||
|
||||
provide(key, value) {
|
||||
if (__DEV__ && (key as string | symbol) in context.provides) {
|
||||
warn(
|
||||
`App already provides property with key "${String(key)}". ` +
|
||||
`It will be overwritten with the new value.`,
|
||||
)
|
||||
if (hasOwn(context.provides, key as string | symbol)) {
|
||||
warn(
|
||||
`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
|
||||
|
|
|
@ -59,10 +59,12 @@ export function inject(
|
|||
// to support `app.use` plugins,
|
||||
// fallback to appContext's `provides` if the instance is at root
|
||||
// #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
|
||||
: instance
|
||||
? instance.parent == null
|
||||
? instance.parent == null || instance.ce
|
||||
? instance.appContext && instance.appContext.provides
|
||||
: instance.parent.provides
|
||||
: undefined
|
||||
|
|
|
@ -8,11 +8,7 @@ import type { ComponentPublicInstance } from './componentPublicInstance'
|
|||
import { ErrorTypeStrings, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import { warn } from './warning'
|
||||
import { toHandlerKey } from '@vue/shared'
|
||||
import {
|
||||
type DebuggerEvent,
|
||||
pauseTracking,
|
||||
resetTracking,
|
||||
} from '@vue/reactivity'
|
||||
import { type DebuggerEvent, setActiveSub } from '@vue/reactivity'
|
||||
import { LifecycleHooks } from './enums'
|
||||
|
||||
export { onActivated, onDeactivated } from './components/KeepAlive'
|
||||
|
@ -33,16 +29,16 @@ export function injectHook(
|
|||
(hook.__weh = (...args: unknown[]) => {
|
||||
// disable tracking inside all lifecycle hooks
|
||||
// since they can potentially be called inside effects.
|
||||
pauseTracking()
|
||||
const prevSub = setActiveSub()
|
||||
// Set currentInstance during hook invocation.
|
||||
// This assumes the hook does not synchronously trigger other hooks, which
|
||||
// can only be false when the user does something really funky.
|
||||
const reset = setCurrentInstance(target)
|
||||
const prev = setCurrentInstance(target)
|
||||
try {
|
||||
return callWithAsyncErrorHandling(hook, target, type, args)
|
||||
} finally {
|
||||
reset()
|
||||
resetTracking()
|
||||
setCurrentInstance(...prev)
|
||||
setActiveSub(prevSub)
|
||||
}
|
||||
})
|
||||
if (prepend) {
|
||||
|
|
|
@ -14,7 +14,6 @@ import {
|
|||
createSetupContext,
|
||||
getCurrentGenericInstance,
|
||||
setCurrentInstance,
|
||||
unsetCurrentInstance,
|
||||
} from './component'
|
||||
import type { EmitFn, EmitsOptions, ObjectEmitsOptions } from './componentEmits'
|
||||
import type {
|
||||
|
@ -511,7 +510,7 @@ export function withAsyncContext(getAwaitable: () => any): [any, () => void] {
|
|||
)
|
||||
}
|
||||
let awaitable = getAwaitable()
|
||||
unsetCurrentInstance()
|
||||
setCurrentInstance(null, undefined)
|
||||
if (isPromise(awaitable)) {
|
||||
awaitable = awaitable.catch(e => {
|
||||
setCurrentInstance(ctx)
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
import {
|
||||
type WatchOptions as BaseWatchOptions,
|
||||
type DebuggerOptions,
|
||||
EffectFlags,
|
||||
type ReactiveMarker,
|
||||
type WatchCallback,
|
||||
type WatchEffect,
|
||||
type WatchHandle,
|
||||
type WatchSource,
|
||||
watch as baseWatch,
|
||||
WatcherEffect,
|
||||
} from '@vue/reactivity'
|
||||
import { type SchedulerJob, SchedulerJobFlags, queueJob } from './scheduler'
|
||||
import { EMPTY_OBJ, NOOP, extend, isFunction, isString } from '@vue/shared'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
type GenericComponentInstance,
|
||||
currentInstance,
|
||||
isInSSRComponentSetup,
|
||||
setCurrentInstance,
|
||||
|
@ -125,7 +127,7 @@ export function watch<
|
|||
// implementation
|
||||
export function watch<T = any, Immediate extends Readonly<boolean> = false>(
|
||||
source: T | WatchSource<T>,
|
||||
cb: any,
|
||||
cb: WatchCallback,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchHandle {
|
||||
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)
|
||||
}
|
||||
|
||||
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(
|
||||
source: WatchSource | WatchSource[] | WatchEffect | object,
|
||||
cb: WatchCallback | null,
|
||||
options: WatchOptions = EMPTY_OBJ,
|
||||
): WatchHandle {
|
||||
const { immediate, deep, flush, once } = options
|
||||
const { immediate, deep, flush = 'pre', once } = options
|
||||
|
||||
if (__DEV__ && !cb) {
|
||||
if (immediate !== undefined) {
|
||||
|
@ -190,50 +237,37 @@ function doWatch(
|
|||
baseWatchOptions.call = (fn, type, args) =>
|
||||
callWithAsyncErrorHandling(fn, instance, type, args)
|
||||
|
||||
// scheduler
|
||||
let isPre = false
|
||||
if (flush === 'post') {
|
||||
baseWatchOptions.scheduler = job => {
|
||||
queuePostRenderEffect(job, instance && instance.suspense)
|
||||
}
|
||||
} else if (flush !== 'sync') {
|
||||
// default: 'pre'
|
||||
isPre = true
|
||||
baseWatchOptions.scheduler = (job, isFirstRun) => {
|
||||
if (isFirstRun) {
|
||||
job()
|
||||
} else {
|
||||
queueJob(job)
|
||||
}
|
||||
}
|
||||
const effect = new RenderWatcherEffect(
|
||||
instance,
|
||||
source,
|
||||
cb,
|
||||
baseWatchOptions,
|
||||
flush,
|
||||
)
|
||||
|
||||
// initial run
|
||||
if (cb) {
|
||||
effect.run(true)
|
||||
} else if (flush === 'post') {
|
||||
queuePostRenderEffect(effect.job, undefined, instance && instance.suspense)
|
||||
} else {
|
||||
effect.run(true)
|
||||
}
|
||||
|
||||
baseWatchOptions.augmentJob = (job: SchedulerJob) => {
|
||||
// important: mark the job as a watcher callback so that scheduler knows
|
||||
// it is allowed to self-trigger (#1727)
|
||||
if (cb) {
|
||||
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)
|
||||
const stop = effect.stop.bind(effect) as WatchHandle
|
||||
stop.pause = effect.pause.bind(effect)
|
||||
stop.resume = effect.resume.bind(effect)
|
||||
stop.stop = stop
|
||||
|
||||
if (__SSR__ && isInSSRComponentSetup) {
|
||||
if (ssrCleanup) {
|
||||
ssrCleanup.push(watchHandle)
|
||||
ssrCleanup.push(stop)
|
||||
} else if (runsImmediately) {
|
||||
watchHandle()
|
||||
stop()
|
||||
}
|
||||
}
|
||||
|
||||
return watchHandle
|
||||
return stop
|
||||
}
|
||||
|
||||
// this.$watch
|
||||
|
@ -256,9 +290,9 @@ export function instanceWatch(
|
|||
cb = value.handler as Function
|
||||
options = value
|
||||
}
|
||||
const reset = setCurrentInstance(this)
|
||||
const prev = setCurrentInstance(this)
|
||||
const res = doWatch(getter, cb.bind(publicThis), options)
|
||||
reset()
|
||||
setCurrentInstance(...prev)
|
||||
return res
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export function convertLegacyAsyncComponent(
|
|||
let resolve: (res: LegacyAsyncReturnValue) => void
|
||||
let reject: (reason?: any) => void
|
||||
const fallbackPromise = new Promise<Component>((r, rj) => {
|
||||
;(resolve = r), (reject = rj)
|
||||
;((resolve = r), (reject = rj))
|
||||
})
|
||||
|
||||
const res = comp(resolve!, reject!)
|
||||
|
|
|
@ -5,9 +5,8 @@ import {
|
|||
TrackOpTypes,
|
||||
isRef,
|
||||
markRaw,
|
||||
pauseTracking,
|
||||
proxyRefs,
|
||||
resetTracking,
|
||||
setActiveSub,
|
||||
shallowReadonly,
|
||||
track,
|
||||
} from '@vue/reactivity'
|
||||
|
@ -97,7 +96,6 @@ import type { RendererElement } from './renderer'
|
|||
import {
|
||||
setCurrentInstance,
|
||||
setInSSRSetupState,
|
||||
unsetCurrentInstance,
|
||||
} from './componentCurrentInstance'
|
||||
|
||||
export * from './componentCurrentInstance'
|
||||
|
@ -121,20 +119,23 @@ export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
|
|||
: T extends FunctionalComponent<infer Props, infer Emits>
|
||||
? ComponentPublicInstance<Props, {}, {}, {}, {}, ShortEmitsToObject<Emits>>
|
||||
: T extends Component<
|
||||
infer Props,
|
||||
infer PropsOrInstance,
|
||||
infer RawBindings,
|
||||
infer D,
|
||||
infer C,
|
||||
infer M
|
||||
>
|
||||
? // NOTE we override Props/RawBindings/D to make sure is not `unknown`
|
||||
ComponentPublicInstance<
|
||||
unknown extends Props ? {} : Props,
|
||||
unknown extends RawBindings ? {} : RawBindings,
|
||||
unknown extends D ? {} : D,
|
||||
C,
|
||||
M
|
||||
>
|
||||
? PropsOrInstance extends { $props: unknown }
|
||||
? // T is returned by `defineComponent()`
|
||||
PropsOrInstance
|
||||
: // NOTE we override Props/RawBindings/D to make sure is not `unknown`
|
||||
ComponentPublicInstance<
|
||||
unknown extends PropsOrInstance ? {} : PropsOrInstance,
|
||||
unknown extends RawBindings ? {} : RawBindings,
|
||||
unknown extends D ? {} : D,
|
||||
C,
|
||||
M
|
||||
>
|
||||
: never // not a vue Component
|
||||
|
||||
/**
|
||||
|
@ -280,7 +281,7 @@ export type ConcreteComponent<
|
|||
* The constructor type is an artificial type returned by defineComponent().
|
||||
*/
|
||||
export type Component<
|
||||
Props = any,
|
||||
PropsOrInstance = any,
|
||||
RawBindings = any,
|
||||
D = any,
|
||||
C extends ComputedOptions = ComputedOptions,
|
||||
|
@ -288,8 +289,8 @@ export type Component<
|
|||
E extends EmitsOptions | Record<string, any[]> = {},
|
||||
S extends Record<string, any> = any,
|
||||
> =
|
||||
| ConcreteComponent<Props, RawBindings, D, C, M, E, S>
|
||||
| ComponentPublicInstanceConstructor<Props>
|
||||
| ConcreteComponent<PropsOrInstance, RawBindings, D, C, M, E, S>
|
||||
| ComponentPublicInstanceConstructor<PropsOrInstance>
|
||||
|
||||
export type { ComponentOptions }
|
||||
|
||||
|
@ -367,9 +368,6 @@ export interface GenericComponentInstance {
|
|||
// state
|
||||
props: Data
|
||||
attrs: Data
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
refs: Data
|
||||
emit: EmitFn
|
||||
/**
|
||||
|
@ -672,13 +670,13 @@ export interface ComponentInternalInstance extends GenericComponentInstance {
|
|||
* For updating css vars on contained teleports
|
||||
* @internal
|
||||
*/
|
||||
ut?: (vars?: Record<string, string>) => void
|
||||
ut?: (vars?: Record<string, unknown>) => void
|
||||
|
||||
/**
|
||||
* dev only. For style v-bind hydration mismatch checks
|
||||
* @internal
|
||||
*/
|
||||
getCssVars?: () => Record<string, string>
|
||||
getCssVars?: () => Record<string, unknown>
|
||||
|
||||
/**
|
||||
* v2 compat only, for caching mutated $options
|
||||
|
@ -888,10 +886,10 @@ function setupStatefulComponent(
|
|||
// 2. call setup()
|
||||
const { setup } = Component
|
||||
if (setup) {
|
||||
pauseTracking()
|
||||
const prevSub = setActiveSub()
|
||||
const setupContext = (instance.setupContext =
|
||||
setup.length > 1 ? createSetupContext(instance) : null)
|
||||
const reset = setCurrentInstance(instance)
|
||||
const prev = setCurrentInstance(instance)
|
||||
const setupResult = callWithErrorHandling(
|
||||
setup,
|
||||
instance,
|
||||
|
@ -902,8 +900,8 @@ function setupStatefulComponent(
|
|||
],
|
||||
)
|
||||
const isAsyncSetup = isPromise(setupResult)
|
||||
resetTracking()
|
||||
reset()
|
||||
setActiveSub(prevSub)
|
||||
setCurrentInstance(...prev)
|
||||
|
||||
if ((isAsyncSetup || instance.sp) && !isAsyncWrapper(instance)) {
|
||||
// async setup / serverPrefetch, mark as async boundary for useId()
|
||||
|
@ -911,6 +909,9 @@ function setupStatefulComponent(
|
|||
}
|
||||
|
||||
if (isAsyncSetup) {
|
||||
const unsetCurrentInstance = (): void => {
|
||||
setCurrentInstance(null, undefined)
|
||||
}
|
||||
setupResult.then(unsetCurrentInstance, unsetCurrentInstance)
|
||||
if (isSSR) {
|
||||
// return the promise so server-renderer can wait on it
|
||||
|
@ -1083,13 +1084,13 @@ export function finishComponentSetup(
|
|||
|
||||
// support for 2.x options
|
||||
if (__FEATURE_OPTIONS_API__ && !(__COMPAT__ && skipOptions)) {
|
||||
const reset = setCurrentInstance(instance)
|
||||
pauseTracking()
|
||||
const prevInstance = setCurrentInstance(instance)
|
||||
const prevSub = setActiveSub()
|
||||
try {
|
||||
applyOptions(instance)
|
||||
} finally {
|
||||
resetTracking()
|
||||
reset()
|
||||
setActiveSub(prevSub)
|
||||
setCurrentInstance(...prevInstance)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import type {
|
|||
GenericComponentInstance,
|
||||
} from './component'
|
||||
import { currentRenderingInstance } from './componentRenderContext'
|
||||
import { type EffectScope, setCurrentScope } from '@vue/reactivity'
|
||||
|
||||
/**
|
||||
* @internal
|
||||
|
@ -25,7 +26,10 @@ export let isInSSRComponentSetup = false
|
|||
|
||||
export let setInSSRSetupState: (state: boolean) => void
|
||||
|
||||
let internalSetCurrentInstance: (
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export let simpleSetCurrentInstance: (
|
||||
instance: GenericComponentInstance | null,
|
||||
) => void
|
||||
|
||||
|
@ -53,7 +57,7 @@ if (__SSR__) {
|
|||
else setters[0](v)
|
||||
}
|
||||
}
|
||||
internalSetCurrentInstance = registerGlobalSetter(
|
||||
simpleSetCurrentInstance = registerGlobalSetter(
|
||||
`__VUE_INSTANCE_SETTERS__`,
|
||||
v => (currentInstance = v),
|
||||
)
|
||||
|
@ -66,7 +70,7 @@ if (__SSR__) {
|
|||
v => (isInSSRComponentSetup = v),
|
||||
)
|
||||
} else {
|
||||
internalSetCurrentInstance = i => {
|
||||
simpleSetCurrentInstance = i => {
|
||||
currentInstance = i
|
||||
}
|
||||
setInSSRSetupState = v => {
|
||||
|
@ -74,34 +78,15 @@ if (__SSR__) {
|
|||
}
|
||||
}
|
||||
|
||||
export const setCurrentInstance = (instance: GenericComponentInstance) => {
|
||||
const prev = currentInstance
|
||||
internalSetCurrentInstance(instance)
|
||||
instance.scope.on()
|
||||
return (): void => {
|
||||
instance.scope.off()
|
||||
internalSetCurrentInstance(prev)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
export const setCurrentInstance = (
|
||||
instance: GenericComponentInstance | null,
|
||||
scope: EffectScope | undefined = instance !== null
|
||||
? instance.scope
|
||||
: undefined,
|
||||
): [GenericComponentInstance | null, EffectScope | undefined] => {
|
||||
try {
|
||||
return [currentInstance, setCurrentScope(scope)]
|
||||
} finally {
|
||||
simpleSetCurrentInstance(instance)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -170,13 +170,15 @@ export function baseEmit(
|
|||
}
|
||||
|
||||
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
|
||||
// it's ok to use static get because modelModifiers can only be in the static
|
||||
// part of the props
|
||||
const modifiers =
|
||||
isModelListener && getModelModifiers(props, event.slice(7), getter)
|
||||
const modifiers = isCompatModelListener
|
||||
? props.modelModifiers
|
||||
: isModelListener && getModelModifiers(props, event.slice(7), getter)
|
||||
if (modifiers) {
|
||||
if (modifiers.trim) {
|
||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||
|
|
|
@ -144,7 +144,9 @@ type InferPropType<T, NullAsAny = true> = [T] extends [null]
|
|||
export type ExtractPropTypes<O> = {
|
||||
// use `keyof Pick<O, RequiredKeys<O>>` instead of `RequiredKeys<O>` to
|
||||
// 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
|
||||
// support IDE features
|
||||
|
@ -522,7 +524,7 @@ function baseResolveDefault(
|
|||
key: string,
|
||||
) {
|
||||
let value
|
||||
const reset = setCurrentInstance(instance)
|
||||
const prev = setCurrentInstance(instance)
|
||||
const props = toRaw(instance.props)
|
||||
value = factory.call(
|
||||
__COMPAT__ && isCompatEnabled(DeprecationTypes.PROPS_DEFAULT_THIS, instance)
|
||||
|
@ -530,7 +532,7 @@ function baseResolveDefault(
|
|||
: null,
|
||||
props,
|
||||
)
|
||||
reset()
|
||||
setCurrentInstance(...prev)
|
||||
return value
|
||||
}
|
||||
|
||||
|
|
|
@ -194,6 +194,10 @@ export const initSlots = (
|
|||
): void => {
|
||||
const slots = (instance.slots = createInternalObject())
|
||||
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)._
|
||||
if (type) {
|
||||
assignSlots(slots, children as Slots, optimized)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue