mirror of https://github.com/vuejs/core.git
Merge branch 'main' into edison/fix/12639
This commit is contained in:
commit
d1c10b23ce
|
@ -14,7 +14,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -31,4 +31,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
|||
ref: minor
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
run: pnpm install
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
@ -56,7 +56,7 @@ jobs:
|
|||
path: temp/size/base.txt
|
||||
|
||||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v6
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
with:
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: size-data.yml
|
||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -35,7 +35,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -63,7 +63,7 @@ jobs:
|
|||
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -88,7 +88,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
@ -104,5 +104,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
|
||||
|
|
|
@ -1 +1 @@
|
|||
20
|
||||
22.14.0
|
||||
|
|
68
CHANGELOG.md
68
CHANGELOG.md
|
@ -1,3 +1,69 @@
|
|||
## [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)
|
||||
|
||||
|
||||
|
@ -8,7 +74,7 @@
|
|||
* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
|
||||
* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
|
||||
* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
|
||||
* **reactiivty:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
|
||||
* **reactivity:** 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))
|
||||
|
|
|
@ -34,7 +34,8 @@ Please make sure to respect issue requirements and use [the new issue helper](ht
|
|||
|
||||
## Stay In Touch
|
||||
|
||||
- [Twitter](https://twitter.com/vuejs)
|
||||
- [X](https://x.com/vuejs)
|
||||
- [Bluesky](https://bsky.app/profile/vuejs.org)
|
||||
- [Blog](https://blog.vuejs.org/)
|
||||
- [Job Board](https://vuejobs.com/?ref=vuejs)
|
||||
|
||||
|
@ -44,7 +45,9 @@ Please make sure to read the [Contributing Guide](https://github.com/vuejs/core/
|
|||
|
||||
Thank you to all the people who already contributed to Vue!
|
||||
|
||||
<a href="https://github.com/vuejs/core/graphs/contributors"><img src="https://opencollective.com/vuejs/contributors.svg?width=890" /></a>
|
||||
<a href="https://github.com/vuejs/core/graphs/contributors"><img src="https://opencollective.com/vuejs/contributors.svg?width=890&limit=500" /></a>
|
||||
|
||||
<sub>_Note: Showing the first 500 contributors only due to GitHub image size limitations_</sub>
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
[build.environment]
|
||||
NODE_VERSION = "18"
|
||||
NODE_VERSION = "22"
|
||||
NPM_FLAGS = "--version" # prevent Netlify npm install
|
||||
|
|
57
package.json
57
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.13",
|
||||
"packageManager": "pnpm@9.15.2",
|
||||
"version": "3.5.15",
|
||||
"packageManager": "pnpm@10.11.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -65,62 +65,51 @@
|
|||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.2",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.10.3",
|
||||
"@swc/core": "^1.11.29",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^22.10.2",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/node": "^22.15.21",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitest/coverage-v8": "^2.1.8",
|
||||
"@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.24.2",
|
||||
"esbuild": "^0.25.4",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-import-x": "^4.6.1",
|
||||
"@vitest/eslint-plugin": "^1.1.20",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-import-x": "^4.13.1",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "^15.2.11",
|
||||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^16.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.17",
|
||||
"markdown-table": "^3.0.4",
|
||||
"marked": "13.0.3",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"picocolors": "^1.1.1",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~23.3.0",
|
||||
"puppeteer": "~24.9.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.29.1",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup": "^4.41.1",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.3",
|
||||
"semver": "^7.7.2",
|
||||
"serve": "^14.2.4",
|
||||
"serve-handler": "^6.1.6",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"simple-git-hooks": "^2.13.0",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.18.1",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^2.1.8"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
"allowedVersions": {
|
||||
"typescript-eslint>eslint": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin>eslint": "^9.0.0",
|
||||
"@typescript-eslint/parser>eslint": "^9.0.0",
|
||||
"@typescript-eslint/type-utils>eslint": "^9.0.0",
|
||||
"@typescript-eslint/utils>eslint": "^9.0.0"
|
||||
}
|
||||
}
|
||||
"vitest": "^3.1.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ app.directive<HTMLElement, string, 'prevent' | 'stop', 'arg1' | 'arg2'>(
|
|||
mounted(el, binding) {
|
||||
expectType<HTMLElement>(el)
|
||||
expectType<string>(binding.value)
|
||||
expectType<{ prevent: boolean; stop: boolean }>(binding.modifiers)
|
||||
expectType<{ prevent?: boolean; stop?: boolean }>(binding.modifiers)
|
||||
expectType<'arg1' | 'arg2'>(binding.arg!)
|
||||
|
||||
// @ts-expect-error not any
|
||||
|
|
|
@ -12,8 +12,11 @@ app.use(PluginWithoutType, 2)
|
|||
app.use(PluginWithoutType, { anything: 'goes' }, true)
|
||||
|
||||
type PluginOptions = {
|
||||
/** option1 */
|
||||
option1?: string
|
||||
/** option2 */
|
||||
option2: number
|
||||
/** option3 */
|
||||
option3: boolean
|
||||
}
|
||||
|
||||
|
@ -25,6 +28,20 @@ const PluginWithObjectOptions = {
|
|||
},
|
||||
}
|
||||
|
||||
const objectPluginOptional = {
|
||||
install(app: App, options?: PluginOptions) {},
|
||||
}
|
||||
app.use(objectPluginOptional)
|
||||
app.use(
|
||||
objectPluginOptional,
|
||||
// Test JSDoc and `go to definition` for options
|
||||
{
|
||||
option1: 'foo',
|
||||
option2: 1,
|
||||
option3: true,
|
||||
},
|
||||
)
|
||||
|
||||
for (const Plugin of [
|
||||
PluginWithObjectOptions,
|
||||
PluginWithObjectOptions.install,
|
||||
|
@ -92,7 +109,27 @@ const PluginTyped: Plugin<PluginOptions> = (app, options) => {}
|
|||
|
||||
// @ts-expect-error: needs options
|
||||
app.use(PluginTyped)
|
||||
app.use(PluginTyped, { option2: 2, option3: true })
|
||||
app.use(
|
||||
PluginTyped,
|
||||
// Test autocomplete for options
|
||||
{
|
||||
option1: '',
|
||||
option2: 2,
|
||||
option3: true,
|
||||
},
|
||||
)
|
||||
|
||||
const functionPluginOptional = (app: App, options?: PluginOptions) => {}
|
||||
app.use(functionPluginOptional)
|
||||
app.use(functionPluginOptional, { option2: 2, option3: true })
|
||||
|
||||
// type optional params
|
||||
const functionPluginOptional2: Plugin<[options?: PluginOptions]> = (
|
||||
app,
|
||||
options,
|
||||
) => {}
|
||||
app.use(functionPluginOptional2)
|
||||
app.use(functionPluginOptional2, { option2: 2, option3: true })
|
||||
|
||||
// vuetify usage
|
||||
const key: string = ''
|
||||
|
|
|
@ -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']}
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('custom', () => {
|
|||
value: number
|
||||
oldValue: number | null
|
||||
arg?: 'Arg'
|
||||
modifiers: Record<'a' | 'b', boolean>
|
||||
modifiers: Partial<Record<'a' | 'b', boolean>>
|
||||
}>(testDirective<number, 'a' | 'b', 'Arg'>())
|
||||
|
||||
expectType<{
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
type MaybeRefOrGetter,
|
||||
type Ref,
|
||||
type ShallowRef,
|
||||
type TemplateRef,
|
||||
type ToRefs,
|
||||
type WritableComputedRef,
|
||||
computed,
|
||||
|
@ -535,7 +536,7 @@ expectType<string>(toValue(unref2))
|
|||
|
||||
// useTemplateRef
|
||||
const tRef = useTemplateRef('foo')
|
||||
expectType<Readonly<ShallowRef<unknown>>>(tRef)
|
||||
expectType<TemplateRef>(tRef)
|
||||
|
||||
const tRef2 = useTemplateRef<HTMLElement>('bar')
|
||||
expectType<Readonly<ShallowRef<HTMLElement | null>>>(tRef2)
|
||||
expectType<TemplateRef<HTMLElement>>(tRef2)
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.4.2",
|
||||
"@vue/repl": "^4.5.1",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
@ -165,8 +165,9 @@ onMounted(() => {
|
|||
|
||||
body {
|
||||
font-size: 13px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
margin: 0;
|
||||
--base: #444;
|
||||
--nav-height: 50px;
|
||||
|
|
|
@ -17,7 +17,10 @@ export async function downloadProject(store: ReplStore) {
|
|||
|
||||
// basic structure
|
||||
zip.file('index.html', index)
|
||||
zip.file('package.json', pkg)
|
||||
zip.file(
|
||||
'package.json',
|
||||
pkg.replace(`"vue": "latest"`, `"vue": "${store.vueVersion || 'latest'}"`),
|
||||
)
|
||||
zip.file('vite.config.js', config)
|
||||
zip.file('README.md', readme)
|
||||
|
||||
|
|
|
@ -8,10 +8,10 @@
|
|||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue": "^3.4.0"
|
||||
"vue": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"vite": "^6.0.6"
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"vite": "^6.3.5"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
body {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
font-family:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
--bg: #1d1f21;
|
||||
--border: #333;
|
||||
}
|
||||
|
|
|
@ -170,6 +170,11 @@ describe('compiler: cacheStatic transform', () => {
|
|||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
key: { content: '__' },
|
||||
value: { content: '[0]' },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
@ -197,6 +202,11 @@ describe('compiler: cacheStatic transform', () => {
|
|||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
key: { content: '__' },
|
||||
value: { content: '[0]' },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.13",
|
||||
"version": "3.5.15",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
@ -188,7 +188,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
|
||||
|
@ -225,7 +227,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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -388,7 +388,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
||||
currentOptions,
|
||||
currentProp.loc,
|
||||
currentProp.rawName,
|
||||
currentProp.arg!.loc.source,
|
||||
)
|
||||
) {
|
||||
currentProp.name = 'model'
|
||||
|
|
|
@ -12,11 +12,14 @@ import {
|
|||
type RootNode,
|
||||
type SimpleExpressionNode,
|
||||
type SlotFunctionExpression,
|
||||
type SlotsObjectProperty,
|
||||
type TemplateChildNode,
|
||||
type TemplateNode,
|
||||
type TextCallNode,
|
||||
type VNodeCall,
|
||||
createArrayExpression,
|
||||
createObjectProperty,
|
||||
createSimpleExpression,
|
||||
getVNodeBlockHelper,
|
||||
getVNodeHelper,
|
||||
} from '../ast'
|
||||
|
@ -140,6 +143,7 @@ function walk(
|
|||
}
|
||||
|
||||
let cachedAsArray = false
|
||||
const slotCacheKeys = []
|
||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||
if (
|
||||
node.tagType === ElementTypes.ELEMENT &&
|
||||
|
@ -163,6 +167,7 @@ function walk(
|
|||
// default slot
|
||||
const slot = getSlotNode(node.codegenNode, 'default')
|
||||
if (slot) {
|
||||
slotCacheKeys.push(context.cached.length)
|
||||
slot.returns = getCacheExpression(
|
||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||
)
|
||||
|
@ -186,6 +191,7 @@ function walk(
|
|||
slotName.arg &&
|
||||
getSlotNode(parent.codegenNode, slotName.arg)
|
||||
if (slot) {
|
||||
slotCacheKeys.push(context.cached.length)
|
||||
slot.returns = getCacheExpression(
|
||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||
)
|
||||
|
@ -196,10 +202,31 @@ function walk(
|
|||
|
||||
if (!cachedAsArray) {
|
||||
for (const child of toCache) {
|
||||
slotCacheKeys.push(context.cached.length)
|
||||
child.codegenNode = context.cache(child.codegenNode!)
|
||||
}
|
||||
}
|
||||
|
||||
// put the slot cached keys on the slot object, so that the cache
|
||||
// can be removed when component unmounting to prevent memory leaks
|
||||
if (
|
||||
slotCacheKeys.length &&
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.COMPONENT &&
|
||||
node.codegenNode &&
|
||||
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
node.codegenNode.children &&
|
||||
!isArray(node.codegenNode.children) &&
|
||||
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||
) {
|
||||
node.codegenNode.children.properties.push(
|
||||
createObjectProperty(
|
||||
`__`,
|
||||
createSimpleExpression(JSON.stringify(slotCacheKeys), false),
|
||||
) as SlotsObjectProperty,
|
||||
)
|
||||
}
|
||||
|
||||
function getCacheExpression(value: JSChildNode): CacheExpression {
|
||||
const exp = context.cache(value)
|
||||
// #6978, #7138, #7114
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -342,7 +342,6 @@ export function buildSlots(
|
|||
: hasForwardedSlots(node.children)
|
||||
? SlotFlags.FORWARDED
|
||||
: SlotFlags.STABLE
|
||||
|
||||
let slots = createObjectExpression(
|
||||
slotsProperties.concat(
|
||||
createObjectProperty(
|
||||
|
|
|
@ -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.13",
|
||||
"version": "3.5.15",
|
||||
"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)
|
||||
|
|
|
@ -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', () => {
|
||||
|
@ -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', () => {
|
||||
|
|
|
@ -148,6 +148,27 @@ export default /*@__PURE__*/_defineComponent({
|
|||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > w/ TSTypeAliasDeclaration 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type FunFoo<O> = (item: O) => boolean;
|
||||
type FunBar = FunFoo<number>;
|
||||
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
foo: { type: Function, required: false, default: () => true },
|
||||
bar: { type: Function, required: false, default: () => true }
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
|
|
|
@ -192,6 +192,25 @@ return () => {}
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > handle function parameters with same name as destructured props 1`] = `
|
||||
"
|
||||
export default {
|
||||
setup(__props) {
|
||||
|
||||
|
||||
function test(value) {
|
||||
try {
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
console.log(__props.value)
|
||||
|
||||
return () => {}
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
||||
"
|
||||
export default {
|
||||
|
|
|
@ -808,4 +808,30 @@ const props = defineProps({ foo: String })
|
|||
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('w/ TSTypeAliasDeclaration', () => {
|
||||
const { content } = compile(`
|
||||
<script setup lang="ts">
|
||||
type FunFoo<O> = (item: O) => boolean;
|
||||
type FunBar = FunFoo<number>;
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
foo?: FunFoo<number>;
|
||||
bar?: FunBar;
|
||||
}>(),
|
||||
{
|
||||
foo: () => true,
|
||||
bar: () => true,
|
||||
},
|
||||
);
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(
|
||||
`foo: { type: Function, required: false, default: () => true }`,
|
||||
)
|
||||
expect(content).toMatch(
|
||||
`bar: { type: Function, required: false, default: () => true }`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -358,6 +358,22 @@ describe('sfc reactive props destructure', () => {
|
|||
expect(content).toMatch(`props: ['item'],`)
|
||||
})
|
||||
|
||||
test('handle function parameters with same name as destructured props', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
const { value } = defineProps()
|
||||
function test(value) {
|
||||
try {
|
||||
} catch {
|
||||
}
|
||||
}
|
||||
console.log(value)
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`console.log(__props.value)`)
|
||||
})
|
||||
|
||||
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
|
|
|
@ -1434,6 +1434,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'],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -211,38 +211,42 @@ color: red
|
|||
expect(
|
||||
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:where(:hover) { color: blue;
|
||||
}"`)
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:where(:hover) { color: blue;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:is(:hover) { color: blue;
|
||||
}"`)
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:is(:hover) { color: blue;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileScoped(
|
||||
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:where(.foo:hover) { color: blue;
|
||||
}"`)
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:where(.foo:hover) { color: blue;
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileScoped(
|
||||
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
|
||||
),
|
||||
).toMatchInlineSnapshot(`
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:is(.foo:hover) { color: blue;
|
||||
}"`)
|
||||
".div[data-v-test] { color: red;
|
||||
}
|
||||
.div[data-v-test]:is(.foo:hover) { color: blue;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('media query', () => {
|
||||
|
@ -489,7 +493,31 @@ describe('SFC style preprocessors', () => {
|
|||
}"
|
||||
`)
|
||||
expect(compileScoped(`.foo * { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo[data-v-test] * { color: red;
|
||||
".foo[data-v-test] [data-v-test] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`.foo :active { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo[data-v-test] :active { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`.foo *:active { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
".foo[data-v-test] [data-v-test]:active { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`.foo * .bar { color: red; }`)).toMatchInlineSnapshot(`
|
||||
".foo * .bar[data-v-test] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`:last-child * { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"[data-v-test]:last-child [data-v-test] { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`:last-child *:active { color: red; }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"[data-v-test]:last-child [data-v-test]:active { color: red;
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@ font-weight: bold;
|
|||
|
||||
const consumer = new SourceMapConsumer(script!.map!)
|
||||
consumer.eachMapping(mapping => {
|
||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||
expect(mapping.originalLine! - mapping.generatedLine).toBe(padding)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -100,8 +100,8 @@ font-weight: bold;
|
|||
|
||||
const consumer = new SourceMapConsumer(template.map!)
|
||||
consumer.eachMapping(mapping => {
|
||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||
expect(mapping.originalColumn - mapping.generatedColumn).toBe(2)
|
||||
expect(mapping.originalLine! - mapping.generatedLine).toBe(padding)
|
||||
expect(mapping.originalColumn! - mapping.generatedColumn).toBe(2)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -115,7 +115,7 @@ font-weight: bold;
|
|||
|
||||
const consumer = new SourceMapConsumer(custom!.map!)
|
||||
consumer.eachMapping(mapping => {
|
||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
||||
expect(mapping.originalLine! - mapping.generatedLine).toBe(padding)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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.13",
|
||||
"version": "3.5.15",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -49,7 +49,7 @@
|
|||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.4.49",
|
||||
"postcss": "^8.5.3",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -58,10 +58,10 @@
|
|||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "~9.0.5",
|
||||
"minimatch": "~10.0.1",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-selector-parser": "^7.0.0",
|
||||
"postcss-selector-parser": "^7.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.83.0"
|
||||
"sass": "^1.89.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,11 @@ import type {
|
|||
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,
|
||||
|
@ -170,8 +174,6 @@ export function compileScript(
|
|||
const scriptLang = script && script.lang
|
||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||
|
||||
let refBindings: string[] | undefined
|
||||
|
||||
if (!scriptSetup) {
|
||||
if (!script) {
|
||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||
|
@ -740,12 +742,6 @@ export function compileScript(
|
|||
for (const key in setupBindings) {
|
||||
ctx.bindingMetadata[key] = setupBindings[key]
|
||||
}
|
||||
// known ref bindings
|
||||
if (refBindings) {
|
||||
for (const key of refBindings) {
|
||||
ctx.bindingMetadata[key] = BindingTypes.SETUP_REF
|
||||
}
|
||||
}
|
||||
|
||||
// 7. inject `useCssVars` calls
|
||||
if (
|
||||
|
@ -817,6 +813,7 @@ export function compileScript(
|
|||
args += `, { ${destructureElements.join(', ')} }`
|
||||
}
|
||||
|
||||
let templateMap
|
||||
// 9. generate return statement
|
||||
let returned
|
||||
if (
|
||||
|
@ -866,7 +863,7 @@ export function compileScript(
|
|||
}
|
||||
// inline render function mode - we are going to compile the template and
|
||||
// inline it right here
|
||||
const { code, ast, preamble, tips, errors } = compileTemplate({
|
||||
const { code, ast, preamble, tips, errors, map } = compileTemplate({
|
||||
filename,
|
||||
ast: sfc.template.ast,
|
||||
source: sfc.template.content,
|
||||
|
@ -884,6 +881,7 @@ export function compileScript(
|
|||
bindingMetadata: ctx.bindingMetadata,
|
||||
},
|
||||
})
|
||||
templateMap = map
|
||||
if (tips.length) {
|
||||
tips.forEach(warnOnce)
|
||||
}
|
||||
|
@ -1022,19 +1020,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,
|
||||
|
@ -1112,6 +1119,7 @@ function walkDeclaration(
|
|||
m === userImportAliases['shallowRef'] ||
|
||||
m === userImportAliases['customRef'] ||
|
||||
m === userImportAliases['toRef'] ||
|
||||
m === userImportAliases['useTemplateRef'] ||
|
||||
m === DEFINE_MODEL,
|
||||
)
|
||||
) {
|
||||
|
@ -1291,3 +1299,42 @@ function isStaticNode(node: Node): 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()
|
||||
}
|
||||
|
|
|
@ -289,7 +289,7 @@ function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
|||
|
||||
const origPosInOldMap = oldMapConsumer.originalPositionFor({
|
||||
line: m.originalLine,
|
||||
column: m.originalColumn,
|
||||
column: m.originalColumn!,
|
||||
})
|
||||
|
||||
if (origPosInOldMap.source == null) {
|
||||
|
@ -305,7 +305,7 @@ function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
|||
line: origPosInOldMap.line, // map line
|
||||
// use current column, since the oldMap produced by @vue/compiler-sfc
|
||||
// does not
|
||||
column: m.originalColumn,
|
||||
column: m.originalColumn!,
|
||||
},
|
||||
source: origPosInOldMap.source,
|
||||
name: origPosInOldMap.name,
|
||||
|
|
|
@ -39,7 +39,7 @@ export function rewriteDefaultAST(
|
|||
ast.forEach(node => {
|
||||
if (node.type === 'ExportDefaultDeclaration') {
|
||||
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
||||
let start: number =
|
||||
const start: number =
|
||||
node.declaration.decorators && node.declaration.decorators.length > 0
|
||||
? node.declaration.decorators[
|
||||
node.declaration.decorators.length - 1
|
||||
|
|
|
@ -291,7 +291,8 @@ export function transformDestructuredProps(
|
|||
parent && parentStack.pop()
|
||||
if (
|
||||
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
||||
isFunctionType(node)
|
||||
isFunctionType(node) ||
|
||||
node.type === 'CatchClause'
|
||||
) {
|
||||
popScope()
|
||||
}
|
||||
|
|
|
@ -860,13 +860,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 +1059,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 +1129,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 +1139,7 @@ export function fileToScope(
|
|||
function parseFile(
|
||||
filename: string,
|
||||
content: string,
|
||||
fs: FS,
|
||||
parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
|
||||
): Statement[] {
|
||||
const ext = extname(filename)
|
||||
|
@ -1151,7 +1152,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 +1569,14 @@ export function inferRuntimeType(
|
|||
case 'TSTypeReference': {
|
||||
const resolved = resolveTypeReference(ctx, node, scope)
|
||||
if (resolved) {
|
||||
if (resolved.type === 'TSTypeAliasDeclaration') {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
resolved.typeAnnotation,
|
||||
resolved._ownerScope,
|
||||
isKeyOf,
|
||||
)
|
||||
}
|
||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||
}
|
||||
|
||||
|
|
|
@ -102,6 +102,7 @@ function rewriteSelector(
|
|||
slotted = false,
|
||||
) {
|
||||
let node: selectorParser.Node | null = null
|
||||
let starNode: selectorParser.Node | null = null
|
||||
let shouldInject = !deep
|
||||
// find the last child node to insert attribute selector
|
||||
selector.each(n => {
|
||||
|
@ -216,17 +217,21 @@ function rewriteSelector(
|
|||
return false
|
||||
}
|
||||
}
|
||||
// .foo * -> .foo[xxxxxxx] *
|
||||
if (node) return
|
||||
// store the universal selector so it can be rewritten later
|
||||
// .foo * -> .foo[xxxxxxx] [xxxxxxx]
|
||||
starNode = n
|
||||
}
|
||||
|
||||
if (
|
||||
(n.type !== 'pseudo' && n.type !== 'combinator') ||
|
||||
(n.type !== 'pseudo' &&
|
||||
n.type !== 'combinator' &&
|
||||
n.type !== 'universal') ||
|
||||
(n.type === 'pseudo' &&
|
||||
(n.value === ':is' || n.value === ':where') &&
|
||||
!node)
|
||||
) {
|
||||
node = n
|
||||
starNode = null
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -274,6 +279,20 @@ function rewriteSelector(
|
|||
quoteMark: `"`,
|
||||
}),
|
||||
)
|
||||
// Used for trailing universal selectors (#12906)
|
||||
// `.foo * {}` -> `.foo[xxxxxxx] [xxxxxxx] {}`
|
||||
if (starNode) {
|
||||
selector.insertBefore(
|
||||
starNode,
|
||||
selectorParser.attribute({
|
||||
attribute: idToAdd,
|
||||
value: idToAdd,
|
||||
raws: {},
|
||||
quoteMark: `"`,
|
||||
}),
|
||||
)
|
||||
selector.removeChild(starNode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.13",
|
||||
"version": "3.5.15",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -1012,6 +1012,17 @@ describe('reactivity/computed', () => {
|
|||
expect(cValue.value).toBe(1)
|
||||
})
|
||||
|
||||
test('should not recompute if computed does not track reactive data', async () => {
|
||||
const spy = vi.fn()
|
||||
const c1 = computed(() => spy())
|
||||
|
||||
c1.value
|
||||
ref(0).value++ // update globalVersion
|
||||
c1.value
|
||||
|
||||
expect(spy).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
test('computed should remain live after losing all subscribers', () => {
|
||||
const state = reactive({ a: 1 })
|
||||
const p = computed(() => state.a + 1)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { isRef, ref } from '../src/ref'
|
||||
import { isRef, ref, shallowRef } from '../src/ref'
|
||||
import {
|
||||
isProxy,
|
||||
isReactive,
|
||||
|
@ -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', () => {
|
||||
|
@ -301,6 +301,13 @@ describe('reactivity/reactive', () => {
|
|||
expect(() => markRaw(obj)).not.toThrowError()
|
||||
})
|
||||
|
||||
test('should not markRaw object as reactive', () => {
|
||||
const a = reactive({ a: 1 })
|
||||
const b = reactive({ b: 2 }) as any
|
||||
b.a = markRaw(toRaw(a))
|
||||
expect(b.a === a).toBe(false)
|
||||
})
|
||||
|
||||
test('should not observe non-extensible objects', () => {
|
||||
const obj = reactive({
|
||||
foo: Object.preventExtensions({ a: 1 }),
|
||||
|
@ -419,4 +426,17 @@ describe('reactivity/reactive', () => {
|
|||
map.set(void 0, 1)
|
||||
expect(c.value).toBe(1)
|
||||
})
|
||||
|
||||
test('should return true for reactive objects', () => {
|
||||
expect(isReactive(reactive({}))).toBe(true)
|
||||
expect(isReactive(readonly(reactive({})))).toBe(true)
|
||||
expect(isReactive(ref({}).value)).toBe(true)
|
||||
expect(isReactive(readonly(ref({})).value)).toBe(true)
|
||||
expect(isReactive(shallowReactive({}))).toBe(true)
|
||||
})
|
||||
|
||||
test('should return false for non-reactive objects', () => {
|
||||
expect(isReactive(ref(true))).toBe(false)
|
||||
expect(isReactive(shallowRef({}).value)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -277,4 +277,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.13",
|
||||
"version": "3.5.15",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -49,6 +49,7 @@ export enum EffectFlags {
|
|||
DIRTY = 1 << 4,
|
||||
ALLOW_RECURSE = 1 << 5,
|
||||
PAUSED = 1 << 6,
|
||||
EVALUATED = 1 << 7,
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -377,22 +378,22 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
|
|||
}
|
||||
computed.globalVersion = globalVersion
|
||||
|
||||
const dep = computed.dep
|
||||
computed.flags |= EffectFlags.RUNNING
|
||||
// In SSR there will be no render effect, so the computed has no subscriber
|
||||
// and therefore tracks no deps, thus we cannot rely on the dirty check.
|
||||
// Instead, computed always re-evaluate and relies on the globalVersion
|
||||
// fast path above for caching.
|
||||
// #12337 if computed has no deps (does not rely on any reactive data) and evaluated,
|
||||
// there is no need to re-evaluate.
|
||||
if (
|
||||
dep.version > 0 &&
|
||||
!computed.isSSR &&
|
||||
computed.deps &&
|
||||
!isDirty(computed)
|
||||
computed.flags & EffectFlags.EVALUATED &&
|
||||
((!computed.deps && !(computed as any)._dirty) || !isDirty(computed))
|
||||
) {
|
||||
computed.flags &= ~EffectFlags.RUNNING
|
||||
return
|
||||
}
|
||||
computed.flags |= EffectFlags.RUNNING
|
||||
|
||||
const dep = computed.dep
|
||||
const prevSub = activeSub
|
||||
const prevShouldTrack = shouldTrack
|
||||
activeSub = computed
|
||||
|
@ -402,6 +403,7 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
|
|||
prepareDeps(computed)
|
||||
const value = computed.fn(computed._value)
|
||||
if (dep.version === 0 || hasChanged(value, computed._value)) {
|
||||
computed.flags |= EffectFlags.EVALUATED
|
||||
computed._value = value
|
||||
dep.version++
|
||||
}
|
||||
|
|
|
@ -8,6 +8,10 @@ export class EffectScope {
|
|||
* @internal
|
||||
*/
|
||||
private _active = true
|
||||
/**
|
||||
* @internal track `on` calls, allow `on` call multiple times
|
||||
*/
|
||||
private _on = 0
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
@ -99,12 +103,16 @@ export class EffectScope {
|
|||
}
|
||||
}
|
||||
|
||||
prevScope: EffectScope | undefined
|
||||
/**
|
||||
* This should only be called on non-detached scopes
|
||||
* @internal
|
||||
*/
|
||||
on(): void {
|
||||
activeEffectScope = this
|
||||
if (++this._on === 1) {
|
||||
this.prevScope = activeEffectScope
|
||||
activeEffectScope = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -112,7 +120,10 @@ export class EffectScope {
|
|||
* @internal
|
||||
*/
|
||||
off(): void {
|
||||
activeEffectScope = this.parent
|
||||
if (this._on > 0 && --this._on === 0) {
|
||||
activeEffectScope = this.prevScope
|
||||
this.prevScope = undefined
|
||||
}
|
||||
}
|
||||
|
||||
stop(fromParent?: boolean): void {
|
||||
|
|
|
@ -108,9 +108,9 @@ export declare const ShallowReactiveMarker: unique symbol
|
|||
export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true }
|
||||
|
||||
/**
|
||||
* Shallow version of {@link reactive()}.
|
||||
* Shallow version of {@link reactive}.
|
||||
*
|
||||
* Unlike {@link reactive()}, there is no deep conversion: only root-level
|
||||
* Unlike {@link reactive}, there is no deep conversion: only root-level
|
||||
* properties are reactive for a shallow reactive object. Property values are
|
||||
* stored and exposed as-is - this also means properties with ref values will
|
||||
* not be automatically unwrapped.
|
||||
|
@ -178,7 +178,7 @@ export type DeepReadonly<T> = T extends Builtin
|
|||
* the original.
|
||||
*
|
||||
* A readonly proxy is deep: any nested property accessed will be readonly as
|
||||
* well. It also has the same ref-unwrapping behavior as {@link reactive()},
|
||||
* well. It also has the same ref-unwrapping behavior as {@link reactive},
|
||||
* except the unwrapped values will also be made readonly.
|
||||
*
|
||||
* @example
|
||||
|
@ -215,9 +215,9 @@ export function readonly<T extends object>(
|
|||
}
|
||||
|
||||
/**
|
||||
* Shallow version of {@link readonly()}.
|
||||
* Shallow version of {@link readonly}.
|
||||
*
|
||||
* Unlike {@link readonly()}, there is no deep conversion: only root-level
|
||||
* Unlike {@link readonly}, there is no deep conversion: only root-level
|
||||
* properties are made readonly. Property values are stored and exposed as-is -
|
||||
* this also means properties with ref values will not be automatically
|
||||
* unwrapped.
|
||||
|
@ -279,16 +279,16 @@ function createReactiveObject(
|
|||
) {
|
||||
return target
|
||||
}
|
||||
// target already has corresponding Proxy
|
||||
const existingProxy = proxyMap.get(target)
|
||||
if (existingProxy) {
|
||||
return existingProxy
|
||||
}
|
||||
// only specific value types can be observed.
|
||||
const targetType = getTargetType(target)
|
||||
if (targetType === TargetType.INVALID) {
|
||||
return target
|
||||
}
|
||||
// target already has corresponding Proxy
|
||||
const existingProxy = proxyMap.get(target)
|
||||
if (existingProxy) {
|
||||
return existingProxy
|
||||
}
|
||||
const proxy = new Proxy(
|
||||
target,
|
||||
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
|
||||
|
@ -298,8 +298,8 @@ function createReactiveObject(
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if an object is a proxy created by {@link reactive()} or
|
||||
* {@link shallowReactive()} (or {@link ref()} in some cases).
|
||||
* Checks if an object is a proxy created by {@link reactive} or
|
||||
* {@link shallowReactive} (or {@link ref} in some cases).
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
|
@ -327,7 +327,7 @@ export function isReactive(value: unknown): boolean {
|
|||
* readonly object can change, but they can't be assigned directly via the
|
||||
* passed object.
|
||||
*
|
||||
* The proxies created by {@link readonly()} and {@link shallowReadonly()} are
|
||||
* The proxies created by {@link readonly} and {@link shallowReadonly} are
|
||||
* both considered readonly, as is a computed ref without a set function.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
|
@ -343,7 +343,7 @@ export function isShallow(value: unknown): boolean {
|
|||
|
||||
/**
|
||||
* Checks if an object is a proxy created by {@link reactive},
|
||||
* {@link readonly}, {@link shallowReactive} or {@link shallowReadonly()}.
|
||||
* {@link readonly}, {@link shallowReactive} or {@link shallowReadonly}.
|
||||
*
|
||||
* @param value - The value to check.
|
||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isproxy}
|
||||
|
@ -356,8 +356,8 @@ export function isProxy(value: any): boolean {
|
|||
* Returns the raw, original object of a Vue-created proxy.
|
||||
*
|
||||
* `toRaw()` can return the original object from proxies created by
|
||||
* {@link reactive()}, {@link readonly()}, {@link shallowReactive()} or
|
||||
* {@link shallowReadonly()}.
|
||||
* {@link reactive}, {@link readonly}, {@link shallowReactive} or
|
||||
* {@link shallowReadonly}.
|
||||
*
|
||||
* This is an escape hatch that can be used to temporarily read without
|
||||
* incurring proxy access / tracking overhead or write without triggering
|
||||
|
@ -397,7 +397,7 @@ export type Raw<T> = T & { [RawSymbol]?: true }
|
|||
* ```
|
||||
*
|
||||
* **Warning:** `markRaw()` together with the shallow APIs such as
|
||||
* {@link shallowReactive()} allow you to selectively opt-out of the default
|
||||
* {@link shallowReactive} allow you to selectively opt-out of the default
|
||||
* deep reactive/readonly conversion and embed raw, non-proxied objects in your
|
||||
* state graph.
|
||||
*
|
||||
|
|
|
@ -67,7 +67,7 @@ export type ShallowRef<T = any, S = T> = Ref<T, S> & {
|
|||
}
|
||||
|
||||
/**
|
||||
* Shallow version of {@link ref()}.
|
||||
* Shallow version of {@link ref}.
|
||||
*
|
||||
* @example
|
||||
* ```js
|
||||
|
@ -229,7 +229,7 @@ export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T {
|
|||
|
||||
/**
|
||||
* Normalizes values / refs / getters to values.
|
||||
* This is similar to {@link unref()}, except that it also normalizes getters.
|
||||
* This is similar to {@link unref}, except that it also normalizes getters.
|
||||
* If the argument is a getter, it will be invoked and its return value will
|
||||
* be returned.
|
||||
*
|
||||
|
@ -331,7 +331,7 @@ export type ToRefs<T = any> = {
|
|||
/**
|
||||
* Converts a reactive object to a plain object where each property of the
|
||||
* resulting object is a ref pointing to the corresponding property of the
|
||||
* original object. Each individual ref is created using {@link toRef()}.
|
||||
* original object. Each individual ref is created using {@link toRef}.
|
||||
*
|
||||
* @param object - Reactive object to be made into an object of linked refs.
|
||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs}
|
||||
|
|
|
@ -264,11 +264,11 @@ export function watch(
|
|||
: oldValue,
|
||||
boundCleanup,
|
||||
]
|
||||
oldValue = newValue
|
||||
call
|
||||
? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
|
||||
: // @ts-expect-error
|
||||
cb!(...args)
|
||||
oldValue = newValue
|
||||
} finally {
|
||||
activeWatcher = currentWatcher
|
||||
}
|
||||
|
|
|
@ -31,6 +31,7 @@ import {
|
|||
TrackOpTypes,
|
||||
TriggerOpTypes,
|
||||
effectScope,
|
||||
onScopeDispose,
|
||||
shallowReactive,
|
||||
shallowRef,
|
||||
toRef,
|
||||
|
@ -1594,7 +1595,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)
|
||||
})
|
||||
|
@ -1873,7 +1874,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'
|
||||
|
@ -1982,4 +1983,31 @@ describe('api: watch', () => {
|
|||
expect(spy1).toHaveBeenCalled()
|
||||
expect(spy2).toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #12631
|
||||
test('this.$watch w/ onScopeDispose', () => {
|
||||
const onCleanup = vi.fn()
|
||||
const toggle = ref(true)
|
||||
|
||||
const Comp = defineComponent({
|
||||
render() {},
|
||||
created(this: any) {
|
||||
this.$watch(
|
||||
() => 1,
|
||||
function () {},
|
||||
)
|
||||
onScopeDispose(onCleanup)
|
||||
},
|
||||
})
|
||||
|
||||
const App = defineComponent({
|
||||
render() {
|
||||
return toggle.value ? h(Comp) : null
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
createApp(App).mount(root)
|
||||
expect(onCleanup).toBeCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -324,4 +324,98 @@ describe('component: slots', () => {
|
|||
'Slot "default" invoked outside of the render function',
|
||||
).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('basic warn', () => {
|
||||
const Comp = {
|
||||
setup(_: any, { slots }: any) {
|
||||
slots.default && slots.default()
|
||||
return () => null
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () => h(Comp, () => h('div'))
|
||||
},
|
||||
}
|
||||
|
||||
createApp(App).mount(nodeOps.createElement('div'))
|
||||
expect(
|
||||
'Slot "default" invoked outside of the render function',
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('basic warn when mounting another app in setup', () => {
|
||||
const Comp = {
|
||||
setup(_: any, { slots }: any) {
|
||||
slots.default?.()
|
||||
return () => null
|
||||
},
|
||||
}
|
||||
|
||||
const mountComp = () => {
|
||||
createApp({
|
||||
setup() {
|
||||
return () => h(Comp, () => 'msg')
|
||||
},
|
||||
}).mount(nodeOps.createElement('div'))
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
mountComp()
|
||||
return () => null
|
||||
},
|
||||
}
|
||||
|
||||
createApp(App).mount(nodeOps.createElement('div'))
|
||||
expect(
|
||||
'Slot "default" invoked outside of the render function',
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should not warn when render in setup', () => {
|
||||
const container = {
|
||||
setup(_: any, { slots }: any) {
|
||||
return () => slots.default && slots.default()
|
||||
},
|
||||
}
|
||||
|
||||
const comp = h(container, null, () => h('div'))
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
render(h(comp), nodeOps.createElement('div'))
|
||||
return () => null
|
||||
},
|
||||
}
|
||||
|
||||
createApp(App).mount(nodeOps.createElement('div'))
|
||||
expect(
|
||||
'Slot "default" invoked outside of the render function',
|
||||
).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('basic warn when render in setup', () => {
|
||||
const container = {
|
||||
setup(_: any, { slots }: any) {
|
||||
slots.default && slots.default()
|
||||
return () => null
|
||||
},
|
||||
}
|
||||
|
||||
const comp = h(container, null, () => h('div'))
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
render(h(comp), nodeOps.createElement('div'))
|
||||
return () => null
|
||||
},
|
||||
}
|
||||
|
||||
createApp(App).mount(nodeOps.createElement('div'))
|
||||
expect(
|
||||
'Slot "default" invoked outside of the render function',
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1198,4 +1198,51 @@ describe('BaseTransition', () => {
|
|||
test('should not error on KeepAlive w/ function children', () => {
|
||||
expect(() => mount({}, () => () => h('div'), true)).not.toThrow()
|
||||
})
|
||||
|
||||
// #12465
|
||||
test('mode: "out-in" w/ KeepAlive + fallthrough attrs (prod mode)', async () => {
|
||||
__DEV__ = false
|
||||
async function testOutIn({ trueBranch, falseBranch }: ToggleOptions) {
|
||||
const toggle = ref(true)
|
||||
const { props, cbs } = mockProps({ mode: 'out-in' }, true)
|
||||
const root = nodeOps.createElement('div')
|
||||
const App = {
|
||||
render() {
|
||||
return h(
|
||||
BaseTransition,
|
||||
{
|
||||
...props,
|
||||
class: 'test',
|
||||
},
|
||||
() =>
|
||||
h(KeepAlive, null, toggle.value ? trueBranch() : falseBranch()),
|
||||
)
|
||||
},
|
||||
}
|
||||
render(h(App), root)
|
||||
|
||||
expect(serializeInner(root)).toBe(`<div class="test">0</div>`)
|
||||
|
||||
// trigger toggle
|
||||
toggle.value = false
|
||||
await nextTick()
|
||||
expect(props.onBeforeLeave).toHaveBeenCalledTimes(1)
|
||||
expect(serialize((props.onBeforeLeave as any).mock.calls[0][0])).toBe(
|
||||
`<div class="test">0</div>`,
|
||||
)
|
||||
expect(props.onLeave).toHaveBeenCalledTimes(1)
|
||||
expect(serialize((props.onLeave as any).mock.calls[0][0])).toBe(
|
||||
`<div class="test">0</div>`,
|
||||
)
|
||||
expect(props.onAfterLeave).not.toHaveBeenCalled()
|
||||
// enter should not have started
|
||||
expect(props.onBeforeEnter).not.toHaveBeenCalled()
|
||||
expect(props.onEnter).not.toHaveBeenCalled()
|
||||
expect(props.onAfterEnter).not.toHaveBeenCalled()
|
||||
cbs.doneLeave[`<div class="test">0</div>`]()
|
||||
expect(serializeInner(root)).toBe(`<span class="test">0</span>`)
|
||||
}
|
||||
await runTestWithKeepAlive(testOutIn)
|
||||
__DEV__ = true
|
||||
})
|
||||
})
|
||||
|
|
|
@ -10,14 +10,29 @@ import {
|
|||
markRaw,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
onMounted,
|
||||
h as originalH,
|
||||
ref,
|
||||
render,
|
||||
serialize,
|
||||
serializeInner,
|
||||
useModel,
|
||||
withDirectives,
|
||||
} from '@vue/runtime-test'
|
||||
import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
|
||||
import {
|
||||
Fragment,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createTextVNode,
|
||||
createVNode,
|
||||
openBlock,
|
||||
} from '../../src/vnode'
|
||||
import { toDisplayString } from '@vue/shared'
|
||||
import { compile, createApp as createDOMApp, render as domRender } from 'vue'
|
||||
import type { HMRRuntime } from '../../src/hmr'
|
||||
|
||||
declare var __VUE_HMR_RUNTIME__: HMRRuntime
|
||||
const { rerender, createRecord } = __VUE_HMR_RUNTIME__
|
||||
|
||||
describe('renderer: teleport', () => {
|
||||
describe('eager mode', () => {
|
||||
|
@ -130,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) {
|
||||
|
@ -243,6 +314,39 @@ describe('renderer: teleport', () => {
|
|||
expect(serializeInner(target)).toBe(`teleported`)
|
||||
})
|
||||
|
||||
test('should traverse comment node after updating in optimize mode', async () => {
|
||||
const target = nodeOps.createElement('div')
|
||||
const root = nodeOps.createElement('div')
|
||||
const count = ref(0)
|
||||
let teleport
|
||||
|
||||
__DEV__ = false
|
||||
render(
|
||||
h(() => {
|
||||
teleport =
|
||||
(openBlock(),
|
||||
createBlock(Teleport, { to: target }, [
|
||||
createCommentVNode('comment in teleport'),
|
||||
]))
|
||||
return h('div', null, [
|
||||
createTextVNode(toDisplayString(count.value)),
|
||||
teleport,
|
||||
])
|
||||
}),
|
||||
root,
|
||||
)
|
||||
const commentNode = teleport!.children[0].el
|
||||
expect(serializeInner(root)).toBe(`<div>0</div>`)
|
||||
expect(serializeInner(target)).toBe(`<!--comment in teleport-->`)
|
||||
expect(serialize(commentNode)).toBe(`<!--comment in teleport-->`)
|
||||
|
||||
count.value = 1
|
||||
await nextTick()
|
||||
__DEV__ = true
|
||||
expect(serializeInner(root)).toBe(`<div>1</div>`)
|
||||
expect(teleport!.children[0].el).toBe(commentNode)
|
||||
})
|
||||
|
||||
test('should remove children when unmounted', () => {
|
||||
const target = nodeOps.createElement('div')
|
||||
const root = nodeOps.createElement('div')
|
||||
|
@ -269,6 +373,34 @@ describe('renderer: teleport', () => {
|
|||
testUnmount({ to: null, disabled: true })
|
||||
})
|
||||
|
||||
// #10747
|
||||
test('should unmount correctly when using top level comment in teleport', async () => {
|
||||
const target = nodeOps.createElement('div')
|
||||
const root = nodeOps.createElement('div')
|
||||
const count = ref(0)
|
||||
|
||||
__DEV__ = false
|
||||
render(
|
||||
h(() => {
|
||||
return h('div', null, [
|
||||
createTextVNode(toDisplayString(count.value)),
|
||||
(openBlock(),
|
||||
createBlock(Teleport, { to: target }, [
|
||||
createCommentVNode('comment in teleport'),
|
||||
])),
|
||||
])
|
||||
}),
|
||||
root,
|
||||
)
|
||||
|
||||
count.value = 1
|
||||
|
||||
await nextTick()
|
||||
__DEV__ = true
|
||||
render(null, root)
|
||||
expect(root.children.length).toBe(0)
|
||||
})
|
||||
|
||||
test('component with multi roots should be removed when unmounted', () => {
|
||||
const target = nodeOps.createElement('div')
|
||||
const root = nodeOps.createElement('div')
|
||||
|
@ -741,4 +873,56 @@ describe('renderer: teleport', () => {
|
|||
expect(tRefInMounted).toBe(target.children[1])
|
||||
})
|
||||
}
|
||||
|
||||
test('handle update and hmr rerender', async () => {
|
||||
const target = document.createElement('div')
|
||||
const root = document.createElement('div')
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
const cls = ref('foo')
|
||||
onMounted(() => {
|
||||
// trigger update
|
||||
cls.value = 'bar'
|
||||
})
|
||||
return { cls, target }
|
||||
},
|
||||
template: `
|
||||
<Teleport :to="target">
|
||||
<div :class="cls">
|
||||
<div>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
</Teleport>
|
||||
`,
|
||||
}
|
||||
|
||||
const appId = 'test-app-id'
|
||||
const App = {
|
||||
__hmrId: appId,
|
||||
components: { Comp },
|
||||
render() {
|
||||
return originalH(Comp, null, { default: () => originalH('div', 'foo') })
|
||||
},
|
||||
}
|
||||
createRecord(appId, App)
|
||||
|
||||
domRender(originalH(App), root)
|
||||
expect(target.innerHTML).toBe(
|
||||
'<div class="foo"><div><div>foo</div></div></div>',
|
||||
)
|
||||
await nextTick()
|
||||
expect(target.innerHTML).toBe(
|
||||
'<div class="bar"><div><div>foo</div></div></div>',
|
||||
)
|
||||
|
||||
rerender(appId, () =>
|
||||
originalH(Comp, null, { default: () => originalH('div', 'bar') }),
|
||||
)
|
||||
await nextTick()
|
||||
expect(target.innerHTML).toBe(
|
||||
'<div class="bar"><div><div>bar</div></div></div>',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import { isReactive, reactive, shallowReactive } from '../../src/index'
|
||||
import {
|
||||
effect,
|
||||
isReactive,
|
||||
reactive,
|
||||
readonly,
|
||||
shallowReactive,
|
||||
} from '../../src/index'
|
||||
import { renderList } from '../../src/helpers/renderList'
|
||||
|
||||
describe('renderList', () => {
|
||||
|
@ -65,4 +71,31 @@ describe('renderList', () => {
|
|||
const shallowReactiveArray = shallowReactive([{ foo: 1 }])
|
||||
expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
|
||||
})
|
||||
|
||||
it('should not allow mutation', () => {
|
||||
const arr = readonly(reactive([{ foo: 1 }]))
|
||||
expect(
|
||||
renderList(arr, item => {
|
||||
;(item as any).foo = 0
|
||||
return item.foo
|
||||
}),
|
||||
).toEqual([1])
|
||||
expect(
|
||||
`Set operation on key "foo" failed: target is readonly.`,
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
it('should trigger effect for deep mutations in readonly reactive arrays', () => {
|
||||
const arr = reactive([{ foo: 1 }])
|
||||
const readonlyArr = readonly(arr)
|
||||
|
||||
let dummy
|
||||
effect(() => {
|
||||
dummy = renderList(readonlyArr, item => item.foo)
|
||||
})
|
||||
expect(dummy).toEqual([1])
|
||||
|
||||
arr[0].foo = 2
|
||||
expect(dummy).toEqual([2])
|
||||
})
|
||||
})
|
||||
|
|
|
@ -32,10 +32,13 @@ import {
|
|||
withCtx,
|
||||
withDirectives,
|
||||
} from '@vue/runtime-dom'
|
||||
import type { HMRRuntime } from '../src/hmr'
|
||||
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
||||
import { PatchFlags, normalizeStyle } from '@vue/shared'
|
||||
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
||||
import { expect } from 'vitest'
|
||||
|
||||
declare var __VUE_HMR_RUNTIME__: HMRRuntime
|
||||
const { createRecord, reload } = __VUE_HMR_RUNTIME__
|
||||
|
||||
function mountWithHydration(html: string, render: () => any) {
|
||||
const container = document.createElement('div')
|
||||
|
@ -1651,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(
|
||||
|
@ -1843,6 +1869,60 @@ describe('SSR hydration', () => {
|
|||
}
|
||||
})
|
||||
|
||||
test('hmr reload child wrapped in KeepAlive', async () => {
|
||||
const id = 'child-reload'
|
||||
const Child = {
|
||||
__hmrId: id,
|
||||
template: `<div>foo</div>`,
|
||||
}
|
||||
createRecord(id, Child)
|
||||
|
||||
const appId = 'test-app-id'
|
||||
const App = {
|
||||
__hmrId: appId,
|
||||
components: { Child },
|
||||
template: `
|
||||
<div>
|
||||
<KeepAlive>
|
||||
<Child />
|
||||
</KeepAlive>
|
||||
</div>
|
||||
`,
|
||||
}
|
||||
|
||||
const root = document.createElement('div')
|
||||
root.innerHTML = await renderToString(h(App))
|
||||
createSSRApp(App).mount(root)
|
||||
expect(root.innerHTML).toBe('<div><div>foo</div></div>')
|
||||
|
||||
reload(id, {
|
||||
__hmrId: id,
|
||||
template: `<div>bar</div>`,
|
||||
})
|
||||
await nextTick()
|
||||
expect(root.innerHTML).toBe('<div><div>bar</div></div>')
|
||||
})
|
||||
|
||||
test('hmr root reload', async () => {
|
||||
const appId = 'test-app-id'
|
||||
const App = {
|
||||
__hmrId: appId,
|
||||
template: `<div>foo</div>`,
|
||||
}
|
||||
|
||||
const root = document.createElement('div')
|
||||
root.innerHTML = await renderToString(h(App))
|
||||
createSSRApp(App).mount(root)
|
||||
expect(root.innerHTML).toBe('<div>foo</div>')
|
||||
|
||||
reload(appId, {
|
||||
__hmrId: appId,
|
||||
template: `<div>bar</div>`,
|
||||
})
|
||||
await nextTick()
|
||||
expect(root.innerHTML).toBe('<div>bar</div>')
|
||||
})
|
||||
|
||||
describe('mismatch handling', () => {
|
||||
test('text node', () => {
|
||||
const { container } = mountWithHydration(`foo`, () => 'bar')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.13",
|
||||
"version": "3.5.15",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
type ComponentOptions,
|
||||
type ConcreteComponent,
|
||||
currentInstance,
|
||||
getComponentName,
|
||||
isInSSRComponentSetup,
|
||||
} from './component'
|
||||
import { isFunction, isObject } from '@vue/shared'
|
||||
|
@ -121,14 +122,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) {
|
||||
|
|
|
@ -22,7 +22,7 @@ import { warn } from './warning'
|
|||
import { type VNode, cloneVNode, createVNode } from './vnode'
|
||||
import type { RootHydrateFunction } from './hydration'
|
||||
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'
|
||||
|
@ -36,9 +36,9 @@ export interface App<HostElement = any> {
|
|||
|
||||
use<Options extends unknown[]>(
|
||||
plugin: Plugin<Options>,
|
||||
...options: Options
|
||||
...options: NoInfer<Options>
|
||||
): this
|
||||
use<Options>(plugin: Plugin<Options>, options: Options): this
|
||||
use<Options>(plugin: Plugin<Options>, options: NoInfer<Options>): this
|
||||
|
||||
mixin(mixin: ComponentOptions): this
|
||||
component(name: string): Component | undefined
|
||||
|
@ -215,9 +215,11 @@ export type ObjectPlugin<Options = any[]> = {
|
|||
export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> &
|
||||
Partial<ObjectPlugin<Options>>
|
||||
|
||||
export type Plugin<Options = any[]> =
|
||||
| FunctionPlugin<Options>
|
||||
| ObjectPlugin<Options>
|
||||
export type Plugin<
|
||||
Options = any[],
|
||||
// TODO: in next major Options extends unknown[] and remove P
|
||||
P extends unknown[] = Options extends unknown[] ? Options : [Options],
|
||||
> = FunctionPlugin<P> | ObjectPlugin<P>
|
||||
|
||||
export function createAppContext(): AppContext {
|
||||
return {
|
||||
|
@ -381,13 +383,12 @@ export function createAppAPI<HostElement>(
|
|||
// HMR root reload
|
||||
if (__DEV__) {
|
||||
context.reload = () => {
|
||||
const cloned = cloneVNode(vnode)
|
||||
// avoid hydration for hmr updating
|
||||
cloned.el = null
|
||||
// casting to ElementNamespace because TS doesn't guarantee type narrowing
|
||||
// over function boundaries
|
||||
render(
|
||||
cloneVNode(vnode),
|
||||
rootContainer,
|
||||
namespace as ElementNamespace,
|
||||
)
|
||||
render(cloned, rootContainer, namespace as ElementNamespace)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -447,10 +448,18 @@ export function createAppAPI<HostElement>(
|
|||
|
||||
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.vnode.appContext && instance.vnode.appContext.provides
|
||||
: instance.parent.provides
|
||||
: undefined
|
||||
|
|
|
@ -115,20 +115,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
|
||||
|
||||
/**
|
||||
|
@ -259,7 +262,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,
|
||||
|
@ -267,8 +270,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 }
|
||||
|
||||
|
@ -806,7 +809,7 @@ export function setupComponent(
|
|||
const { props, children } = instance.vnode
|
||||
const isStateful = isStatefulComponent(instance)
|
||||
initProps(instance, props, isStateful, isSSR)
|
||||
initSlots(instance, children, optimized)
|
||||
initSlots(instance, children, optimized || isSSR)
|
||||
|
||||
const setupResult = isStateful
|
||||
? setupStatefulComponent(instance, isSSR)
|
||||
|
|
|
@ -143,7 +143,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
|
||||
|
|
|
@ -17,7 +17,11 @@ import {
|
|||
} from '@vue/shared'
|
||||
import { warn } from './warning'
|
||||
import { isKeepAlive } from './components/KeepAlive'
|
||||
import { type ContextualRenderFn, withCtx } from './componentRenderContext'
|
||||
import {
|
||||
type ContextualRenderFn,
|
||||
currentRenderingInstance,
|
||||
withCtx,
|
||||
} from './componentRenderContext'
|
||||
import { isHmrUpdating } from './hmr'
|
||||
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
|
||||
import { TriggerOpTypes, trigger } from '@vue/reactivity'
|
||||
|
@ -75,6 +79,11 @@ export type RawSlots = {
|
|||
* @internal
|
||||
*/
|
||||
_?: SlotFlags
|
||||
/**
|
||||
* cache indexes for slot content
|
||||
* @internal
|
||||
*/
|
||||
__?: number[]
|
||||
}
|
||||
|
||||
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
||||
|
@ -97,7 +106,8 @@ const normalizeSlot = (
|
|||
if (
|
||||
__DEV__ &&
|
||||
currentInstance &&
|
||||
(!ctx || ctx.root === currentInstance.root)
|
||||
!(ctx === null && currentRenderingInstance) &&
|
||||
!(ctx && ctx.root !== currentInstance.root)
|
||||
) {
|
||||
warn(
|
||||
`Slot "${key}" invoked outside of the render function: ` +
|
||||
|
@ -170,7 +180,7 @@ const assignSlots = (
|
|||
// when rendering the optimized slots by manually written render function,
|
||||
// do not copy the `slots._` compiler flag so that `renderSlot` creates
|
||||
// slot Fragment with BAIL patchFlag to force full updates
|
||||
if (optimized || key !== '_') {
|
||||
if (optimized || !isInternalKey(key)) {
|
||||
slots[key] = children[key]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -501,9 +501,8 @@ function getInnerChild(vnode: VNode): VNode | undefined {
|
|||
|
||||
return vnode
|
||||
}
|
||||
// #7121 ensure get the child component subtree in case
|
||||
// it's been replaced during HMR
|
||||
if (__DEV__ && vnode.component) {
|
||||
// #7121,#12465 get the component subtree if it's been mounted
|
||||
if (vnode.component) {
|
||||
return vnode.component.subTree
|
||||
}
|
||||
|
||||
|
|
|
@ -187,6 +187,11 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
// Update components tree
|
||||
devtoolsComponentAdded(instance)
|
||||
}
|
||||
|
||||
// for e2e test
|
||||
if (__DEV__ && __BROWSER__) {
|
||||
;(instance as any).__keepAliveStorageContainer = storageContainer
|
||||
}
|
||||
}
|
||||
|
||||
function unmount(vnode: VNode) {
|
||||
|
|
|
@ -164,15 +164,16 @@ export const TeleportImpl = {
|
|||
}
|
||||
|
||||
if (isTeleportDeferred(n2.props)) {
|
||||
n2.el!.__isMounted = false
|
||||
queuePostRenderEffect(() => {
|
||||
mountToTarget()
|
||||
n2.el!.__isMounted = true
|
||||
delete n2.el!.__isMounted
|
||||
}, parentSuspense)
|
||||
} else {
|
||||
mountToTarget()
|
||||
}
|
||||
} else {
|
||||
if (isTeleportDeferred(n2.props) && !n1.el!.__isMounted) {
|
||||
if (isTeleportDeferred(n2.props) && n1.el!.__isMounted === false) {
|
||||
queuePostRenderEffect(() => {
|
||||
TeleportImpl.process(
|
||||
n1,
|
||||
|
@ -186,7 +187,6 @@ export const TeleportImpl = {
|
|||
optimized,
|
||||
internals,
|
||||
)
|
||||
delete n1.el!.__isMounted
|
||||
}, parentSuspense)
|
||||
return
|
||||
}
|
||||
|
@ -220,7 +220,8 @@ export const TeleportImpl = {
|
|||
// even in block tree mode we need to make sure all root-level nodes
|
||||
// in the teleport inherit previous DOM references so that they can
|
||||
// be moved in future patches.
|
||||
traverseStaticChildren(n1, n2, true)
|
||||
// in dev mode, deep traversal is necessary for HMR
|
||||
traverseStaticChildren(n1, n2, !__DEV__)
|
||||
} else if (!optimized) {
|
||||
patchChildren(
|
||||
n1,
|
||||
|
|
|
@ -4,6 +4,8 @@ import {
|
|||
isReadonly,
|
||||
isRef,
|
||||
isShallow,
|
||||
pauseTracking,
|
||||
resetTracking,
|
||||
toRaw,
|
||||
} from '@vue/reactivity'
|
||||
import { EMPTY_OBJ, extend, isArray, isFunction, isObject } from '@vue/shared'
|
||||
|
@ -34,13 +36,16 @@ export function initCustomFormatter(): void {
|
|||
if (obj.__isVue) {
|
||||
return ['div', vueStyle, `VueInstance`]
|
||||
} else if (isRef(obj)) {
|
||||
// avoid tracking during debugger accessing
|
||||
pauseTracking()
|
||||
const value = obj.value
|
||||
resetTracking()
|
||||
return [
|
||||
'div',
|
||||
{},
|
||||
['span', vueStyle, genRefFlag(obj)],
|
||||
'<',
|
||||
// avoid debugger accessing value affecting behavior
|
||||
formatValue('_value' in obj ? obj._value : obj),
|
||||
formatValue(value),
|
||||
`>`,
|
||||
]
|
||||
} else if (isReactive(obj)) {
|
||||
|
|
|
@ -111,7 +111,9 @@ export type Directive<
|
|||
| ObjectDirective<HostElement, Value, Modifiers, Arg>
|
||||
| FunctionDirective<HostElement, Value, Modifiers, Arg>
|
||||
|
||||
export type DirectiveModifiers<K extends string = string> = Record<K, boolean>
|
||||
export type DirectiveModifiers<K extends string = string> = Partial<
|
||||
Record<K, boolean>
|
||||
>
|
||||
|
||||
export function validateDirectiveName(name: string): void {
|
||||
if (isBuiltInDirective(name)) {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import type { VNode, VNodeChild } from '../vnode'
|
||||
import {
|
||||
isReactive,
|
||||
isReadonly,
|
||||
isShallow,
|
||||
shallowReadArray,
|
||||
toReactive,
|
||||
toReadonly,
|
||||
} from '@vue/reactivity'
|
||||
import { isArray, isObject, isString } from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
|
@ -69,14 +71,20 @@ export function renderList(
|
|||
if (sourceIsArray || isString(source)) {
|
||||
const sourceIsReactiveArray = sourceIsArray && isReactive(source)
|
||||
let needsWrap = false
|
||||
let isReadonlySource = false
|
||||
if (sourceIsReactiveArray) {
|
||||
needsWrap = !isShallow(source)
|
||||
isReadonlySource = isReadonly(source)
|
||||
source = shallowReadArray(source)
|
||||
}
|
||||
ret = new Array(source.length)
|
||||
for (let i = 0, l = source.length; i < l; i++) {
|
||||
ret[i] = renderItem(
|
||||
needsWrap ? toReactive(source[i]) : source[i],
|
||||
needsWrap
|
||||
? isReadonlySource
|
||||
? toReadonly(toReactive(source[i]))
|
||||
: toReactive(source[i])
|
||||
: source[i],
|
||||
i,
|
||||
undefined,
|
||||
cached && cached[i],
|
||||
|
|
|
@ -5,9 +5,11 @@ import { EMPTY_OBJ } from '@vue/shared'
|
|||
|
||||
export const knownTemplateRefs: WeakSet<ShallowRef> = new WeakSet()
|
||||
|
||||
export type TemplateRef<T = unknown> = Readonly<ShallowRef<T | null>>
|
||||
|
||||
export function useTemplateRef<T = unknown, Keys extends string = string>(
|
||||
key: Keys,
|
||||
): Readonly<ShallowRef<T | null>> {
|
||||
): TemplateRef<T> {
|
||||
const i = getCurrentInstance()
|
||||
const r = shallowRef(null)
|
||||
if (i) {
|
||||
|
|
|
@ -398,9 +398,11 @@ export function createHydrationFunctions(
|
|||
parentComponent.vnode.props.appear
|
||||
|
||||
const content = (el as HTMLTemplateElement).content
|
||||
.firstChild as Element
|
||||
.firstChild as Element & { $cls?: string }
|
||||
|
||||
if (needCallTransitionHooks) {
|
||||
const cls = content.getAttribute('class')
|
||||
if (cls) content.$cls = cls
|
||||
transition!.beforeEnter(content)
|
||||
}
|
||||
|
||||
|
@ -786,7 +788,7 @@ export function createHydrationFunctions(
|
|||
* Dev only
|
||||
*/
|
||||
function propHasMismatch(
|
||||
el: Element,
|
||||
el: Element & { $cls?: string },
|
||||
key: string,
|
||||
clientValue: any,
|
||||
vnode: VNode,
|
||||
|
@ -799,7 +801,12 @@ function propHasMismatch(
|
|||
if (key === 'class') {
|
||||
// classes might be in different order, but that doesn't affect cascade
|
||||
// so we just need to check if the class lists contain the same classes.
|
||||
actual = el.getAttribute('class')
|
||||
if (el.$cls) {
|
||||
actual = el.$cls
|
||||
delete el.$cls
|
||||
} else {
|
||||
actual = el.getAttribute('class')
|
||||
}
|
||||
expected = normalizeClass(clientValue)
|
||||
if (!isSetEqual(toClassSet(actual || ''), toClassSet(expected))) {
|
||||
mismatchType = MismatchTypes.CLASS
|
||||
|
|
|
@ -64,7 +64,7 @@ export { defineComponent } from './apiDefineComponent'
|
|||
export { defineAsyncComponent } from './apiAsyncComponent'
|
||||
export { useAttrs, useSlots } from './apiSetupHelpers'
|
||||
export { useModel } from './helpers/useModel'
|
||||
export { useTemplateRef } from './helpers/useTemplateRef'
|
||||
export { useTemplateRef, type TemplateRef } from './helpers/useTemplateRef'
|
||||
export { useId } from './helpers/useId'
|
||||
export {
|
||||
hydrateOnIdle,
|
||||
|
|
|
@ -961,7 +961,8 @@ function baseCreateRenderer(
|
|||
// which also requires the correct parent container
|
||||
!isSameVNodeType(oldVNode, newVNode) ||
|
||||
// - In the case of a component, it could contain anything.
|
||||
oldVNode.shapeFlag & (ShapeFlags.COMPONENT | ShapeFlags.TELEPORT))
|
||||
oldVNode.shapeFlag &
|
||||
(ShapeFlags.COMPONENT | ShapeFlags.TELEPORT | ShapeFlags.SUSPENSE))
|
||||
? hostParentNode(oldVNode.el)!
|
||||
: // In other cases, the parent container is not actually used so we
|
||||
// just pass the block element here to avoid a DOM parentNode call.
|
||||
|
@ -1208,12 +1209,12 @@ function baseCreateRenderer(
|
|||
}
|
||||
}
|
||||
|
||||
// avoid hydration for hmr updating
|
||||
if (__DEV__ && isHmrUpdating) initialVNode.el = null
|
||||
|
||||
// setup() is async. This component relies on async logic to be resolved
|
||||
// before proceeding
|
||||
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
|
||||
// avoid hydration for hmr updating
|
||||
if (__DEV__ && isHmrUpdating) initialVNode.el = null
|
||||
|
||||
parentSuspense &&
|
||||
parentSuspense.registerDep(instance, setupRenderEffect, optimized)
|
||||
|
||||
|
@ -2049,7 +2050,13 @@ function baseCreateRenderer(
|
|||
queuePostRenderEffect(() => transition!.enter(el!), parentSuspense)
|
||||
} else {
|
||||
const { leave, delayLeave, afterLeave } = transition!
|
||||
const remove = () => hostInsert(el!, container, anchor)
|
||||
const remove = () => {
|
||||
if (vnode.ctx!.isUnmounted) {
|
||||
hostRemove(el!)
|
||||
} else {
|
||||
hostInsert(el!, container, anchor)
|
||||
}
|
||||
}
|
||||
const performLeave = () => {
|
||||
leave(el!, () => {
|
||||
remove()
|
||||
|
@ -2092,7 +2099,9 @@ function baseCreateRenderer(
|
|||
|
||||
// unset ref
|
||||
if (ref != null) {
|
||||
pauseTracking()
|
||||
setRef(ref, null, parentSuspense, vnode, true)
|
||||
resetTracking()
|
||||
}
|
||||
|
||||
// #6593 should clean memo cache when unmount
|
||||
|
@ -2256,7 +2265,17 @@ function baseCreateRenderer(
|
|||
unregisterHMR(instance)
|
||||
}
|
||||
|
||||
const { bum, scope, job, subTree, um, m, a } = instance
|
||||
const {
|
||||
bum,
|
||||
scope,
|
||||
job,
|
||||
subTree,
|
||||
um,
|
||||
m,
|
||||
a,
|
||||
parent,
|
||||
slots: { __: slotCacheKeys },
|
||||
} = instance
|
||||
invalidateMount(m)
|
||||
invalidateMount(a)
|
||||
|
||||
|
@ -2265,6 +2284,13 @@ function baseCreateRenderer(
|
|||
invokeArrayFns(bum)
|
||||
}
|
||||
|
||||
// remove slots content from parent renderCache
|
||||
if (parent && isArray(slotCacheKeys)) {
|
||||
slotCacheKeys.forEach(v => {
|
||||
parent.renderCache[v] = undefined
|
||||
})
|
||||
}
|
||||
|
||||
if (
|
||||
__COMPAT__ &&
|
||||
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
||||
|
@ -2478,11 +2504,15 @@ export function traverseStaticChildren(
|
|||
if (c2.type === Text) {
|
||||
c2.el = c1.el
|
||||
}
|
||||
// also inherit for comment nodes, but not placeholders (e.g. v-if which
|
||||
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
|
||||
// would have received .el during block patch)
|
||||
if (__DEV__ && c2.type === Comment && !c2.el) {
|
||||
if (c2.type === Comment && !c2.el) {
|
||||
c2.el = c1.el
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
c2.el && (c2.el.__vnode = c2)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -444,6 +444,36 @@ describe('defineCustomElement', () => {
|
|||
const e = container.childNodes[0] as VueElement
|
||||
expect(e.shadowRoot!.innerHTML).toBe('hello')
|
||||
})
|
||||
|
||||
test('prop types validation', async () => {
|
||||
const E = defineCustomElement({
|
||||
props: {
|
||||
num: {
|
||||
type: [Number, String],
|
||||
},
|
||||
bool: {
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
render() {
|
||||
return h('div', [
|
||||
h('span', [`${this.num} is ${typeof this.num}`]),
|
||||
h('span', [`${this.bool} is ${typeof this.bool}`]),
|
||||
])
|
||||
},
|
||||
})
|
||||
|
||||
customElements.define('my-el-with-type-props', E)
|
||||
render(h('my-el-with-type-props', { num: 1, bool: true }), container)
|
||||
const e = container.childNodes[0] as VueElement
|
||||
// @ts-expect-error
|
||||
expect(e.num).toBe(1)
|
||||
// @ts-expect-error
|
||||
expect(e.bool).toBe(true)
|
||||
expect(e.shadowRoot!.innerHTML).toBe(
|
||||
'<div><span>1 is number</span><span>true is boolean</span></div>',
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('attrs', () => {
|
||||
|
@ -708,6 +738,101 @@ describe('defineCustomElement', () => {
|
|||
`<div>changedA! changedB!</div>`,
|
||||
)
|
||||
})
|
||||
|
||||
// #13212
|
||||
test('inherited from app context within nested elements', async () => {
|
||||
const outerValues: (string | undefined)[] = []
|
||||
const innerValues: (string | undefined)[] = []
|
||||
const innerChildValues: (string | undefined)[] = []
|
||||
|
||||
const Outer = defineCustomElement(
|
||||
{
|
||||
setup() {
|
||||
outerValues.push(
|
||||
inject<string>('shared'),
|
||||
inject<string>('outer'),
|
||||
inject<string>('inner'),
|
||||
)
|
||||
},
|
||||
render() {
|
||||
return h('div', [renderSlot(this.$slots, 'default')])
|
||||
},
|
||||
},
|
||||
{
|
||||
configureApp(app) {
|
||||
app.provide('shared', 'shared')
|
||||
app.provide('outer', 'outer')
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const Inner = defineCustomElement(
|
||||
{
|
||||
setup() {
|
||||
// ensure values are not self-injected
|
||||
provide('inner', 'inner-child')
|
||||
|
||||
innerValues.push(
|
||||
inject<string>('shared'),
|
||||
inject<string>('outer'),
|
||||
inject<string>('inner'),
|
||||
)
|
||||
},
|
||||
render() {
|
||||
return h('div', [renderSlot(this.$slots, 'default')])
|
||||
},
|
||||
},
|
||||
{
|
||||
configureApp(app) {
|
||||
app.provide('outer', 'override-outer')
|
||||
app.provide('inner', 'inner')
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
const InnerChild = defineCustomElement({
|
||||
setup() {
|
||||
innerChildValues.push(
|
||||
inject<string>('shared'),
|
||||
inject<string>('outer'),
|
||||
inject<string>('inner'),
|
||||
)
|
||||
},
|
||||
render() {
|
||||
return h('div')
|
||||
},
|
||||
})
|
||||
|
||||
customElements.define('provide-from-app-outer', Outer)
|
||||
customElements.define('provide-from-app-inner', Inner)
|
||||
customElements.define('provide-from-app-inner-child', InnerChild)
|
||||
|
||||
container.innerHTML =
|
||||
'<provide-from-app-outer>' +
|
||||
'<provide-from-app-inner>' +
|
||||
'<provide-from-app-inner-child></provide-from-app-inner-child>' +
|
||||
'</provide-from-app-inner>' +
|
||||
'</provide-from-app-outer>'
|
||||
|
||||
const outer = container.childNodes[0] as VueElement
|
||||
expect(outer.shadowRoot!.innerHTML).toBe('<div><slot></slot></div>')
|
||||
|
||||
expect('[Vue warn]: injection "inner" not found.').toHaveBeenWarnedTimes(
|
||||
1,
|
||||
)
|
||||
expect(
|
||||
'[Vue warn]: App already provides property with key "outer" inherited from its parent element. ' +
|
||||
'It will be overwritten with the new value.',
|
||||
).toHaveBeenWarnedTimes(1)
|
||||
|
||||
expect(outerValues).toEqual(['shared', 'outer', undefined])
|
||||
expect(innerValues).toEqual(['shared', 'override-outer', 'inner'])
|
||||
expect(innerChildValues).toEqual([
|
||||
'shared',
|
||||
'override-outer',
|
||||
'inner-child',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('styles', () => {
|
||||
|
@ -1131,6 +1256,92 @@ describe('defineCustomElement', () => {
|
|||
expect(target.innerHTML).toBe(`<span>default</span>`)
|
||||
app.unmount()
|
||||
})
|
||||
|
||||
test('toggle nested custom element with shadowRoot: false', async () => {
|
||||
customElements.define(
|
||||
'my-el-child-shadow-false',
|
||||
defineCustomElement(
|
||||
{
|
||||
render(ctx: any) {
|
||||
return h('div', null, [renderSlot(ctx.$slots, 'default')])
|
||||
},
|
||||
},
|
||||
{ shadowRoot: false },
|
||||
),
|
||||
)
|
||||
const ChildWrapper = {
|
||||
render() {
|
||||
return h('my-el-child-shadow-false', null, 'child')
|
||||
},
|
||||
}
|
||||
|
||||
customElements.define(
|
||||
'my-el-parent-shadow-false',
|
||||
defineCustomElement(
|
||||
{
|
||||
props: {
|
||||
isShown: { type: Boolean, required: true },
|
||||
},
|
||||
render(ctx: any, _: any, $props: any) {
|
||||
return $props.isShown
|
||||
? h('div', { key: 0 }, [renderSlot(ctx.$slots, 'default')])
|
||||
: null
|
||||
},
|
||||
},
|
||||
{ shadowRoot: false },
|
||||
),
|
||||
)
|
||||
const ParentWrapper = {
|
||||
props: {
|
||||
isShown: { type: Boolean, required: true },
|
||||
},
|
||||
render(ctx: any, _: any, $props: any) {
|
||||
return h('my-el-parent-shadow-false', { isShown: $props.isShown }, [
|
||||
renderSlot(ctx.$slots, 'default'),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
const isShown = ref(true)
|
||||
const App = {
|
||||
render() {
|
||||
return h(ParentWrapper, { isShown: isShown.value } as any, {
|
||||
default: () => [h(ChildWrapper)],
|
||||
})
|
||||
},
|
||||
}
|
||||
const container = document.createElement('div')
|
||||
document.body.appendChild(container)
|
||||
const app = createApp(App)
|
||||
app.mount(container)
|
||||
expect(container.innerHTML).toBe(
|
||||
`<my-el-parent-shadow-false is-shown="" data-v-app="">` +
|
||||
`<div>` +
|
||||
`<my-el-child-shadow-false data-v-app="">` +
|
||||
`<div>child</div>` +
|
||||
`</my-el-child-shadow-false>` +
|
||||
`</div>` +
|
||||
`</my-el-parent-shadow-false>`,
|
||||
)
|
||||
|
||||
isShown.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<my-el-parent-shadow-false data-v-app=""><!----></my-el-parent-shadow-false>`,
|
||||
)
|
||||
|
||||
isShown.value = true
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<my-el-parent-shadow-false data-v-app="" is-shown="">` +
|
||||
`<div>` +
|
||||
`<my-el-child-shadow-false data-v-app="">` +
|
||||
`<div>child</div>` +
|
||||
`</my-el-child-shadow-false>` +
|
||||
`</div>` +
|
||||
`</my-el-parent-shadow-false>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('helpers', () => {
|
||||
|
@ -1330,6 +1541,41 @@ describe('defineCustomElement', () => {
|
|||
|
||||
expect(e.shadowRoot?.innerHTML).toBe('<div>app-injected</div>')
|
||||
})
|
||||
|
||||
test('with hmr reload', async () => {
|
||||
const __hmrId = '__hmrWithApp'
|
||||
const def = defineComponent({
|
||||
__hmrId,
|
||||
setup() {
|
||||
const msg = inject('msg')
|
||||
return { msg }
|
||||
},
|
||||
render(this: any) {
|
||||
return h('div', [h('span', this.msg), h('span', this.$foo)])
|
||||
},
|
||||
})
|
||||
const E = defineCustomElement(def, {
|
||||
configureApp(app) {
|
||||
app.provide('msg', 'app-injected')
|
||||
app.config.globalProperties.$foo = 'foo'
|
||||
},
|
||||
})
|
||||
customElements.define('my-element-with-app-hmr', E)
|
||||
|
||||
container.innerHTML = `<my-element-with-app-hmr></my-element-with-app-hmr>`
|
||||
const el = container.childNodes[0] as VueElement
|
||||
expect(el.shadowRoot?.innerHTML).toBe(
|
||||
`<div><span>app-injected</span><span>foo</span></div>`,
|
||||
)
|
||||
|
||||
// hmr
|
||||
__VUE_HMR_RUNTIME__.reload(__hmrId, def as any)
|
||||
|
||||
await nextTick()
|
||||
expect(el.shadowRoot?.innerHTML).toBe(
|
||||
`<div><span>app-injected</span><span>foo</span></div>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// #9885
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.5.13",
|
||||
"version": "3.5.15",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
|
|
|
@ -269,18 +269,14 @@ export class VueElement
|
|||
this._root = this
|
||||
}
|
||||
}
|
||||
|
||||
if (!(this._def as ComponentOptions).__asyncLoader) {
|
||||
// for sync component defs we can immediately resolve props
|
||||
this._resolveProps(this._def)
|
||||
}
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
// avoid resolving component if it's not connected
|
||||
if (!this.isConnected) return
|
||||
|
||||
if (!this.shadowRoot) {
|
||||
// avoid re-parsing slots if already resolved
|
||||
if (!this.shadowRoot && !this._resolved) {
|
||||
this._parseSlots()
|
||||
}
|
||||
this._connected = true
|
||||
|
@ -298,8 +294,7 @@ export class VueElement
|
|||
|
||||
if (!this._instance) {
|
||||
if (this._resolved) {
|
||||
this._setParent()
|
||||
this._update()
|
||||
this._mount(this._def)
|
||||
} else {
|
||||
if (parent && parent._pendingResolve) {
|
||||
this._pendingResolve = parent._pendingResolve.then(() => {
|
||||
|
@ -316,7 +311,18 @@ export class VueElement
|
|||
private _setParent(parent = this._parent) {
|
||||
if (parent) {
|
||||
this._instance!.parent = parent._instance
|
||||
this._instance!.provides = parent._instance!.provides
|
||||
this._inheritParentContext(parent)
|
||||
}
|
||||
}
|
||||
|
||||
private _inheritParentContext(parent = this._parent) {
|
||||
// #13212, the provides object of the app context must inherit the provides
|
||||
// object from the parent element so we can inject values from both places
|
||||
if (parent && this._app) {
|
||||
Object.setPrototypeOf(
|
||||
this._app._context.provides,
|
||||
parent._instance!.provides,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -380,12 +386,7 @@ export class VueElement
|
|||
}
|
||||
}
|
||||
this._numberProps = numberProps
|
||||
|
||||
if (isAsync) {
|
||||
// defining getter/setters on prototype
|
||||
// for sync defs, this already happened in the constructor
|
||||
this._resolveProps(def)
|
||||
}
|
||||
this._resolveProps(def)
|
||||
|
||||
// apply CSS
|
||||
if (this.shadowRoot) {
|
||||
|
@ -417,6 +418,8 @@ export class VueElement
|
|||
def.name = 'VueElement'
|
||||
}
|
||||
this._app = this._createApp(def)
|
||||
// inherit before configureApp to detect context overwrites
|
||||
this._inheritParentContext()
|
||||
if (def.configureApp) {
|
||||
def.configureApp(this._app)
|
||||
}
|
||||
|
@ -520,7 +523,9 @@ export class VueElement
|
|||
}
|
||||
|
||||
private _update() {
|
||||
render(this._createVNode(), this._root)
|
||||
const vnode = this._createVNode()
|
||||
if (this._app) vnode.appContext = this._app._context
|
||||
render(vnode, this._root)
|
||||
}
|
||||
|
||||
private _createVNode(): VNode<any, any> {
|
||||
|
|
|
@ -81,6 +81,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
|
|||
moveClass,
|
||||
)
|
||||
) {
|
||||
prevChildren = []
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -110,6 +111,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
|
|||
})
|
||||
el.addEventListener('transitionend', cb)
|
||||
})
|
||||
prevChildren = []
|
||||
})
|
||||
|
||||
return () => {
|
||||
|
|
|
@ -79,6 +79,7 @@ export function compatCoerceAttr(
|
|||
}
|
||||
} else if (
|
||||
value === false &&
|
||||
!(el.tagName === 'INPUT' && key === 'value') &&
|
||||
!isSpecialBooleanAttr(key) &&
|
||||
compatUtils.isCompatEnabled(DeprecationTypes.ATTR_FALSE_VALUE, instance)
|
||||
) {
|
||||
|
|
|
@ -102,7 +102,12 @@ function shouldSetAsProp(
|
|||
// them as attributes.
|
||||
// Note that `contentEditable` doesn't have this problem: its DOM
|
||||
// property is also enumerated string values.
|
||||
if (key === 'spellcheck' || key === 'draggable' || key === 'translate') {
|
||||
if (
|
||||
key === 'spellcheck' ||
|
||||
key === 'draggable' ||
|
||||
key === 'translate' ||
|
||||
key === 'autocorrect'
|
||||
) {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { createApp } from 'vue'
|
||||
import { createApp, defineAsyncComponent, h } from 'vue'
|
||||
import { renderToString } from '../src/renderToString'
|
||||
|
||||
const components = {
|
||||
|
@ -154,6 +154,38 @@ describe('ssr: slot', () => {
|
|||
).toBe(`<div><p>1</p><p>2</p></div>`)
|
||||
})
|
||||
|
||||
// #12438
|
||||
test('async component slot with v-if true', async () => {
|
||||
const Layout = defineAsyncComponent(() =>
|
||||
Promise.resolve({
|
||||
template: `<div><slot name="header">default header</slot></div>`,
|
||||
}),
|
||||
)
|
||||
const LayoutLoader = {
|
||||
setup(_: any, context: any) {
|
||||
return () => h(Layout, {}, context.slots)
|
||||
},
|
||||
}
|
||||
expect(
|
||||
await renderToString(
|
||||
createApp({
|
||||
components: {
|
||||
LayoutLoader,
|
||||
},
|
||||
template: `
|
||||
<Suspense>
|
||||
<LayoutLoader>
|
||||
<template v-if="true" #header>
|
||||
new header
|
||||
</template>
|
||||
</LayoutLoader>
|
||||
</Suspense>
|
||||
`,
|
||||
}),
|
||||
),
|
||||
).toBe(`<div><!--[--> new header <!--]--></div>`)
|
||||
})
|
||||
|
||||
// #11326
|
||||
test('dynamic component slot', async () => {
|
||||
expect(
|
||||
|
|
|
@ -49,7 +49,7 @@ test('pipeToWebWritable', async () => {
|
|||
}
|
||||
|
||||
const { readable, writable } = new TransformStream()
|
||||
pipeToWebWritable(createApp(App), {}, writable)
|
||||
pipeToWebWritable(createApp(App), {}, writable as any)
|
||||
|
||||
const reader = readable.getReader()
|
||||
const decoder = new TextDecoder()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.5.13",
|
||||
"version": "3.5.15",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/shared",
|
||||
"version": "3.5.13",
|
||||
"version": "3.5.15",
|
||||
"description": "internal utils shared across @vue packages",
|
||||
"main": "index.js",
|
||||
"module": "dist/shared.esm-bundler.js",
|
||||
|
|
|
@ -93,6 +93,16 @@ test('COMPILER_V_BIND_OBJECT_ORDER', () => {
|
|||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should not warn COMPILER_V_BIND_OBJECT_ORDER work with vFor', () => {
|
||||
const vm = new Vue({
|
||||
template: `<div><div v-bind="{ id: 'bar', class: 'baz' }" v-for="item in 5" /></div>`,
|
||||
}).$mount()
|
||||
expect(vm.$el).toBeInstanceOf(HTMLDivElement)
|
||||
expect(
|
||||
CompilerDeprecationTypes.COMPILER_V_BIND_OBJECT_ORDER,
|
||||
).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('COMPILER_V_ON_NATIVE', () => {
|
||||
const spy = vi.fn()
|
||||
const vm = new Vue({
|
||||
|
|
|
@ -208,6 +208,20 @@ test('ATTR_FALSE_VALUE', () => {
|
|||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('ATTR_FALSE_VALUE with false on input value', () => {
|
||||
const vm = new Vue({
|
||||
template: `<input :value="false"/>`,
|
||||
}).$mount()
|
||||
expect(vm.$el).toBeInstanceOf(HTMLInputElement)
|
||||
expect(vm.$el.hasAttribute('value')).toBe(true)
|
||||
expect(vm.$el.getAttribute('value')).toBe('false')
|
||||
expect(
|
||||
(deprecationData[DeprecationTypes.ATTR_FALSE_VALUE].message as Function)(
|
||||
'value',
|
||||
),
|
||||
).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test("ATTR_FALSE_VALUE with false value shouldn't throw warning", () => {
|
||||
const vm = new Vue({
|
||||
template: `<div :id="false" :foo="false"/>`,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue