mirror of https://github.com/vuejs/core.git
chore: Merge branch 'main' into edison/fix/13460
This commit is contained in:
commit
1aa2a6b12a
|
@ -11,7 +11,7 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
@ -31,4 +31,4 @@ jobs:
|
|||
- name: Run prettier
|
||||
run: pnpm run format
|
||||
|
||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
name: canary minor release
|
||||
on:
|
||||
# Runs every Monday at 1 AM UTC (9:00 AM in Singapore)
|
||||
schedule:
|
||||
- cron: 0 1 * * MON
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
canary:
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
environment: Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: minor
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- run: pnpm release --canary --publish --tag minor
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@ -1,31 +0,0 @@
|
|||
name: canary release
|
||||
on:
|
||||
# Runs every Monday at 1 AM UTC (9:00 AM in Singapore)
|
||||
schedule:
|
||||
- cron: 0 1 * * MON
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
canary:
|
||||
# prevents this action from running on forks
|
||||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
environment: Release
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
cache: 'pnpm'
|
||||
|
||||
- run: pnpm install
|
||||
|
||||
- run: pnpm release --canary --publish
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
|||
environment: Release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
@ -36,12 +36,13 @@ jobs:
|
|||
- name: Install deps
|
||||
run: pnpm install
|
||||
|
||||
- name: Update npm
|
||||
run: npm i -g npm@latest
|
||||
|
||||
- name: Build and publish
|
||||
id: publish
|
||||
run: |
|
||||
pnpm release --publishOnly
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Create GitHub release
|
||||
id: release_tag
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
|||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
@ -37,7 +37,7 @@ jobs:
|
|||
run: pnpm install
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
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@v9
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: size-data.yml
|
||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
@ -32,7 +32,7 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
@ -54,7 +54,7 @@ jobs:
|
|||
e2e-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup cache for Chromium binary
|
||||
uses: actions/cache@v4
|
||||
|
@ -85,7 +85,7 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
|
|
99
CHANGELOG.md
99
CHANGELOG.md
|
@ -1,3 +1,100 @@
|
|||
## [3.5.21](https://github.com/vuejs/core/compare/v3.5.20...v3.5.21) (2025-09-02)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** force dynamic slots when slot referencing scope vars ([#9427](https://github.com/vuejs/core/issues/9427)) ([99d54b2](https://github.com/vuejs/core/commit/99d54b28b46dbea006205dff71c383a31dd1b87a)), closes [#9380](https://github.com/vuejs/core/issues/9380)
|
||||
* **compiler-sfc:** check lang before attempt to compile script ([#13508](https://github.com/vuejs/core/issues/13508)) ([55922ff](https://github.com/vuejs/core/commit/55922ff3168a1397ad72f18946eb1c4051cdab3b)), closes [#8368](https://github.com/vuejs/core/issues/8368)
|
||||
* **compiler-sfc:** support `${configDir}` in paths for TypeScript 5.5+ ([#13491](https://github.com/vuejs/core/issues/13491)) ([8696e34](https://github.com/vuejs/core/commit/8696e346b4780d88247464490f1a992cc0c3658c)), closes [#13484](https://github.com/vuejs/core/issues/13484)
|
||||
* **compiler-sfc:** support global augments with named exports ([#13789](https://github.com/vuejs/core/issues/13789)) ([35da3c6](https://github.com/vuejs/core/commit/35da3c6dcb30030ef60fa22e30aa83a56e396c60))
|
||||
* **custom-element:** prevent defineCustomElement from mutating the options object ([#13791](https://github.com/vuejs/core/issues/13791)) ([e322436](https://github.com/vuejs/core/commit/e322436887549c129e61eb58a0084167103451bb))
|
||||
* **hmr:** prevent `__VUE_HMR_RUNTIME__` from being overwritten by vue runtime in 3rd-party libraries ([#13817](https://github.com/vuejs/core/issues/13817)) ([1392734](https://github.com/vuejs/core/commit/1392734ae5d5a3b2be124753e198eafa324f6815)), closes [vitejs/vite-plugin-vue#644](https://github.com/vitejs/vite-plugin-vue/issues/644)
|
||||
* **hmr:** prevent update unmounting component during HMR reload ([#13815](https://github.com/vuejs/core/issues/13815)) ([ef20b86](https://github.com/vuejs/core/commit/ef20b86b36a127e317f8981df970dc8efd277053)), closes [vitejs/vite-plugin-vue#599](https://github.com/vitejs/vite-plugin-vue/issues/599)
|
||||
* **runtime-core:** disable tracking block in h function ([#8213](https://github.com/vuejs/core/issues/8213)) ([8f6b505](https://github.com/vuejs/core/commit/8f6b5050518441a5047d128138da44f798836002)), closes [#6913](https://github.com/vuejs/core/issues/6913)
|
||||
* **runtime-core:** use separate emits caches for components and mixins ([#11661](https://github.com/vuejs/core/issues/11661)) ([15fc75f](https://github.com/vuejs/core/commit/15fc75f4031dea805c3bbb67a75e48a9dc307c11))
|
||||
* **Suspence:** handle Suspense + KeepAlive HMR updating edge case ([#13076](https://github.com/vuejs/core/issues/13076)) ([5d75a17](https://github.com/vuejs/core/commit/5d75a170c8d23acd11ef2513173d4cbc4d0b54de)), closes [#13075](https://github.com/vuejs/core/issues/13075)
|
||||
* **Teleport:** hydrate disabled Teleport with undefined target ([#11235](https://github.com/vuejs/core/issues/11235)) ([00978f7](https://github.com/vuejs/core/commit/00978f7d14e85b49d9d334ea92fa8c03733ce64c)), closes [#11230](https://github.com/vuejs/core/issues/11230)
|
||||
* **templateRef:** prevent unnecessary set ref on dynamic ref change or component unmount ([#12642](https://github.com/vuejs/core/issues/12642)) ([93ba107](https://github.com/vuejs/core/commit/93ba10767230872fcdca974a1e19e8bd69b7eb6a)), closes [#12639](https://github.com/vuejs/core/issues/12639)
|
||||
* **watch:** use maximum depth for duplicates ([#13434](https://github.com/vuejs/core/issues/13434)) ([f2699a5](https://github.com/vuejs/core/commit/f2699a5cb376ffa452a54feb171c14411c67287c))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* improve regexp performance with non-capturing groups ([#13567](https://github.com/vuejs/core/issues/13567)) ([1e8b65a](https://github.com/vuejs/core/commit/1e8b65aa4934c94ef6142b4f49cdfb13ba5e6ce5))
|
||||
|
||||
|
||||
|
||||
## [3.5.20](https://github.com/vuejs/core/compare/v3.5.19...v3.5.20) (2025-08-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **runtime-dom:** add name to vShow for prop mismatch check ([#13806](https://github.com/vuejs/core/issues/13806)) ([1031e8d](https://github.com/vuejs/core/commit/1031e8de08b735059217b1ad0057f62565c99c4f)), closes [#13805](https://github.com/vuejs/core/issues/13805) re-fix [#13744](https://github.com/vuejs/core/issues/13744) revert [#13777](https://github.com/vuejs/core/issues/13777)
|
||||
|
||||
|
||||
|
||||
## [3.5.19](https://github.com/vuejs/core/compare/v3.5.18...v3.5.19) (2025-08-21)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** adjacent v-else should cause a compiler error ([#13699](https://github.com/vuejs/core/issues/13699)) ([911e670](https://github.com/vuejs/core/commit/911e67045e2a63e0ecbd198ed4f567530f6d1c17)), closes [#13698](https://github.com/vuejs/core/issues/13698)
|
||||
* **compiler-core:** prevent cached array children from retaining detached dom nodes ([#13691](https://github.com/vuejs/core/issues/13691)) ([7f60ef8](https://github.com/vuejs/core/commit/7f60ef83e735dbd29d323347acecf69f22b06d53)), closes [element-plus/element-plus#21408](https://github.com/element-plus/element-plus/issues/21408) [#13211](https://github.com/vuejs/core/issues/13211)
|
||||
* **compiler-sfc:** improve type inference for generic type aliases types ([#12876](https://github.com/vuejs/core/issues/12876)) ([d9dd628](https://github.com/vuejs/core/commit/d9dd628800ae32e673bdfabfe79f1988037991d0)), closes [#12872](https://github.com/vuejs/core/issues/12872)
|
||||
* **compiler-sfc:** throw mismatched script langs error before invoking babel ([#13194](https://github.com/vuejs/core/issues/13194)) ([0562548](https://github.com/vuejs/core/commit/0562548ab3a040073386021222225e0e9d43c632)), closes [#13193](https://github.com/vuejs/core/issues/13193)
|
||||
* **compiler-ssr:** disable v-memo transform in ssr vdom fallback branch ([#13725](https://github.com/vuejs/core/issues/13725)) ([0a202d8](https://github.com/vuejs/core/commit/0a202d890ff2a564b1fab51e4ac621708640818e)), closes [#13724](https://github.com/vuejs/core/issues/13724)
|
||||
* **devtools:** clear performance measures ([#13701](https://github.com/vuejs/core/issues/13701)) ([c875019](https://github.com/vuejs/core/commit/c875019d49b4c36a88d929ccadc31ad414747c7b)), closes [#13700](https://github.com/vuejs/core/issues/13700)
|
||||
* **hmr:** prevent updating unmounting component during HMR rerender ([#13775](https://github.com/vuejs/core/issues/13775)) ([6e5143d](https://github.com/vuejs/core/commit/6e5143d9635dac3f20fb394a827109df30e232ae)), closes [#13771](https://github.com/vuejs/core/issues/13771) [#13772](https://github.com/vuejs/core/issues/13772)
|
||||
* **hydration:** also set vShow name if `__FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__` flag is enabled ([#13777](https://github.com/vuejs/core/issues/13777)) ([439e1a5](https://github.com/vuejs/core/commit/439e1a543e62de4dbf7658d78d05c358c9677c86)), closes [#13744](https://github.com/vuejs/core/issues/13744)
|
||||
* **reactivity:** warn on nested readonly ref update during unwrapping ([#12141](https://github.com/vuejs/core/issues/12141)) ([1498821](https://github.com/vuejs/core/commit/1498821ed9eeb22a0767e53ddc1f6a2840598a29))
|
||||
* **runtime-core:** avoid setting direct ref of useTemplateRef in dev ([#13449](https://github.com/vuejs/core/issues/13449)) ([4a2953f](https://github.com/vuejs/core/commit/4a2953f57b90dfc24e34ff1a87cc1ebb0b97636d))
|
||||
* **runtime-core:** improve consistency of `PublicInstanceProxyHandlers.has` ([#13507](https://github.com/vuejs/core/issues/13507)) ([d7283f3](https://github.com/vuejs/core/commit/d7283f3b7f0631c8b8a4a31a05983dac9f078c4f))
|
||||
* **suspense:** don't immediately resolve suspense on last dep unmount ([#13456](https://github.com/vuejs/core/issues/13456)) ([a871315](https://github.com/vuejs/core/commit/a8713159ee24602c7c2b70c5fd52d2e5cd37dca5)), closes [#13453](https://github.com/vuejs/core/issues/13453)
|
||||
* **transition:** handle KeepAlive + transition leaving edge case ([#13152](https://github.com/vuejs/core/issues/13152)) ([3190b17](https://github.com/vuejs/core/commit/3190b179b0545a3dc4549737793eec630cf9f0d1)), closes [#13153](https://github.com/vuejs/core/issues/13153)
|
||||
|
||||
|
||||
|
||||
## [3.5.18](https://github.com/vuejs/core/compare/v3.5.17...v3.5.18) (2025-07-23)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** avoid cached text vnodes retaining detached DOM nodes ([#13662](https://github.com/vuejs/core/issues/13662)) ([00695a5](https://github.com/vuejs/core/commit/00695a5b41b2d032deaeada83831ff83aa6bfd4e)), closes [#13661](https://github.com/vuejs/core/issues/13661)
|
||||
* **compiler-core:** avoid self updates of `v-pre` ([#12556](https://github.com/vuejs/core/issues/12556)) ([21b685a](https://github.com/vuejs/core/commit/21b685ad9d9d0e6060fc7d07b719bf35f2d9ae1f))
|
||||
* **compiler-core:** identifiers in function parameters should not be inferred as references ([#13548](https://github.com/vuejs/core/issues/13548)) ([9b02923](https://github.com/vuejs/core/commit/9b029239edf88558465b941e1e4c085f92b1ebff))
|
||||
* **compiler-core:** recognize empty string as non-identifier ([#12553](https://github.com/vuejs/core/issues/12553)) ([ce93339](https://github.com/vuejs/core/commit/ce933390ad1c72bed258f7ad959a78f0e8acdf57))
|
||||
* **compiler-core:** transform empty `v-bind` dynamic argument content correctly ([#12554](https://github.com/vuejs/core/issues/12554)) ([d3af67e](https://github.com/vuejs/core/commit/d3af67e878790892f9d34cfea15d13625aabe733))
|
||||
* **compiler-sfc:** transform empty srcset w/ includeAbsolute: true ([#13639](https://github.com/vuejs/core/issues/13639)) ([d8e40ef](https://github.com/vuejs/core/commit/d8e40ef7e1c20ee86b294e7cf78e2de60d12830e)), closes [vitejs/vite-plugin-vue#631](https://github.com/vitejs/vite-plugin-vue/issues/631)
|
||||
* **css-vars:** nullish v-bind in style should not lead to unexpected inheritance ([#12461](https://github.com/vuejs/core/issues/12461)) ([c85f1b5](https://github.com/vuejs/core/commit/c85f1b5a132eb8ec25f71b250e25e65a5c20964f)), closes [#12434](https://github.com/vuejs/core/issues/12434) [#12439](https://github.com/vuejs/core/issues/12439) [#7474](https://github.com/vuejs/core/issues/7474) [#7475](https://github.com/vuejs/core/issues/7475)
|
||||
* **custom-element:** ensure exposed methods are accessible from custom elements by making them enumerable ([#13634](https://github.com/vuejs/core/issues/13634)) ([90573b0](https://github.com/vuejs/core/commit/90573b06bf6fb6c14c6bbff6c4e34e0ab108953a)), closes [#13632](https://github.com/vuejs/core/issues/13632)
|
||||
* **hydration:** prevent lazy hydration for updated components ([#13511](https://github.com/vuejs/core/issues/13511)) ([a9269c6](https://github.com/vuejs/core/commit/a9269c642bf944560bc29adb5dae471c11cd9ee8)), closes [#13510](https://github.com/vuejs/core/issues/13510)
|
||||
* **runtime-core:** ensure correct anchor el for unresolved async components ([#13560](https://github.com/vuejs/core/issues/13560)) ([7f29943](https://github.com/vuejs/core/commit/7f2994393dcdb82cacbf62e02b5ba5565f32588b)), closes [#13559](https://github.com/vuejs/core/issues/13559)
|
||||
* **slots:** refine internal key checking to support slot names starting with an underscore ([#13612](https://github.com/vuejs/core/issues/13612)) ([c5f7db1](https://github.com/vuejs/core/commit/c5f7db11542bb2246363aef78c88a8e6cef0ee93)), closes [#13611](https://github.com/vuejs/core/issues/13611)
|
||||
* **ssr:** ensure empty slots render as a comment node in Transition ([#13396](https://github.com/vuejs/core/issues/13396)) ([8cfc10a](https://github.com/vuejs/core/commit/8cfc10a80b9cbf5d801ab149e49b8506d192e7e1)), closes [#13394](https://github.com/vuejs/core/issues/13394)
|
||||
|
||||
|
||||
|
||||
## [3.5.17](https://github.com/vuejs/core/compare/v3.5.16...v3.5.17) (2025-06-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compat:** allow v-model built in modifiers on component ([#12654](https://github.com/vuejs/core/issues/12654)) ([cb14b86](https://github.com/vuejs/core/commit/cb14b860f150c4a83bcd52cd26096b7a5aa3a2bf)), closes [#12652](https://github.com/vuejs/core/issues/12652)
|
||||
* **compile-sfc:** handle mapped types work with omit and pick ([#12648](https://github.com/vuejs/core/issues/12648)) ([4eb46e4](https://github.com/vuejs/core/commit/4eb46e443f1878199755cb73d481d318a9714392)), closes [#12647](https://github.com/vuejs/core/issues/12647)
|
||||
* **compiler-core:** do not increase newlines in `InEntity` state ([#13362](https://github.com/vuejs/core/issues/13362)) ([f05a8d6](https://github.com/vuejs/core/commit/f05a8d613bd873b811cfdb9979ccac8382dba322))
|
||||
* **compiler-core:** ignore whitespace when matching adjacent v-if ([#12321](https://github.com/vuejs/core/issues/12321)) ([10ebcef](https://github.com/vuejs/core/commit/10ebcef8c870dbc042b0ea49b1424b2e8f692145)), closes [#9173](https://github.com/vuejs/core/issues/9173)
|
||||
* **compiler-core:** prevent comments from blocking static node hoisting ([#13345](https://github.com/vuejs/core/issues/13345)) ([55dad62](https://github.com/vuejs/core/commit/55dad625acd9e9ddd5a933d5e323ecfdec1a612f)), closes [#13344](https://github.com/vuejs/core/issues/13344)
|
||||
* **compiler-sfc:** improved type resolution for function type aliases ([#13452](https://github.com/vuejs/core/issues/13452)) ([f3479aa](https://github.com/vuejs/core/commit/f3479aac9625f4459e650d1c0a70e73863147903)), closes [#13444](https://github.com/vuejs/core/issues/13444)
|
||||
* **custom-element:** ensure configureApp is applied to async component ([#12607](https://github.com/vuejs/core/issues/12607)) ([5ba1afb](https://github.com/vuejs/core/commit/5ba1afba09c3ea56c1c17484f5d8aeae210ce52a)), closes [#12448](https://github.com/vuejs/core/issues/12448)
|
||||
* **custom-element:** prevent injecting child styles if shadowRoot is false ([#12769](https://github.com/vuejs/core/issues/12769)) ([73055d8](https://github.com/vuejs/core/commit/73055d8d9578d485e3fe846726b50666e1aa56f5)), closes [#12630](https://github.com/vuejs/core/issues/12630)
|
||||
* **reactivity:** add `__v_skip` flag to `Dep` to prevent reactive conversion ([#12804](https://github.com/vuejs/core/issues/12804)) ([e8d8f5f](https://github.com/vuejs/core/commit/e8d8f5f604e821acc46b4200d5b06979c05af1c2)), closes [#12803](https://github.com/vuejs/core/issues/12803)
|
||||
* **runtime-core:** unset old ref during patching when new ref is absent ([#12900](https://github.com/vuejs/core/issues/12900)) ([47ddf98](https://github.com/vuejs/core/commit/47ddf986021dff8de68b0da72787e53a6c19de4c)), closes [#12898](https://github.com/vuejs/core/issues/12898)
|
||||
* **slots:** make cache indexes marker non-enumerable ([#13469](https://github.com/vuejs/core/issues/13469)) ([919c447](https://github.com/vuejs/core/commit/919c44744bba1f0c661c87d2059c3b429611aa7e)), closes [#13468](https://github.com/vuejs/core/issues/13468)
|
||||
* **ssr:** handle initial selected state for select with v-model + v-for/v-if option ([#13487](https://github.com/vuejs/core/issues/13487)) ([1552095](https://github.com/vuejs/core/commit/15520954f9f1c7f834175938a50dba5d4be0e6c4)), closes [#13486](https://github.com/vuejs/core/issues/13486)
|
||||
* **types:** typo of `vOnce` and `vSlot` ([#13343](https://github.com/vuejs/core/issues/13343)) ([762fae4](https://github.com/vuejs/core/commit/762fae4b57ad60602e5c84465a3bff562785b314))
|
||||
|
||||
|
||||
|
||||
## [3.5.16](https://github.com/vuejs/core/compare/v3.5.15...v3.5.16) (2025-05-29)
|
||||
|
||||
|
||||
|
@ -232,7 +329,7 @@
|
|||
|
||||
* **compiler-core:** fix handling of delimiterOpen in VPre ([#11915](https://github.com/vuejs/core/issues/11915)) ([706d4ac](https://github.com/vuejs/core/commit/706d4ac1d0210b2d9134b3228280187fe02fc971)), closes [#11913](https://github.com/vuejs/core/issues/11913)
|
||||
* **compiler-dom:** fix stringify static edge for partially eligible chunks in cached parent ([1d99d61](https://github.com/vuejs/core/commit/1d99d61c1bd77f9ea6743f6214a82add8346a121)), closes [#11879](https://github.com/vuejs/core/issues/11879) [#11890](https://github.com/vuejs/core/issues/11890)
|
||||
* **compiler-dom:** should ignore leading newline in <textarea> per spec ([3c4bf76](https://github.com/vuejs/core/commit/3c4bf7627649ec1e3220f8c4e4163c20d2afb367))
|
||||
* **compiler-dom:** should ignore leading newline in `<textarea>` per spec ([3c4bf76](https://github.com/vuejs/core/commit/3c4bf7627649ec1e3220f8c4e4163c20d2afb367))
|
||||
* **compiler-sfc:** nested css supports atrule and comment ([#11899](https://github.com/vuejs/core/issues/11899)) ([0e7bc71](https://github.com/vuejs/core/commit/0e7bc717e6640644f062957ec5031506f0dab215)), closes [#11896](https://github.com/vuejs/core/issues/11896)
|
||||
* **custom-element:** handle nested customElement mount w/ shadowRoot false ([#11861](https://github.com/vuejs/core/issues/11861)) ([f2d8019](https://github.com/vuejs/core/commit/f2d801918841e7673ff3f048d0d895592a2f7e23)), closes [#11851](https://github.com/vuejs/core/issues/11851) [#11871](https://github.com/vuejs/core/issues/11871)
|
||||
* **hmr:** reload async child wrapped in Suspense + KeepAlive ([#11907](https://github.com/vuejs/core/issues/11907)) ([10a2c60](https://github.com/vuejs/core/commit/10a2c6053bd30d160d0214bb3566f540187e6874)), closes [#11868](https://github.com/vuejs/core/issues/11868)
|
||||
|
|
32
package.json
32
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.16",
|
||||
"packageManager": "pnpm@10.11.1",
|
||||
"version": "3.5.21",
|
||||
"packageManager": "pnpm@10.15.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
@ -17,11 +17,11 @@
|
|||
"format": "prettier --write --cache .",
|
||||
"format-check": "prettier --check --cache .",
|
||||
"test": "vitest",
|
||||
"test-unit": "vitest --project unit",
|
||||
"test-unit": "vitest --project unit*",
|
||||
"test-e2e": "node scripts/build.js vue -f global -d && vitest --project e2e",
|
||||
"test-dts": "run-s build-dts test-dts-only",
|
||||
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
||||
"test-coverage": "vitest run --project unit --coverage",
|
||||
"test-coverage": "vitest run --project unit* --coverage",
|
||||
"prebench": "node scripts/build.js -pf esm-browser reactivity",
|
||||
"prebench-compare": "node scripts/build.js -pf esm-browser reactivity",
|
||||
"bench": "vitest bench --project=unit --outputJson=temp/bench.json",
|
||||
|
@ -65,21 +65,21 @@
|
|||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.3",
|
||||
"@rollup/plugin-commonjs": "^28.0.6",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.11.29",
|
||||
"@swc/core": "^1.13.5",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^22.15.29",
|
||||
"@types/node": "^22.17.2",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitest/coverage-v8": "^3.1.4",
|
||||
"@vitest/eslint-plugin": "^1.2.1",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/eslint-plugin": "^1.3.5",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"esbuild": "^0.25.9",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-import-x": "^4.13.1",
|
||||
|
@ -87,18 +87,18 @@
|
|||
"jsdom": "^26.1.0",
|
||||
"lint-staged": "^16.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.17",
|
||||
"magic-string": "^0.30.18",
|
||||
"markdown-table": "^3.0.4",
|
||||
"marked": "13.0.3",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"picocolors": "^1.1.1",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~24.9.0",
|
||||
"puppeteer": "~24.17.1",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.41.1",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"rollup": "^4.50.0",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.7.2",
|
||||
|
@ -110,6 +110,6 @@
|
|||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^3.1.4"
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { nextTick } from 'vue'
|
||||
import { describe, expectType } from './utils'
|
||||
|
||||
describe('nextTick', async () => {
|
||||
expectType<Promise<void>>(nextTick())
|
||||
expectType<Promise<string>>(nextTick(() => 'foo'))
|
||||
expectType<Promise<string>>(nextTick(() => Promise.resolve('foo')))
|
||||
expectType<Promise<string>>(
|
||||
nextTick(() => Promise.resolve(Promise.resolve('foo'))),
|
||||
)
|
||||
|
||||
expectType<void>(await nextTick())
|
||||
expectType<string>(await nextTick(() => 'foo'))
|
||||
expectType<string>(await nextTick(() => Promise.resolve('foo')))
|
||||
expectType<string>(
|
||||
await nextTick(() => Promise.resolve(Promise.resolve('foo'))),
|
||||
)
|
||||
|
||||
nextTick().then(value => {
|
||||
expectType<void>(value)
|
||||
})
|
||||
nextTick(() => 'foo').then(value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
nextTick(() => Promise.resolve('foo')).then(value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
nextTick(() => Promise.resolve(Promise.resolve('foo'))).then(value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
})
|
|
@ -13,7 +13,7 @@
|
|||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.5.1",
|
||||
"@vue/repl": "^4.7.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import Header from './Header.vue'
|
||||
import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
|
||||
import Monaco from '@vue/repl/monaco-editor'
|
||||
import { ref, watchEffect, onMounted, computed } from 'vue'
|
||||
import { ref, watchEffect, onMounted, computed, watch } from 'vue'
|
||||
|
||||
const replRef = ref<InstanceType<typeof Repl>>()
|
||||
|
||||
|
@ -115,6 +115,34 @@ onMounted(() => {
|
|||
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
||||
window.process = { env: {} }
|
||||
})
|
||||
|
||||
const isVaporSupported = ref(false)
|
||||
watch(
|
||||
() => store.vueVersion,
|
||||
(version, oldVersion) => {
|
||||
const [major, minor] = (version || store.compiler.version)
|
||||
.split('.')
|
||||
.map((v: string) => parseInt(v, 10))
|
||||
isVaporSupported.value = major > 3 || (major === 3 && minor >= 6)
|
||||
if (oldVersion) reloadPage()
|
||||
},
|
||||
{ immediate: true, flush: 'pre' },
|
||||
)
|
||||
|
||||
const previewOptions = computed(() => ({
|
||||
customCode: {
|
||||
importCode: `import { initCustomFormatter${isVaporSupported.value ? ', vaporInteropPlugin' : ''} } from 'vue'`,
|
||||
useCode: `
|
||||
${isVaporSupported.value ? 'app.use(vaporInteropPlugin)' : ''}
|
||||
if (window.devtoolsFormatters) {
|
||||
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
||||
window.devtoolsFormatters.splice(index, 1)
|
||||
initCustomFormatter()
|
||||
} else {
|
||||
initCustomFormatter()
|
||||
}`,
|
||||
},
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -141,20 +169,11 @@ onMounted(() => {
|
|||
:editorOptions="{ autoSaveText: false }"
|
||||
:store="store"
|
||||
:showCompileOutput="true"
|
||||
:showSsrOutput="useSSRMode"
|
||||
:showOpenSourceMap="true"
|
||||
:autoResize="true"
|
||||
:clearConsole="false"
|
||||
:preview-options="{
|
||||
customCode: {
|
||||
importCode: `import { initCustomFormatter } from 'vue'`,
|
||||
useCode: `if (window.devtoolsFormatters) {
|
||||
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
||||
window.devtoolsFormatters.splice(index, 1)
|
||||
initCustomFormatter()
|
||||
} else {
|
||||
initCustomFormatter()
|
||||
}`,
|
||||
},
|
||||
}"
|
||||
:preview-options="previewOptions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"vue": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"vite": "^6.3.5"
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"vite": "^7.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"enableNonBrowserBranches": true
|
||||
},
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.52.2",
|
||||
"monaco-editor": "^0.53.0",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,9 +7,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -21,7 +21,7 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
|
@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -42,11 +42,11 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createCommentVNode("comment")
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -58,11 +58,11 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||
_createTextVNode("foo"),
|
||||
_createTextVNode("foo", -1 /* CACHED */),
|
||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -74,9 +74,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -147,9 +147,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -161,9 +161,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
@ -215,9 +215,9 @@ return function render(_ctx, _cache) {
|
|||
const _directive_foo = _resolveDirective("foo")
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||
]))), [
|
||||
]))])), [
|
||||
[_directive_foo]
|
||||
])
|
||||
]))
|
||||
|
@ -401,9 +401,9 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
ok
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
: _createCommentVNode("v-if", true)
|
||||
]))
|
||||
}
|
||||
|
@ -422,7 +422,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_createCommentVNode("comment"),
|
||||
_createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", { id: "b" }, [
|
||||
_createElementVNode("div", { id: "c" }, [
|
||||
_createElementVNode("div", { id: "d" }, [
|
||||
|
@ -430,7 +430,7 @@ return function render(_ctx, _cache) {
|
|||
])
|
||||
])
|
||||
], -1 /* CACHED */)
|
||||
]))
|
||||
]))])
|
||||
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
|
@ -448,9 +448,9 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ import { PatchFlags } from '@vue/shared'
|
|||
|
||||
const cachedChildrenArrayMatcher = (
|
||||
tags: string[],
|
||||
needArraySpread = false,
|
||||
needArraySpread = true,
|
||||
) => ({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
needArraySpread,
|
||||
|
@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
|
|||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
key: { content: '__' },
|
||||
value: { content: '[0]' },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
|
|||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
key: { content: '__' },
|
||||
value: { content: '[0]' },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
|
|
@ -301,6 +301,25 @@ describe('compiler: v-if', () => {
|
|||
])
|
||||
})
|
||||
|
||||
test('error on adjacent v-else', () => {
|
||||
const onError = vi.fn()
|
||||
|
||||
const {
|
||||
node: { branches },
|
||||
} = parseWithIfTransform(
|
||||
`<div v-if="false"/><div v-else/><div v-else/>`,
|
||||
{ onError },
|
||||
0,
|
||||
)
|
||||
|
||||
expect(onError.mock.calls[0]).toMatchObject([
|
||||
{
|
||||
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||
loc: branches[branches.length - 1].loc,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('error on user key', () => {
|
||||
const onError = vi.fn()
|
||||
// dynamic
|
||||
|
|
|
@ -478,7 +478,10 @@ describe('compiler: transform component slots', () => {
|
|||
})
|
||||
|
||||
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
|
||||
function assertDynamicSlots(template: string, shouldForce: boolean) {
|
||||
function assertDynamicSlots(
|
||||
template: string,
|
||||
expectedPatchFlag?: PatchFlags,
|
||||
) {
|
||||
const { root } = parseWithSlots(template, { prefixIdentifiers: true })
|
||||
let flag: any
|
||||
if (root.children[0].type === NodeTypes.FOR) {
|
||||
|
@ -491,8 +494,8 @@ describe('compiler: transform component slots', () => {
|
|||
.children[0] as ComponentNode
|
||||
flag = (innerComp.codegenNode as VNodeCall).patchFlag
|
||||
}
|
||||
if (shouldForce) {
|
||||
expect(flag).toBe(PatchFlags.DYNAMIC_SLOTS)
|
||||
if (expectedPatchFlag) {
|
||||
expect(flag).toBe(expectedPatchFlag)
|
||||
} else {
|
||||
expect(flag).toBeUndefined()
|
||||
}
|
||||
|
@ -502,14 +505,13 @@ describe('compiler: transform component slots', () => {
|
|||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar">foo</Comp>
|
||||
</div>`,
|
||||
false,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar">{{ i }}</Comp>
|
||||
</div>`,
|
||||
true,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
// reference the component's own slot variable should not force dynamic slots
|
||||
|
@ -517,14 +519,13 @@ describe('compiler: transform component slots', () => {
|
|||
`<Comp v-slot="foo">
|
||||
<Comp v-slot="bar">{{ bar }}</Comp>
|
||||
</Comp>`,
|
||||
false,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<Comp v-slot="foo">
|
||||
<Comp v-slot="bar">{{ foo }}</Comp>
|
||||
</Comp>`,
|
||||
true,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
// #2564
|
||||
|
@ -532,14 +533,35 @@ describe('compiler: transform component slots', () => {
|
|||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar"><button @click="fn(i)" /></Comp>
|
||||
</div>`,
|
||||
true,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar"><button @click="fn()" /></Comp>
|
||||
</div>`,
|
||||
false,
|
||||
)
|
||||
|
||||
// #9380
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp :i="i">foo</Comp>
|
||||
</div>`,
|
||||
PatchFlags.PROPS,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot="{ value = i }"><button @click="fn()" /></Comp>
|
||||
</div>`,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot:[i]><button @click="fn()" /></Comp>
|
||||
</div>`,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
import type { ExpressionNode, TransformContext } from '../src'
|
||||
import { babelParse, walkIdentifiers } from '@vue/compiler-sfc'
|
||||
import {
|
||||
type ExpressionNode,
|
||||
type TransformContext,
|
||||
isReferencedIdentifier,
|
||||
} from '../src'
|
||||
import { type Position, createSimpleExpression } from '../src/ast'
|
||||
import {
|
||||
advancePositionWithClone,
|
||||
|
@ -115,3 +120,18 @@ test('toValidAssetId', () => {
|
|||
'_component_test_2797935797_1',
|
||||
)
|
||||
})
|
||||
|
||||
describe('isReferencedIdentifier', () => {
|
||||
test('identifiers in function parameters should not be inferred as references', () => {
|
||||
expect.assertions(4)
|
||||
const ast = babelParse(`(({ title }) => [])`)
|
||||
walkIdentifiers(
|
||||
ast.program.body[0],
|
||||
(node, parent, parentStack, isReference) => {
|
||||
expect(isReference).toBe(false)
|
||||
expect(isReferencedIdentifier(node, parent, parentStack)).toBe(false)
|
||||
},
|
||||
true,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.16",
|
||||
"version": "3.5.21",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
@ -122,7 +122,7 @@ export function isReferencedIdentifier(
|
|||
return false
|
||||
}
|
||||
|
||||
if (isReferenced(id, parent)) {
|
||||
if (isReferenced(id, parent, parentStack[parentStack.length - 2])) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -132,7 +132,8 @@ export function isReferencedIdentifier(
|
|||
case 'AssignmentExpression':
|
||||
case 'AssignmentPattern':
|
||||
return true
|
||||
case 'ObjectPattern':
|
||||
case 'ObjectProperty':
|
||||
return parent.key !== id && isInDestructureAssignment(parent, parentStack)
|
||||
case 'ArrayPattern':
|
||||
return isInDestructureAssignment(parent, parentStack)
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ import {
|
|||
isCoreComponent,
|
||||
isSimpleIdentifier,
|
||||
isStaticArgOf,
|
||||
isVPre,
|
||||
} from './utils'
|
||||
import { decodeHTML } from 'entities/lib/decode.js'
|
||||
import {
|
||||
|
@ -246,7 +247,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
ondirarg(start, end) {
|
||||
if (start === end) return
|
||||
const arg = getSlice(start, end)
|
||||
if (inVPre) {
|
||||
if (inVPre && !isVPre(currentProp!)) {
|
||||
;(currentProp as AttributeNode).name += arg
|
||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||
} else {
|
||||
|
@ -262,7 +263,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
|
||||
ondirmodifier(start, end) {
|
||||
const mod = getSlice(start, end)
|
||||
if (inVPre) {
|
||||
if (inVPre && !isVPre(currentProp!)) {
|
||||
;(currentProp as AttributeNode).name += '.' + mod
|
||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
||||
|
|
|
@ -12,19 +12,22 @@ import {
|
|||
type RootNode,
|
||||
type SimpleExpressionNode,
|
||||
type SlotFunctionExpression,
|
||||
type SlotsObjectProperty,
|
||||
type TemplateChildNode,
|
||||
type TemplateNode,
|
||||
type TextCallNode,
|
||||
type VNodeCall,
|
||||
createArrayExpression,
|
||||
createObjectProperty,
|
||||
createSimpleExpression,
|
||||
getVNodeBlockHelper,
|
||||
getVNodeHelper,
|
||||
} from '../ast'
|
||||
import type { TransformContext } from '../transform'
|
||||
import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
|
||||
import {
|
||||
PatchFlagNames,
|
||||
PatchFlags,
|
||||
isArray,
|
||||
isString,
|
||||
isSymbol,
|
||||
} from '@vue/shared'
|
||||
import { findDir, isSlotOutlet } from '../utils'
|
||||
import {
|
||||
GUARD_REACTIVE_PROPS,
|
||||
|
@ -109,6 +112,15 @@ function walk(
|
|||
? ConstantTypes.NOT_CONSTANT
|
||||
: getConstantType(child, context)
|
||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
||||
if (
|
||||
child.codegenNode.type === NodeTypes.JS_CALL_EXPRESSION &&
|
||||
child.codegenNode.arguments.length > 0
|
||||
) {
|
||||
child.codegenNode.arguments.push(
|
||||
PatchFlags.CACHED +
|
||||
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.CACHED]} */` : ``),
|
||||
)
|
||||
}
|
||||
toCache.push(child)
|
||||
continue
|
||||
}
|
||||
|
@ -142,7 +154,6 @@ function walk(
|
|||
}
|
||||
|
||||
let cachedAsArray = false
|
||||
const slotCacheKeys = []
|
||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||
if (
|
||||
node.tagType === ElementTypes.ELEMENT &&
|
||||
|
@ -166,7 +177,6 @@ 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[]),
|
||||
)
|
||||
|
@ -190,7 +200,6 @@ function walk(
|
|||
slotName.arg &&
|
||||
getSlotNode(parent.codegenNode, slotName.arg)
|
||||
if (slot) {
|
||||
slotCacheKeys.push(context.cached.length)
|
||||
slot.returns = getCacheExpression(
|
||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||
)
|
||||
|
@ -201,39 +210,22 @@ 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
|
||||
// a cached children array inside v-for can caused HMR errors since
|
||||
// it might be mutated when mounting the first item
|
||||
if (inFor && context.hmr) {
|
||||
// #13221
|
||||
// fix memory leak in cached array:
|
||||
// cached vnodes get replaced by cloned ones during mountChildren,
|
||||
// which bind DOM elements. These DOM references persist after unmount,
|
||||
// preventing garbage collection. Array spread avoids mutating cached
|
||||
// array, preventing memory leaks.
|
||||
exp.needArraySpread = true
|
||||
}
|
||||
return exp
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
arg.children.unshift(`(`)
|
||||
arg.children.push(`) || ""`)
|
||||
} else if (!arg.isStatic) {
|
||||
arg.content = `${arg.content} || ""`
|
||||
arg.content = arg.content ? `${arg.content} || ""` : `""`
|
||||
}
|
||||
|
||||
// .sync is replaced by v-model:arg
|
||||
|
|
|
@ -36,7 +36,7 @@ import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
|||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
export const transformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
/^(?:if|else|else-if)$/,
|
||||
(node, dir, context) => {
|
||||
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
|
||||
// #1587: We need to dynamically increment the key based on the current
|
||||
|
@ -141,9 +141,9 @@ export function processIf(
|
|||
}
|
||||
|
||||
if (sibling && sibling.type === NodeTypes.IF) {
|
||||
// Check if v-else was followed by v-else-if
|
||||
// Check if v-else was followed by v-else-if or there are two adjacent v-else
|
||||
if (
|
||||
dir.name === 'else-if' &&
|
||||
(dir.name === 'else-if' || dir.name === 'else') &&
|
||||
sibling.branches[sibling.branches.length - 1].condition === undefined
|
||||
) {
|
||||
context.onError(
|
||||
|
|
|
@ -16,7 +16,7 @@ const seen = new WeakSet()
|
|||
export const transformMemo: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
const dir = findDir(node, 'memo')
|
||||
if (!dir || seen.has(node)) {
|
||||
if (!dir || seen.has(node) || context.inSSR) {
|
||||
return
|
||||
}
|
||||
seen.add(node)
|
||||
|
|
|
@ -131,9 +131,17 @@ export function buildSlots(
|
|||
// since it likely uses a scope variable.
|
||||
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
|
||||
// with `prefixIdentifiers: true`, this can be further optimized to make
|
||||
// it dynamic only when the slot actually uses the scope variables.
|
||||
// it dynamic when
|
||||
// 1. the slot arg or exp uses the scope variables.
|
||||
// 2. the slot children use the scope variables.
|
||||
if (!__BROWSER__ && !context.ssr && context.prefixIdentifiers) {
|
||||
hasDynamicSlots = hasScopeRef(node, context.identifiers)
|
||||
hasDynamicSlots =
|
||||
node.props.some(
|
||||
prop =>
|
||||
isVSlot(prop) &&
|
||||
(hasScopeRef(prop.arg, context.identifiers) ||
|
||||
hasScopeRef(prop.exp, context.identifiers)),
|
||||
) || children.some(child => hasScopeRef(child, context.identifiers))
|
||||
}
|
||||
|
||||
// 1. Check for slot with slotProps on component itself.
|
||||
|
@ -215,7 +223,7 @@ export function buildSlots(
|
|||
),
|
||||
)
|
||||
} else if (
|
||||
(vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
|
||||
(vElse = findDir(slotElement, /^else(?:-if)?$/, true /* allowEmpty */))
|
||||
) {
|
||||
// find adjacent v-if
|
||||
let j = i
|
||||
|
@ -226,7 +234,7 @@ export function buildSlots(
|
|||
break
|
||||
}
|
||||
}
|
||||
if (prev && isTemplateNode(prev) && findDir(prev, /^(else-)?if$/)) {
|
||||
if (prev && isTemplateNode(prev) && findDir(prev, /^(?:else-)?if$/)) {
|
||||
__TEST__ && assert(dynamicSlots.length > 0)
|
||||
// attach this slot to previous conditional
|
||||
let conditional = dynamicSlots[
|
||||
|
|
|
@ -63,7 +63,7 @@ export function isCoreComponent(tag: string): symbol | void {
|
|||
}
|
||||
}
|
||||
|
||||
const nonIdentifierRE = /^\d|[^\$\w\xA0-\uFFFF]/
|
||||
const nonIdentifierRE = /^$|^\d|[^\$\w\xA0-\uFFFF]/
|
||||
export const isSimpleIdentifier = (name: string): boolean =>
|
||||
!nonIdentifierRE.test(name)
|
||||
|
||||
|
@ -189,7 +189,7 @@ export const isMemberExpression: (
|
|||
) => boolean = __BROWSER__ ? isMemberExpressionBrowser : isMemberExpressionNode
|
||||
|
||||
const fnExpRE =
|
||||
/^\s*(async\s*)?(\([^)]*?\)|[\w$_]+)\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||
/^\s*(?:async\s*)?(?:\([^)]*?\)|[\w$_]+)\s*(?::[^=]+)?=>|^\s*(?:async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||
|
||||
export const isFnExpressionBrowser: (exp: ExpressionNode) => boolean = exp =>
|
||||
fnExpRE.test(getExpSource(exp))
|
||||
|
@ -343,6 +343,10 @@ export function isText(
|
|||
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
||||
}
|
||||
|
||||
export function isVPre(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'pre'
|
||||
}
|
||||
|
||||
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||
}
|
||||
|
|
|
@ -4,11 +4,11 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
||||
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -16,9 +16,9 @@ exports[`stringify static html > escape 1`] = `
|
|||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -26,9 +26,9 @@ exports[`stringify static html > serializing constant bindings 1`] = `
|
|||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -36,9 +36,9 @@ exports[`stringify static html > serializing template string style 1`] = `
|
|||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -46,7 +46,7 @@ exports[`stringify static html > should bail for <option> elements with null val
|
|||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("select", null, [
|
||||
_createElementVNode("option", { value: null }),
|
||||
_createElementVNode("option", { value: "1" }),
|
||||
|
@ -55,7 +55,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("option", { value: "1" }),
|
||||
_createElementVNode("option", { value: "1" })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -63,7 +63,7 @@ exports[`stringify static html > should bail for <option> elements with number v
|
|||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("select", null, [
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
|
@ -71,7 +71,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -95,7 +95,7 @@ exports[`stringify static html > should bail on bindings that are cached but not
|
|||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
|
@ -104,7 +104,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("img", { src: _imports_0_ })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -112,9 +112,9 @@ exports[`stringify static html > should work for <option> elements with string v
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -122,9 +122,9 @@ exports[`stringify static html > should work for multiple adjacent nodes 1`] = `
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -132,9 +132,9 @@ exports[`stringify static html > should work on eligible content (elements > 20)
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -142,9 +142,9 @@ exports[`stringify static html > should work on eligible content (elements with
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
@ -152,9 +152,9 @@ exports[`stringify static html > should work with bindings that are non-static b
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.16",
|
||||
"version": "3.5.21",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -42,7 +42,7 @@ if (__TEST__) {
|
|||
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
||||
throw new Error(
|
||||
`DOMErrorCodes need to be updated to ${
|
||||
ErrorCodes.__EXTEND_POINT__ + 1
|
||||
ErrorCodes.__EXTEND_POINT__
|
||||
} to match extension point from core ErrorCodes.`,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -184,7 +184,7 @@ const getCachedNode = (
|
|||
}
|
||||
}
|
||||
|
||||
const dataAriaRE = /^(data|aria)-/
|
||||
const dataAriaRE = /^(?:data|aria)-/
|
||||
const isStringifiableAttr = (name: string, ns: Namespaces) => {
|
||||
return (
|
||||
(ns === Namespaces.HTML
|
||||
|
|
|
@ -884,9 +884,9 @@ export default {
|
|||
|
||||
return (_ctx, _push, _parent, _attrs) => {
|
||||
const _cssVars = { style: {
|
||||
"--xxxxxxxx-count": (count.value),
|
||||
"--xxxxxxxx-style\\\\.color": (style.color),
|
||||
"--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
||||
":--xxxxxxxx-count": (count.value),
|
||||
":--xxxxxxxx-style\\\\.color": (style.color),
|
||||
":--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
||||
}}
|
||||
_push(\`<!--[--><div\${
|
||||
_ssrRenderAttrs(_cssVars)
|
||||
|
|
|
@ -81,9 +81,9 @@ import _imports_1 from '/bar.png'
|
|||
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
|
@ -33,6 +33,16 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform srcset > transform empty srcset w/ includeAbsolute: true 1`] = `
|
||||
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
const _hoisted_1 = { srcset: " " }
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("img", _hoisted_1))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform srcset > transform srcset 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
import _imports_0 from './logo.png'
|
||||
|
@ -245,8 +255,8 @@ const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
|
|||
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -652,10 +652,10 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(content).toMatch(`return (_ctx, _push`)
|
||||
expect(content).toMatch(`ssrInterpolate`)
|
||||
expect(content).not.toMatch(`useCssVars`)
|
||||
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
|
||||
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
|
||||
expect(content).toMatch(`":--${mockId}-count": (count.value)`)
|
||||
expect(content).toMatch(`":--${mockId}-style\\\\.color": (style.color)`)
|
||||
expect(content).toMatch(
|
||||
`"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
||||
`":--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
@ -913,6 +913,13 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(() =>
|
||||
compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
|
||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||
|
||||
// #13193 must check lang before parsing with babel
|
||||
expect(() =>
|
||||
compile(
|
||||
`<script lang="ts">const a = 1</script><script setup lang="tsx">const Comp = () => <p>test</p></script>`,
|
||||
),
|
||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||
})
|
||||
|
||||
const moduleErrorMsg = `cannot contain ES module exports`
|
||||
|
@ -1543,4 +1550,19 @@ describe('compileScript', () => {
|
|||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('should not compile unrecognized language', () => {
|
||||
const { content, lang, scriptAst } = compile(
|
||||
`<script lang="coffee">
|
||||
export default
|
||||
data: ->
|
||||
myVal: 0
|
||||
</script>`,
|
||||
)
|
||||
expect(content).toMatch(`export default
|
||||
data: ->
|
||||
myVal: 0`)
|
||||
expect(lang).toBe('coffee')
|
||||
expect(scriptAst).not.toBeDefined()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -148,27 +148,6 @@ 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 { }
|
||||
}
|
||||
|
||||
|
|
|
@ -808,30 +808,4 @@ 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 }`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -538,7 +538,7 @@ describe('resolveType', () => {
|
|||
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Symbol', 'String', 'Number'],
|
||||
bar: [UNKNOWN_TYPE],
|
||||
bar: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -731,6 +731,49 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
describe('type alias declaration', () => {
|
||||
// #13240
|
||||
test('function type', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type FunFoo<O> = (item: O) => boolean;
|
||||
type FunBar = FunFoo<number>;
|
||||
defineProps<{
|
||||
foo?: FunFoo<number>;
|
||||
bar?: FunBar;
|
||||
}>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Function'],
|
||||
bar: ['Function'],
|
||||
})
|
||||
})
|
||||
|
||||
test('with intersection type', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Brand<T> = T & {};
|
||||
defineProps<{
|
||||
foo: Brand<string>;
|
||||
}>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['String', 'Object'],
|
||||
})
|
||||
})
|
||||
|
||||
test('with union type', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Wrapped<T> = T | symbol | number
|
||||
defineProps<{foo?: Wrapped<boolean>}>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Boolean', 'Symbol', 'Number'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('generics', () => {
|
||||
test('generic with type literal', () => {
|
||||
expect(
|
||||
|
@ -1155,6 +1198,45 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
|
||||
})
|
||||
|
||||
// #13484
|
||||
test('ts module resolve w/ project reference & extends & ${configDir}', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: './tsconfig.app.json' }],
|
||||
}),
|
||||
'/tsconfig.app.json': JSON.stringify({
|
||||
extends: ['./tsconfigs/base.json'],
|
||||
}),
|
||||
'/tsconfigs/base.json': JSON.stringify({
|
||||
compilerOptions: {
|
||||
paths: {
|
||||
'@/*': ['${configDir}/src/*'],
|
||||
},
|
||||
},
|
||||
include: ['${configDir}/src/**/*.ts', '${configDir}/src/**/*.vue'],
|
||||
}),
|
||||
'/src/types.ts':
|
||||
'export type BaseProps = { foo?: string, bar?: string }',
|
||||
}
|
||||
|
||||
const { props, deps } = resolve(
|
||||
`
|
||||
import { BaseProps } from '@/types.ts';
|
||||
defineProps<BaseProps>()
|
||||
`,
|
||||
files,
|
||||
{},
|
||||
'/src/components/Foo.vue',
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['String'],
|
||||
bar: ['String'],
|
||||
})
|
||||
expect(deps && [...deps]).toStrictEqual(['/src/types.ts'])
|
||||
})
|
||||
|
||||
test('ts module resolve w/ project reference folder', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
|
@ -1299,6 +1381,33 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
||||
})
|
||||
|
||||
test('global types with named exports', () => {
|
||||
const files = {
|
||||
'/global.d.ts': `
|
||||
declare global {
|
||||
export interface ExportedInterface { foo: number }
|
||||
export type ExportedType = { bar: boolean }
|
||||
}
|
||||
export {}
|
||||
`,
|
||||
}
|
||||
|
||||
const globalTypeFiles = { globalTypeFiles: Object.keys(files) }
|
||||
|
||||
expect(
|
||||
resolve(`defineProps<ExportedInterface>()`, files, globalTypeFiles)
|
||||
.props,
|
||||
).toStrictEqual({
|
||||
foo: ['Number'],
|
||||
})
|
||||
|
||||
expect(
|
||||
resolve(`defineProps<ExportedType>()`, files, globalTypeFiles).props,
|
||||
).toStrictEqual({
|
||||
bar: ['Boolean'],
|
||||
})
|
||||
})
|
||||
|
||||
test('global types with ambient references', () => {
|
||||
const files = {
|
||||
// with references
|
||||
|
|
|
@ -72,6 +72,14 @@ describe('compiler sfc: transform srcset', () => {
|
|||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('transform empty srcset w/ includeAbsolute: true', () => {
|
||||
expect(
|
||||
compileWithSrcset(`<img srcset=" " />`, {
|
||||
includeAbsolute: true,
|
||||
}).code,
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('transform srcset w/ stringify', () => {
|
||||
const code = compileWithSrcset(
|
||||
`<div>${src}</div>`,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.16",
|
||||
"version": "3.5.21",
|
||||
"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.5.3",
|
||||
"postcss": "^8.5.6",
|
||||
"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": "~10.0.1",
|
||||
"minimatch": "~10.0.3",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-selector-parser": "^7.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.89.1"
|
||||
"sass": "^1.90.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import type {
|
|||
Declaration,
|
||||
ExportSpecifier,
|
||||
Identifier,
|
||||
LVal,
|
||||
Node,
|
||||
ObjectPattern,
|
||||
Statement,
|
||||
|
@ -55,7 +56,13 @@ import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
|
|||
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
||||
import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
||||
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
|
||||
import {
|
||||
getImportedName,
|
||||
isCallOf,
|
||||
isJS,
|
||||
isLiteralNode,
|
||||
isTS,
|
||||
} from './script/utils'
|
||||
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||
import { isImportUsed } from './script/importUsageCheck'
|
||||
import { processAwait } from './script/topLevelAwait'
|
||||
|
@ -167,33 +174,43 @@ export function compileScript(
|
|||
)
|
||||
}
|
||||
|
||||
const ctx = new ScriptCompileContext(sfc, options)
|
||||
const { script, scriptSetup, source, filename } = sfc
|
||||
const hoistStatic = options.hoistStatic !== false && !script
|
||||
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
||||
const scriptLang = script && script.lang
|
||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||
const isJSOrTS =
|
||||
isJS(scriptLang, scriptSetupLang) || isTS(scriptLang, scriptSetupLang)
|
||||
|
||||
if (!scriptSetup) {
|
||||
if (!script) {
|
||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||
}
|
||||
// normal <script> only
|
||||
return processNormalScript(ctx, scopeId)
|
||||
}
|
||||
|
||||
if (script && scriptLang !== scriptSetupLang) {
|
||||
if (script && scriptSetup && scriptLang !== scriptSetupLang) {
|
||||
throw new Error(
|
||||
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
|
||||
`language type.`,
|
||||
)
|
||||
}
|
||||
|
||||
if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
|
||||
if (!scriptSetup) {
|
||||
if (!script) {
|
||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||
}
|
||||
|
||||
// normal <script> only
|
||||
if (script.lang && !isJSOrTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return script
|
||||
}
|
||||
|
||||
const ctx = new ScriptCompileContext(sfc, options)
|
||||
return processNormalScript(ctx, scopeId)
|
||||
}
|
||||
|
||||
if (scriptSetupLang && !isJSOrTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return scriptSetup
|
||||
}
|
||||
|
||||
const ctx = new ScriptCompileContext(sfc, options)
|
||||
|
||||
// metadata that needs to be returned
|
||||
// const ctx.bindingMetadata: BindingMetadata = {}
|
||||
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
|
||||
|
@ -540,7 +557,7 @@ export function compileScript(
|
|||
}
|
||||
|
||||
// defineProps
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
|
||||
if (ctx.propsDestructureRestId) {
|
||||
setupBindings[ctx.propsDestructureRestId] =
|
||||
BindingTypes.SETUP_REACTIVE_CONST
|
||||
|
@ -548,10 +565,10 @@ export function compileScript(
|
|||
|
||||
// defineEmits
|
||||
const isDefineEmits =
|
||||
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
||||
!isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
|
||||
!isDefineEmits &&
|
||||
(processDefineSlots(ctx, init, decl.id) ||
|
||||
processDefineModel(ctx, init, decl.id))
|
||||
(processDefineSlots(ctx, init, decl.id as LVal) ||
|
||||
processDefineModel(ctx, init, decl.id as LVal))
|
||||
|
||||
if (
|
||||
isDefineProps &&
|
||||
|
|
|
@ -9,6 +9,7 @@ import type { BindingMetadata } from '../../../compiler-core/src'
|
|||
import MagicString from 'magic-string'
|
||||
import type { TypeScope } from './resolveType'
|
||||
import { warn } from '../warn'
|
||||
import { isJS, isTS } from './utils'
|
||||
|
||||
export class ScriptCompileContext {
|
||||
isJS: boolean
|
||||
|
@ -87,16 +88,8 @@ export class ScriptCompileContext {
|
|||
const scriptLang = script && script.lang
|
||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||
|
||||
this.isJS =
|
||||
scriptLang === 'js' ||
|
||||
scriptLang === 'jsx' ||
|
||||
scriptSetupLang === 'js' ||
|
||||
scriptSetupLang === 'jsx'
|
||||
this.isTS =
|
||||
scriptLang === 'ts' ||
|
||||
scriptLang === 'tsx' ||
|
||||
scriptSetupLang === 'ts' ||
|
||||
scriptSetupLang === 'tsx'
|
||||
this.isJS = isJS(scriptLang, scriptSetupLang)
|
||||
this.isTS = isTS(scriptLang, scriptSetupLang)
|
||||
|
||||
const customElement = options.customElement
|
||||
const filename = this.descriptor.filename
|
||||
|
|
|
@ -114,7 +114,7 @@ export function processDefineModel(
|
|||
return true
|
||||
}
|
||||
|
||||
export function genModelProps(ctx: ScriptCompileContext) {
|
||||
export function genModelProps(ctx: ScriptCompileContext): string | undefined {
|
||||
if (!ctx.hasDefineModelCall) return
|
||||
|
||||
const isProd = !!ctx.options.isProd
|
||||
|
|
|
@ -12,10 +12,6 @@ export function processNormalScript(
|
|||
scopeId: string,
|
||||
): SFCScriptBlock {
|
||||
const script = ctx.descriptor.script!
|
||||
if (script.lang && !ctx.isJS && !ctx.isTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return script
|
||||
}
|
||||
try {
|
||||
let content = script.content
|
||||
let map = script.map
|
||||
|
|
|
@ -1029,6 +1029,14 @@ function resolveWithTS(
|
|||
if (configs.length === 1) {
|
||||
matchedConfig = configs[0]
|
||||
} else {
|
||||
const [major, minor] = ts.versionMajorMinor.split('.').map(Number)
|
||||
const getPattern = (base: string, p: string) => {
|
||||
// ts 5.5+ supports ${configDir} in paths
|
||||
const supportsConfigDir = major > 5 || (major === 5 && minor >= 5)
|
||||
return p.startsWith('${configDir}') && supportsConfigDir
|
||||
? normalizePath(p.replace('${configDir}', dirname(configPath!)))
|
||||
: joinPaths(base, p)
|
||||
}
|
||||
// resolve which config matches the current file
|
||||
for (const c of configs) {
|
||||
const base = normalizePath(
|
||||
|
@ -1039,11 +1047,11 @@ function resolveWithTS(
|
|||
const excluded: string[] | undefined = c.config.raw?.exclude
|
||||
if (
|
||||
(!included && (!base || containingFile.startsWith(base))) ||
|
||||
included?.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
included?.some(p => isMatch(containingFile, getPattern(base, p)))
|
||||
) {
|
||||
if (
|
||||
excluded &&
|
||||
excluded.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
excluded.some(p => isMatch(containingFile, getPattern(base, p)))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
@ -1296,9 +1304,14 @@ function recordTypes(
|
|||
}
|
||||
} else if (stmt.type === 'TSModuleDeclaration' && stmt.global) {
|
||||
for (const s of (stmt.body as TSModuleBlock).body) {
|
||||
if (s.type === 'ExportNamedDeclaration' && s.declaration) {
|
||||
// Handle export declarations inside declare global
|
||||
recordType(s.declaration, types, declares)
|
||||
} else {
|
||||
recordType(s, types, declares)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
recordType(stmt, types, declares)
|
||||
}
|
||||
|
@ -1500,6 +1513,7 @@ export function inferRuntimeType(
|
|||
node: Node & MaybeWithScope,
|
||||
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
||||
isKeyOf = false,
|
||||
typeParameters?: Record<string, Node>,
|
||||
): string[] {
|
||||
try {
|
||||
switch (node.type) {
|
||||
|
@ -1589,17 +1603,42 @@ export function inferRuntimeType(
|
|||
const resolved = resolveTypeReference(ctx, node, scope)
|
||||
if (resolved) {
|
||||
if (resolved.type === 'TSTypeAliasDeclaration') {
|
||||
// #13240
|
||||
// Special case for function type aliases to ensure correct runtime behavior
|
||||
// other type aliases still fallback to unknown as before
|
||||
if (resolved.typeAnnotation.type === 'TSFunctionType') {
|
||||
return ['Function']
|
||||
}
|
||||
|
||||
if (node.typeParameters) {
|
||||
const typeParams: Record<string, Node> = Object.create(null)
|
||||
if (resolved.typeParameters) {
|
||||
resolved.typeParameters.params.forEach((p, i) => {
|
||||
typeParams![p.name] = node.typeParameters!.params[i]
|
||||
})
|
||||
}
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
resolved.typeAnnotation,
|
||||
resolved._ownerScope,
|
||||
isKeyOf,
|
||||
typeParams,
|
||||
)
|
||||
}
|
||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||
}
|
||||
|
||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||
}
|
||||
if (node.typeName.type === 'Identifier') {
|
||||
if (typeParameters && typeParameters[node.typeName.name]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
typeParameters[node.typeName.name],
|
||||
scope,
|
||||
isKeyOf,
|
||||
typeParameters,
|
||||
)
|
||||
}
|
||||
if (isKeyOf) {
|
||||
switch (node.typeName.name) {
|
||||
case 'String':
|
||||
|
@ -1732,11 +1771,15 @@ export function inferRuntimeType(
|
|||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||
|
||||
case 'TSUnionType':
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf)
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf, typeParameters)
|
||||
case 'TSIntersectionType': {
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
|
||||
t => t !== UNKNOWN_TYPE,
|
||||
)
|
||||
return flattenTypes(
|
||||
ctx,
|
||||
node.types,
|
||||
scope,
|
||||
isKeyOf,
|
||||
typeParameters,
|
||||
).filter(t => t !== UNKNOWN_TYPE)
|
||||
}
|
||||
|
||||
case 'TSEnumDeclaration':
|
||||
|
@ -1807,14 +1850,17 @@ function flattenTypes(
|
|||
types: TSType[],
|
||||
scope: TypeScope,
|
||||
isKeyOf: boolean = false,
|
||||
typeParameters: Record<string, Node> | undefined = undefined,
|
||||
): string[] {
|
||||
if (types.length === 1) {
|
||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
|
||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
|
||||
}
|
||||
return [
|
||||
...new Set(
|
||||
([] as string[]).concat(
|
||||
...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
|
||||
...types.map(t =>
|
||||
inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters),
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -121,3 +121,8 @@ export const propNameEscapeSymbolsRE: RegExp =
|
|||
export function getEscapedPropName(key: string): string {
|
||||
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
||||
}
|
||||
|
||||
export const isJS = (...langs: (string | null | undefined)[]): boolean =>
|
||||
langs.some(lang => lang === 'js' || lang === 'jsx')
|
||||
export const isTS = (...langs: (string | null | undefined)[]): boolean =>
|
||||
langs.some(lang => lang === 'ts' || lang === 'tsx')
|
||||
|
|
|
@ -23,7 +23,12 @@ export function genCssVarsFromList(
|
|||
return `{\n ${vars
|
||||
.map(
|
||||
key =>
|
||||
`"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
|
||||
// The `:` prefix here is used in `ssrRenderStyle` to distinguish whether
|
||||
// a custom property comes from `ssrCssVars`. If it does, we need to reset
|
||||
// its value to `initial` on the component instance to avoid unintentionally
|
||||
// inheriting the same property value from a different instance of the same
|
||||
// component in the outer scope.
|
||||
`"${isSSR ? `:--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
|
||||
)
|
||||
.join(',\n ')}\n}`
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ import {
|
|||
import selectorParser from 'postcss-selector-parser'
|
||||
import { warn } from '../warn'
|
||||
|
||||
const animationNameRE = /^(-\w+-)?animation-name$/
|
||||
const animationRE = /^(-\w+-)?animation$/
|
||||
const animationNameRE = /^(?:-\w+-)?animation-name$/
|
||||
const animationRE = /^(?:-\w+-)?animation$/
|
||||
const keyframesRE = /^(?:-\w+-)?keyframes$/
|
||||
|
||||
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||
const keyframes = Object.create(null)
|
||||
|
@ -21,10 +22,7 @@ const scopedPlugin: PluginCreator<string> = (id = '') => {
|
|||
processRule(id, rule)
|
||||
},
|
||||
AtRule(node) {
|
||||
if (
|
||||
/-?keyframes$/.test(node.name) &&
|
||||
!node.params.endsWith(`-${shortId}`)
|
||||
) {
|
||||
if (keyframesRE.test(node.name) && !node.params.endsWith(`-${shortId}`)) {
|
||||
// register keyframes
|
||||
keyframes[node.params] = node.params = node.params + '-' + shortId
|
||||
}
|
||||
|
@ -72,7 +70,7 @@ function processRule(id: string, rule: Rule) {
|
|||
processedRules.has(rule) ||
|
||||
(rule.parent &&
|
||||
rule.parent.type === 'atrule' &&
|
||||
/-?keyframes$/.test((rule.parent as AtRule).name))
|
||||
keyframesRE.test((rule.parent as AtRule).name))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ export function isRelativeUrl(url: string): boolean {
|
|||
return firstChar === '.' || firstChar === '~' || firstChar === '@'
|
||||
}
|
||||
|
||||
const externalRE = /^(https?:)?\/\//
|
||||
const externalRE = /^(?:https?:)?\/\//
|
||||
export function isExternalUrl(url: string): boolean {
|
||||
return externalRE.test(url)
|
||||
}
|
||||
|
|
|
@ -71,6 +71,7 @@ export const transformSrcset: NodeTransform = (
|
|||
|
||||
const shouldProcessUrl = (url: string) => {
|
||||
return (
|
||||
url &&
|
||||
!isExternalUrl(url) &&
|
||||
!isDataUrl(url) &&
|
||||
(options.includeAbsolute || isRelativeUrl(url))
|
||||
|
|
|
@ -317,6 +317,35 @@ describe('ssr: components', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
// #13724
|
||||
test('slot content with v-memo', () => {
|
||||
const { code } = compile(`<foo><bar v-memo="[]" /></foo>`)
|
||||
expect(code).not.toMatch(`_cache`)
|
||||
expect(compile(`<foo><bar v-memo="[]" /></foo>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require("vue")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
const _component_foo = _resolveComponent("foo")
|
||||
const _component_bar = _resolveComponent("bar")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(_ssrRenderComponent(_component_bar, null, null, _parent, _scopeId))
|
||||
} else {
|
||||
return [
|
||||
_createVNode(_component_bar)
|
||||
]
|
||||
}
|
||||
}),
|
||||
_: 1 /* STABLE */
|
||||
}, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
describe('built-in fallthroughs', () => {
|
||||
test('transition', () => {
|
||||
expect(compile(`<transition><div/></transition>`).code)
|
||||
|
|
|
@ -166,6 +166,132 @@ describe('ssr: v-model', () => {
|
|||
_push(\`</optgroup></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(`
|
||||
<select multiple v-model="model">
|
||||
<optgroup>
|
||||
<option v-for="item in items" :value="item">{{item}}</option>
|
||||
</optgroup>
|
||||
</select>`).code,
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
|
||||
_ssrRenderList(_ctx.items, (item) => {
|
||||
_push(\`<option\${
|
||||
_ssrRenderAttr("value", item)
|
||||
}\${
|
||||
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||
? _ssrLooseContain(_ctx.model, item)
|
||||
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
|
||||
}>\${
|
||||
_ssrInterpolate(item)
|
||||
}</option>\`)
|
||||
})
|
||||
_push(\`<!--]--></optgroup></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(`
|
||||
<select multiple v-model="model">
|
||||
<optgroup>
|
||||
<option v-if="true" :value="item">{{item}}</option>
|
||||
</optgroup>
|
||||
</select>`).code,
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
|
||||
if (true) {
|
||||
_push(\`<option\${
|
||||
_ssrRenderAttr("value", _ctx.item)
|
||||
}\${
|
||||
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||
? _ssrLooseContain(_ctx.model, _ctx.item)
|
||||
: _ssrLooseEqual(_ctx.model, _ctx.item))) ? " selected" : ""
|
||||
}>\${
|
||||
_ssrInterpolate(_ctx.item)
|
||||
}</option>\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
_push(\`</optgroup></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(`
|
||||
<select multiple v-model="model">
|
||||
<optgroup>
|
||||
<template v-if="ok">
|
||||
<option v-for="item in items" :value="item">{{item}}</option>
|
||||
</template>
|
||||
</optgroup>
|
||||
</select>`).code,
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup>\`)
|
||||
if (_ctx.ok) {
|
||||
_push(\`<!--[-->\`)
|
||||
_ssrRenderList(_ctx.items, (item) => {
|
||||
_push(\`<option\${
|
||||
_ssrRenderAttr("value", item)
|
||||
}\${
|
||||
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||
? _ssrLooseContain(_ctx.model, item)
|
||||
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
|
||||
}>\${
|
||||
_ssrInterpolate(item)
|
||||
}</option>\`)
|
||||
})
|
||||
_push(\`<!--]-->\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
_push(\`</optgroup></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
|
||||
expect(
|
||||
compileWithWrapper(`
|
||||
<select multiple v-model="model">
|
||||
<optgroup>
|
||||
<template v-for="item in items" :value="item">
|
||||
<option v-if="item===1" :value="item">{{item}}</option>
|
||||
</template>
|
||||
</optgroup>
|
||||
</select>`).code,
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
|
||||
_ssrRenderList(_ctx.items, (item) => {
|
||||
_push(\`<!--[-->\`)
|
||||
if (item===1) {
|
||||
_push(\`<option\${
|
||||
_ssrRenderAttr("value", item)
|
||||
}\${
|
||||
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||
? _ssrLooseContain(_ctx.model, item)
|
||||
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
|
||||
}>\${
|
||||
_ssrInterpolate(item)
|
||||
}</option>\`)
|
||||
} else {
|
||||
_push(\`<!---->\`)
|
||||
}
|
||||
_push(\`<!--]-->\`)
|
||||
})
|
||||
_push(\`<!--]--></optgroup></select></div>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('<input type="radio">', () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.16",
|
||||
"version": "3.5.21",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -29,7 +29,7 @@ if (__TEST__) {
|
|||
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
||||
throw new Error(
|
||||
`SSRErrorCodes need to be updated to ${
|
||||
DOMErrorCodes.__EXTEND_POINT__ + 1
|
||||
DOMErrorCodes.__EXTEND_POINT__
|
||||
} to match extension point from core DOMErrorCodes.`,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
|
||||
// Plugin for the first transform pass, which simply constructs the AST node
|
||||
export const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
/^(?:if|else|else-if)$/,
|
||||
processIf,
|
||||
)
|
||||
|
||||
|
|
|
@ -39,6 +39,18 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
const processSelectChildren = (children: TemplateChildNode[]) => {
|
||||
children.forEach(child => {
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
processOption(child as PlainElementNode)
|
||||
} else if (child.type === NodeTypes.FOR) {
|
||||
processSelectChildren(child.children)
|
||||
} else if (child.type === NodeTypes.IF) {
|
||||
child.branches.forEach(b => processSelectChildren(b.children))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function processOption(plainNode: PlainElementNode) {
|
||||
if (plainNode.tag === 'option') {
|
||||
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
|
||||
|
@ -65,9 +77,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
)
|
||||
}
|
||||
} else if (plainNode.tag === 'optgroup') {
|
||||
plainNode.children.forEach(option =>
|
||||
processOption(option as PlainElementNode),
|
||||
)
|
||||
processSelectChildren(plainNode.children)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,18 +173,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
checkDuplicatedValue()
|
||||
node.children = [createInterpolation(model, model.loc)]
|
||||
} else if (node.tag === 'select') {
|
||||
const processChildren = (children: TemplateChildNode[]) => {
|
||||
children.forEach(child => {
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
processOption(child as PlainElementNode)
|
||||
} else if (child.type === NodeTypes.FOR) {
|
||||
processChildren(child.children)
|
||||
} else if (child.type === NodeTypes.IF) {
|
||||
child.branches.forEach(b => processChildren(b.children))
|
||||
}
|
||||
})
|
||||
}
|
||||
processChildren(node.children)
|
||||
processSelectChildren(node.children)
|
||||
} else {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
|
|
|
@ -498,9 +498,10 @@ describe('reactivity/readonly', () => {
|
|||
const r = ref(false)
|
||||
const ror = readonly(r)
|
||||
const obj = reactive({ ror })
|
||||
expect(() => {
|
||||
obj.ror = true
|
||||
}).toThrow()
|
||||
expect(
|
||||
`Set operation on key "ror" failed: target is readonly.`,
|
||||
).toHaveBeenWarned()
|
||||
expect(obj.ror).toBe(false)
|
||||
})
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.16",
|
||||
"version": "3.5.21",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -107,7 +107,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
|
|||
return reactiveReadArray(this).join(separator)
|
||||
},
|
||||
|
||||
// keys() iterator only reads `length`, no optimisation required
|
||||
// keys() iterator only reads `length`, no optimization required
|
||||
|
||||
lastIndexOf(...args: unknown[]) {
|
||||
return searchProxy(this, 'lastIndexOf', args)
|
||||
|
@ -200,7 +200,7 @@ function iterator(
|
|||
wrapValue: (value: any) => unknown,
|
||||
) {
|
||||
// note that taking ARRAY_ITERATE dependency here is not strictly equivalent
|
||||
// to calling iterate on the proxified array.
|
||||
// to calling iterate on the proxied array.
|
||||
// creating the iterator does not access any array property:
|
||||
// it is only when .next() is called that length and indexes are accessed.
|
||||
// pushed to the extreme, an iterator could be created in one effect scope,
|
||||
|
|
|
@ -153,7 +153,13 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
|||
}
|
||||
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
||||
if (isOldValueReadonly) {
|
||||
return false
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
||||
target[key],
|
||||
)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
oldValue.value = value
|
||||
return true
|
||||
|
|
|
@ -125,7 +125,7 @@ function createInstrumentations(
|
|||
get size() {
|
||||
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
|
||||
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||
return Reflect.get(target, 'size', target)
|
||||
return target.size
|
||||
},
|
||||
has(this: CollectionTypes, key: unknown): boolean {
|
||||
const target = this[ReactiveFlags.RAW]
|
||||
|
|
|
@ -275,7 +275,7 @@ export function proxyRefs<T extends object>(
|
|||
objectWithRefs: T,
|
||||
): ShallowUnwrapRef<T> {
|
||||
return isReactive(objectWithRefs)
|
||||
? objectWithRefs
|
||||
? (objectWithRefs as ShallowUnwrapRef<T>)
|
||||
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
|
||||
}
|
||||
|
||||
|
|
|
@ -331,17 +331,17 @@ export function watch(
|
|||
export function traverse(
|
||||
value: unknown,
|
||||
depth: number = Infinity,
|
||||
seen?: Set<unknown>,
|
||||
seen?: Map<unknown, number>,
|
||||
): unknown {
|
||||
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||
return value
|
||||
}
|
||||
|
||||
seen = seen || new Set()
|
||||
if (seen.has(value)) {
|
||||
seen = seen || new Map()
|
||||
if ((seen.get(value) || 0) >= depth) {
|
||||
return value
|
||||
}
|
||||
seen.add(value)
|
||||
seen.set(value, depth)
|
||||
depth--
|
||||
if (isRef(value)) {
|
||||
traverse(value.value, depth, seen)
|
||||
|
|
|
@ -1689,6 +1689,57 @@ describe('api: watch', () => {
|
|||
expect(cb).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
test('watching the same object at different depths', async () => {
|
||||
const arr1: any[] = reactive([[[{ foo: {} }]]])
|
||||
const arr2 = arr1[0]
|
||||
const arr3 = arr2[0]
|
||||
const obj = arr3[0]
|
||||
arr1.push(arr3)
|
||||
|
||||
const cb1 = vi.fn()
|
||||
const cb2 = vi.fn()
|
||||
const cb3 = vi.fn()
|
||||
const cb4 = vi.fn()
|
||||
watch(arr1, cb1, { deep: 1 })
|
||||
watch(arr1, cb2, { deep: 2 })
|
||||
watch(arr1, cb3, { deep: 3 })
|
||||
watch(arr1, cb4, { deep: 4 })
|
||||
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(0)
|
||||
expect(cb3).toHaveBeenCalledTimes(0)
|
||||
expect(cb4).toHaveBeenCalledTimes(0)
|
||||
|
||||
obj.foo = {}
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(0)
|
||||
expect(cb3).toHaveBeenCalledTimes(1)
|
||||
expect(cb4).toHaveBeenCalledTimes(1)
|
||||
|
||||
obj.foo.bar = 1
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(0)
|
||||
expect(cb3).toHaveBeenCalledTimes(1)
|
||||
expect(cb4).toHaveBeenCalledTimes(2)
|
||||
|
||||
arr3.push(obj.foo)
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(1)
|
||||
expect(cb3).toHaveBeenCalledTimes(2)
|
||||
expect(cb4).toHaveBeenCalledTimes(3)
|
||||
|
||||
obj.foo.bar = 2
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(1)
|
||||
expect(cb3).toHaveBeenCalledTimes(3)
|
||||
expect(cb4).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
test('pause / resume', async () => {
|
||||
const count = ref(0)
|
||||
const cb = vi.fn()
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import {
|
||||
type ComponentPublicInstance,
|
||||
createApp,
|
||||
defineComponent,
|
||||
h,
|
||||
nextTick,
|
||||
|
@ -598,4 +599,45 @@ describe('component: emit', () => {
|
|||
render(h(ComponentC), el)
|
||||
expect(renderFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('merging emits for a component that is also used as a mixin', () => {
|
||||
const render = () => h('div')
|
||||
const CompA = {
|
||||
render,
|
||||
}
|
||||
const validateByMixin = vi.fn(() => true)
|
||||
const validateByGlobalMixin = vi.fn(() => true)
|
||||
|
||||
const mixin = {
|
||||
emits: {
|
||||
one: validateByMixin,
|
||||
},
|
||||
}
|
||||
|
||||
const CompB = defineComponent({
|
||||
mixins: [mixin, CompA],
|
||||
created(this) {
|
||||
this.$emit('one', 1)
|
||||
},
|
||||
render,
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
render() {
|
||||
return [h(CompA), h(CompB)]
|
||||
},
|
||||
})
|
||||
|
||||
app.mixin({
|
||||
emits: {
|
||||
one: validateByGlobalMixin,
|
||||
two: null,
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
expect(validateByMixin).toHaveBeenCalledTimes(1)
|
||||
expect(validateByGlobalMixin).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -167,13 +167,26 @@ describe('component: proxy', () => {
|
|||
data() {
|
||||
return {
|
||||
foo: 0,
|
||||
$foo: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cmp: () => {
|
||||
throw new Error('value of cmp should not be accessed')
|
||||
},
|
||||
$cmp: () => {
|
||||
throw new Error('value of $cmp should not be read')
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
bar: 1,
|
||||
}
|
||||
},
|
||||
__cssModules: {
|
||||
$style: {},
|
||||
cssStyles: {},
|
||||
},
|
||||
mounted() {
|
||||
instanceProxy = this
|
||||
},
|
||||
|
@ -181,6 +194,7 @@ describe('component: proxy', () => {
|
|||
|
||||
const app = createApp(Comp, { msg: 'hello' })
|
||||
app.config.globalProperties.global = 1
|
||||
app.config.globalProperties.$global = 1
|
||||
|
||||
app.mount(nodeOps.createElement('div'))
|
||||
|
||||
|
@ -188,12 +202,20 @@ describe('component: proxy', () => {
|
|||
expect('msg' in instanceProxy).toBe(true)
|
||||
// data
|
||||
expect('foo' in instanceProxy).toBe(true)
|
||||
// ctx
|
||||
expect('$foo' in instanceProxy).toBe(false)
|
||||
// setupState
|
||||
expect('bar' in instanceProxy).toBe(true)
|
||||
// ctx
|
||||
expect('cmp' in instanceProxy).toBe(true)
|
||||
expect('$cmp' in instanceProxy).toBe(true)
|
||||
// public properties
|
||||
expect('$el' in instanceProxy).toBe(true)
|
||||
// CSS modules
|
||||
expect('$style' in instanceProxy).toBe(true)
|
||||
expect('cssStyles' in instanceProxy).toBe(true)
|
||||
// global properties
|
||||
expect('global' in instanceProxy).toBe(true)
|
||||
expect('$global' in instanceProxy).toBe(true)
|
||||
|
||||
// non-existent
|
||||
expect('$foobar' in instanceProxy).toBe(false)
|
||||
|
@ -202,11 +224,15 @@ describe('component: proxy', () => {
|
|||
// #4962 triggering getter should not cause non-existent property to
|
||||
// pass the has check
|
||||
instanceProxy.baz
|
||||
instanceProxy.$baz
|
||||
expect('baz' in instanceProxy).toBe(false)
|
||||
expect('$baz' in instanceProxy).toBe(false)
|
||||
|
||||
// set non-existent (goes into proxyTarget sink)
|
||||
instanceProxy.baz = 1
|
||||
expect('baz' in instanceProxy).toBe(true)
|
||||
instanceProxy.$baz = 1
|
||||
expect('$baz' in instanceProxy).toBe(true)
|
||||
|
||||
// dev mode ownKeys check for console inspection
|
||||
// should only expose own keys
|
||||
|
@ -214,7 +240,10 @@ describe('component: proxy', () => {
|
|||
'msg',
|
||||
'bar',
|
||||
'foo',
|
||||
'cmp',
|
||||
'$cmp',
|
||||
'baz',
|
||||
'$baz',
|
||||
])
|
||||
})
|
||||
|
||||
|
|
|
@ -6,6 +6,8 @@ import {
|
|||
nodeOps,
|
||||
ref,
|
||||
render,
|
||||
serializeInner,
|
||||
useSlots,
|
||||
} from '@vue/runtime-test'
|
||||
import { createBlock, normalizeVNode } from '../src/vnode'
|
||||
import { createSlots } from '../src/helpers/createSlots'
|
||||
|
@ -42,6 +44,25 @@ describe('component: slots', () => {
|
|||
expect(slots).toMatchObject({})
|
||||
})
|
||||
|
||||
test('initSlots: ensure compiler marker non-enumerable', () => {
|
||||
const Comp = {
|
||||
render() {
|
||||
const slots = useSlots()
|
||||
// Only user-defined slots should be enumerable
|
||||
expect(Object.keys(slots)).toEqual(['foo'])
|
||||
|
||||
// Internal compiler markers must still exist but be non-enumerable
|
||||
expect(slots).toHaveProperty('_')
|
||||
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
|
||||
false,
|
||||
)
|
||||
return h('div')
|
||||
},
|
||||
}
|
||||
const slots = { foo: () => {}, _: 1 }
|
||||
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
||||
})
|
||||
|
||||
test('initSlots: should normalize object slots (when value is null, string, array)', () => {
|
||||
const { slots } = renderWithSlots({
|
||||
_inner: '_inner',
|
||||
|
@ -50,6 +71,10 @@ describe('component: slots', () => {
|
|||
footer: ['f1', 'f2'],
|
||||
})
|
||||
|
||||
expect(
|
||||
'[Vue warn]: Non-function value encountered for slot "_inner". Prefer function slots for better performance.',
|
||||
).toHaveBeenWarned()
|
||||
|
||||
expect(
|
||||
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
|
||||
).toHaveBeenWarned()
|
||||
|
@ -58,8 +83,8 @@ describe('component: slots', () => {
|
|||
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
|
||||
).toHaveBeenWarned()
|
||||
|
||||
expect(slots).not.toHaveProperty('_inner')
|
||||
expect(slots).not.toHaveProperty('foo')
|
||||
expect(slots._inner()).toMatchObject([normalizeVNode('_inner')])
|
||||
expect(slots.header()).toMatchObject([normalizeVNode('header')])
|
||||
expect(slots.footer()).toMatchObject([
|
||||
normalizeVNode('f1'),
|
||||
|
@ -418,4 +443,22 @@ describe('component: slots', () => {
|
|||
'Slot "default" invoked outside of the render function',
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('slot name starts with underscore', () => {
|
||||
const Comp = {
|
||||
setup(_: any, { slots }: any) {
|
||||
return () => slots._foo()
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () => h(Comp, null, { _foo: () => 'foo' })
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
createApp(App).mount(root)
|
||||
expect(serializeInner(root)).toBe('foo')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -17,6 +17,8 @@ import {
|
|||
onUnmounted,
|
||||
ref,
|
||||
render,
|
||||
renderList,
|
||||
renderSlot,
|
||||
resolveDynamicComponent,
|
||||
serializeInner,
|
||||
shallowRef,
|
||||
|
@ -2161,6 +2163,80 @@ describe('Suspense', () => {
|
|||
await Promise.all(deps)
|
||||
})
|
||||
|
||||
// #13453
|
||||
test('add new async deps during patching', async () => {
|
||||
const getComponent = (type: string) => {
|
||||
if (type === 'A') {
|
||||
return defineAsyncComponent({
|
||||
setup() {
|
||||
return () => h('div', 'A')
|
||||
},
|
||||
})
|
||||
}
|
||||
return defineAsyncComponent({
|
||||
setup() {
|
||||
return () => h('div', 'B')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const types = ref(['A'])
|
||||
const add = async () => {
|
||||
types.value.push('B')
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
// mount Suspense B
|
||||
// [Suspense A] -> [Suspense A(pending), Suspense B(pending)]
|
||||
await add()
|
||||
// patch Suspense B (still pending)
|
||||
// [Suspense A(pending), Suspense B(pending)] -> [Suspense B(pending)]
|
||||
types.value.shift()
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return h(Fragment, null, [
|
||||
renderList(types.value, type => {
|
||||
return h(
|
||||
Suspense,
|
||||
{ key: type },
|
||||
{
|
||||
default: () => [
|
||||
renderSlot(this.$slots, 'default', { type: type }),
|
||||
],
|
||||
},
|
||||
)
|
||||
}),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Comp, null, {
|
||||
default: (params: any) => [h(getComponent(params.type))],
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe(`<!---->`)
|
||||
|
||||
await Promise.all(deps)
|
||||
expect(serializeInner(root)).toBe(`<div>A</div>`)
|
||||
|
||||
update()
|
||||
await nextTick()
|
||||
// wait for both A and B to resolve
|
||||
await Promise.all(deps)
|
||||
// wait for new B to resolve
|
||||
await Promise.all(deps)
|
||||
expect(serializeInner(root)).toBe(`<div>B</div>`)
|
||||
})
|
||||
|
||||
describe('warnings', () => {
|
||||
// base function to check if a combination of slots warns or not
|
||||
function baseCheckWarn(
|
||||
|
@ -2230,5 +2306,57 @@ describe('Suspense', () => {
|
|||
fallback: [h('div'), h('div')],
|
||||
})
|
||||
})
|
||||
|
||||
// #13559
|
||||
test('renders multiple async components in Suspense with v-for and updates on items change', async () => {
|
||||
const CompAsyncSetup = defineAsyncComponent({
|
||||
props: ['item'],
|
||||
render(ctx: any) {
|
||||
return h('div', ctx.item.name)
|
||||
},
|
||||
})
|
||||
|
||||
const items = ref([
|
||||
{ id: 1, name: '111' },
|
||||
{ id: 2, name: '222' },
|
||||
{ id: 3, name: '333' },
|
||||
])
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: () =>
|
||||
h(
|
||||
Fragment,
|
||||
null,
|
||||
items.value.map(item =>
|
||||
h(CompAsyncSetup, { item, key: item.id }),
|
||||
),
|
||||
),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
await nextTick()
|
||||
await Promise.all(deps)
|
||||
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>111</div><div>222</div><div>333</div>`,
|
||||
)
|
||||
|
||||
items.value = [
|
||||
{ id: 4, name: '444' },
|
||||
{ id: 5, name: '555' },
|
||||
{ id: 6, name: '666' },
|
||||
]
|
||||
await nextTick()
|
||||
await Promise.all(deps)
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>444</div><div>555</div><div>666</div>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -106,6 +106,134 @@ describe('useTemplateRef', () => {
|
|||
expect(tRef!.value).toBe(null)
|
||||
})
|
||||
|
||||
test('should work when used with direct ref value with ref_key', () => {
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
return () => h('div', { ref: tRef, ref_key: key })
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toBe(root.children[0])
|
||||
})
|
||||
|
||||
test('should work when used with direct ref value with ref_key and ref_for', () => {
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
[1, 2, 3].map(x =>
|
||||
h('span', { ref: tRef, ref_key: key, ref_for: true }, x.toString()),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toHaveLength(3)
|
||||
})
|
||||
|
||||
test('should work when used with direct ref value with ref_key and dynamic value', async () => {
|
||||
const refMode = ref('h1-ref')
|
||||
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
switch (refMode.value) {
|
||||
case 'h1-ref':
|
||||
return h('h1', { ref: tRef, ref_key: key })
|
||||
case 'h2-ref':
|
||||
return h('h2', { ref: tRef, ref_key: key })
|
||||
case 'no-ref':
|
||||
return h('span')
|
||||
case 'nothing':
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect(tRef!.value.tag).toBe('h1')
|
||||
|
||||
refMode.value = 'h2-ref'
|
||||
await nextTick()
|
||||
expect(tRef!.value.tag).toBe('h2')
|
||||
|
||||
refMode.value = 'no-ref'
|
||||
await nextTick()
|
||||
expect(tRef!.value).toBeNull()
|
||||
|
||||
refMode.value = 'nothing'
|
||||
await nextTick()
|
||||
expect(tRef!.value).toBeNull()
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should work when used with dynamic direct refs and ref_keys', async () => {
|
||||
const refKey = ref('foo')
|
||||
|
||||
let tRefs: Record<string, ShallowRef>
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRefs = {
|
||||
foo: useTemplateRef('foo'),
|
||||
bar: useTemplateRef('bar'),
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return h('div', { ref: tRefs[refKey.value], ref_key: refKey.value })
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect(tRefs!['foo'].value).toBe(root.children[0])
|
||||
expect(tRefs!['bar'].value).toBeNull()
|
||||
|
||||
refKey.value = 'bar'
|
||||
await nextTick()
|
||||
expect(tRefs!['foo'].value).toBeNull()
|
||||
expect(tRefs!['bar'].value).toBe(root.children[0])
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should not work when used with direct ref value without ref_key (in dev mode)', () => {
|
||||
let tRef: ShallowRef
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef('refKey')
|
||||
return () => h('div', { ref: tRef })
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect(tRef!.value).toBeNull()
|
||||
})
|
||||
|
||||
test('should work when used as direct ref value (compiled in prod mode)', () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
|
@ -125,4 +253,65 @@ describe('useTemplateRef', () => {
|
|||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
test('should work when used as direct ref value with ref_key and ref_for (compiled in prod mode)', () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
[1, 2, 3].map(x =>
|
||||
h(
|
||||
'span',
|
||||
{ ref: tRef, ref_key: key, ref_for: true },
|
||||
x.toString(),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toHaveLength(3)
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
test('should work when used as direct ref value with ref_for but without ref_key (compiled in prod mode)', () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
let tRef: ShallowRef
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef('refKey')
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
[1, 2, 3].map(x =>
|
||||
h('span', { ref: tRef, ref_for: true }, x.toString()),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toHaveLength(3)
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -894,4 +894,150 @@ describe('hot module replacement', () => {
|
|||
await timeout()
|
||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
||||
})
|
||||
|
||||
test('multi reload child wrapped in Suspense + KeepAlive', async () => {
|
||||
const id = 'test-child-reload-3'
|
||||
const Child: ComponentOptions = {
|
||||
__hmrId: id,
|
||||
setup() {
|
||||
const count = ref(0)
|
||||
return { count }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
}
|
||||
createRecord(id, Child)
|
||||
|
||||
const appId = 'test-app-id'
|
||||
const App: ComponentOptions = {
|
||||
__hmrId: appId,
|
||||
components: { Child },
|
||||
render: compileToFunction(`
|
||||
<KeepAlive>
|
||||
<Suspense>
|
||||
<Child />
|
||||
</Suspense>
|
||||
</KeepAlive>
|
||||
`),
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe('<div>0</div>')
|
||||
await timeout()
|
||||
reload(id, {
|
||||
__hmrId: id,
|
||||
setup() {
|
||||
const count = ref(1)
|
||||
return { count }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
})
|
||||
await timeout()
|
||||
expect(serializeInner(root)).toBe('<div>1</div>')
|
||||
|
||||
reload(id, {
|
||||
__hmrId: id,
|
||||
setup() {
|
||||
const count = ref(2)
|
||||
return { count }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
})
|
||||
await timeout()
|
||||
expect(serializeInner(root)).toBe('<div>2</div>')
|
||||
})
|
||||
|
||||
test('rerender for nested component', () => {
|
||||
const id = 'child-nested-rerender'
|
||||
const Foo: ComponentOptions = {
|
||||
__hmrId: id,
|
||||
render() {
|
||||
return this.$slots.default()
|
||||
},
|
||||
}
|
||||
createRecord(id, Foo)
|
||||
|
||||
const parentId = 'parent-nested-rerender'
|
||||
const Parent: ComponentOptions = {
|
||||
__hmrId: parentId,
|
||||
render() {
|
||||
return h(Foo, null, {
|
||||
default: () => this.$slots.default(),
|
||||
_: 3 /* FORWARDED */,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const appId = 'app-nested-rerender'
|
||||
const App: ComponentOptions = {
|
||||
__hmrId: appId,
|
||||
render: () =>
|
||||
h(Parent, null, {
|
||||
default: () => [
|
||||
h(Foo, null, {
|
||||
default: () => ['foo'],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}
|
||||
createRecord(parentId, App)
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe('foo')
|
||||
|
||||
rerender(id, () => 'bar')
|
||||
expect(serializeInner(root)).toBe('bar')
|
||||
})
|
||||
|
||||
// https://github.com/vitejs/vite-plugin-vue/issues/599
|
||||
// Both Outer and Inner are reloaded when './server.js' changes
|
||||
test('reload nested components from single update', async () => {
|
||||
const innerId = 'nested-reload-inner'
|
||||
const outerId = 'nested-reload-outer'
|
||||
|
||||
let Inner = {
|
||||
__hmrId: innerId,
|
||||
render() {
|
||||
return h('div', 'foo')
|
||||
},
|
||||
}
|
||||
let Outer = {
|
||||
__hmrId: outerId,
|
||||
render() {
|
||||
return h(Inner)
|
||||
},
|
||||
}
|
||||
|
||||
createRecord(innerId, Inner)
|
||||
createRecord(outerId, Outer)
|
||||
|
||||
const App = {
|
||||
render: () => h(Outer),
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe('<div>foo</div>')
|
||||
|
||||
Inner = {
|
||||
__hmrId: innerId,
|
||||
render() {
|
||||
return h('div', 'bar')
|
||||
},
|
||||
}
|
||||
Outer = {
|
||||
__hmrId: outerId,
|
||||
render() {
|
||||
return h(Inner)
|
||||
},
|
||||
}
|
||||
|
||||
// trigger reload for both Outer and Inner
|
||||
reload(outerId, Outer)
|
||||
reload(innerId, Inner)
|
||||
await nextTick()
|
||||
|
||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1160,6 +1160,69 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
})
|
||||
|
||||
// #13510
|
||||
test('update async component after parent mount before async component resolve', async () => {
|
||||
const Comp = {
|
||||
props: ['toggle'],
|
||||
render(this: any) {
|
||||
return h('h1', [
|
||||
this.toggle ? 'Async component' : 'Updated async component',
|
||||
])
|
||||
},
|
||||
}
|
||||
let serverResolve: any
|
||||
let AsyncComp = defineAsyncComponent(
|
||||
() =>
|
||||
new Promise(r => {
|
||||
serverResolve = r
|
||||
}),
|
||||
)
|
||||
|
||||
const toggle = ref(true)
|
||||
const App = {
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
// change state, after mount and before async component resolve
|
||||
nextTick(() => (toggle.value = false))
|
||||
})
|
||||
|
||||
return () => {
|
||||
return h(AsyncComp, { toggle: toggle.value })
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// server render
|
||||
const htmlPromise = renderToString(h(App))
|
||||
serverResolve(Comp)
|
||||
const html = await htmlPromise
|
||||
expect(html).toMatchInlineSnapshot(`"<h1>Async component</h1>"`)
|
||||
|
||||
// hydration
|
||||
let clientResolve: any
|
||||
AsyncComp = defineAsyncComponent(
|
||||
() =>
|
||||
new Promise(r => {
|
||||
clientResolve = r
|
||||
}),
|
||||
)
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = html
|
||||
createSSRApp(App).mount(container)
|
||||
|
||||
// resolve
|
||||
clientResolve(Comp)
|
||||
await new Promise(r => setTimeout(r))
|
||||
|
||||
// prevent lazy hydration since the component has been patched
|
||||
expect('Skipping lazy hydration for component').toHaveBeenWarned()
|
||||
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<h1>Updated async component</h1>"`,
|
||||
)
|
||||
})
|
||||
|
||||
test('hydrate safely when property used by async setup changed before render', async () => {
|
||||
const toggle = ref(true)
|
||||
|
||||
|
@ -1677,6 +1740,35 @@ describe('SSR hydration', () => {
|
|||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
// #13394
|
||||
test('transition appear work with empty content', async () => {
|
||||
const show = ref(true)
|
||||
const { vnode, container } = mountWithHydration(
|
||||
`<template><!----></template>`,
|
||||
function (this: any) {
|
||||
return h(
|
||||
Transition,
|
||||
{ appear: true },
|
||||
{
|
||||
default: () =>
|
||||
show.value
|
||||
? renderSlot(this.$slots, 'default')
|
||||
: createTextVNode('foo'),
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// empty slot render as a comment node
|
||||
expect(container.firstChild!.nodeType).toBe(Node.COMMENT_NODE)
|
||||
expect(vnode.el).toBe(container.firstChild)
|
||||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe('foo')
|
||||
})
|
||||
|
||||
test('transition appear with v-if', () => {
|
||||
const show = false
|
||||
const { vnode, container } = mountWithHydration(
|
||||
|
@ -2265,6 +2357,30 @@ describe('SSR hydration', () => {
|
|||
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('with disabled teleport + undefined target', async () => {
|
||||
const container = document.createElement('div')
|
||||
const isOpen = ref(false)
|
||||
const App = {
|
||||
setup() {
|
||||
return { isOpen }
|
||||
},
|
||||
template: `
|
||||
<Teleport :to="undefined" :disabled="true">
|
||||
<div v-if="isOpen">
|
||||
Menu is open...
|
||||
</div>
|
||||
</Teleport>`,
|
||||
}
|
||||
container.innerHTML = await renderToString(h(App))
|
||||
const app = createSSRApp(App)
|
||||
app.mount(container)
|
||||
isOpen.value = true
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--teleport start--><div> Menu is open... </div><!--teleport end-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('escape css var name', () => {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
render,
|
||||
serializeInner,
|
||||
shallowRef,
|
||||
watch,
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
describe('api: template refs', () => {
|
||||
|
@ -179,6 +180,89 @@ describe('api: template refs', () => {
|
|||
expect(el.value).toBe(null)
|
||||
})
|
||||
|
||||
// #12639
|
||||
it('update and unmount child in the same tick', async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const el = ref(null)
|
||||
const toggle = ref(true)
|
||||
const show = ref(true)
|
||||
|
||||
const Comp = defineComponent({
|
||||
emits: ['change'],
|
||||
props: ['show'],
|
||||
setup(props, { emit }) {
|
||||
watch(
|
||||
() => props.show,
|
||||
() => {
|
||||
emit('change')
|
||||
},
|
||||
)
|
||||
return () => h('div', 'hi')
|
||||
},
|
||||
})
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return {
|
||||
refKey: el,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return toggle.value
|
||||
? h(Comp, {
|
||||
ref: 'refKey',
|
||||
show: show.value,
|
||||
onChange: () => (toggle.value = false),
|
||||
})
|
||||
: null
|
||||
},
|
||||
}
|
||||
render(h(App), root)
|
||||
expect(el.value).not.toBe(null)
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(el.value).toBe(null)
|
||||
})
|
||||
|
||||
it('set and change ref in the same tick', async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const show = ref(false)
|
||||
const refName = ref('a')
|
||||
|
||||
const Child = defineComponent({
|
||||
setup() {
|
||||
refName.value = 'b'
|
||||
return () => {}
|
||||
},
|
||||
})
|
||||
|
||||
const Comp = {
|
||||
render() {
|
||||
return h(Child, {
|
||||
ref: refName.value,
|
||||
})
|
||||
},
|
||||
updated(this: any) {
|
||||
expect(this.$refs.a).toBe(null)
|
||||
expect(this.$refs.b).not.toBe(null)
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
render() {
|
||||
return show.value ? h(Comp) : null
|
||||
},
|
||||
}
|
||||
|
||||
render(h(App), root)
|
||||
expect(refName.value).toBe('a')
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(refName.value).toBe('b')
|
||||
})
|
||||
|
||||
it('unset old ref when new ref is absent', async () => {
|
||||
const root1 = nodeOps.createElement('div')
|
||||
const root2 = nodeOps.createElement('div')
|
||||
|
|
|
@ -553,18 +553,6 @@ describe('vnode', () => {
|
|||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
||||
})
|
||||
|
||||
test('with suspense', () => {
|
||||
const hoist = createVNode('div')
|
||||
let vnode1
|
||||
const vnode =
|
||||
(openBlock(),
|
||||
createBlock('div', null, [
|
||||
hoist,
|
||||
(vnode1 = createVNode(() => {}, null, 'text')),
|
||||
]))
|
||||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
||||
})
|
||||
|
||||
// #1039
|
||||
// <component :is="foo">{{ bar }}</component>
|
||||
// - content is compiled as slot
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.16",
|
||||
"version": "3.5.21",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -43,7 +43,7 @@ export interface AsyncComponentOptions<T = any> {
|
|||
export const isAsyncWrapper = (i: ComponentInternalInstance | VNode): boolean =>
|
||||
!!(i.type as ComponentOptions).__asyncLoader
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
export function defineAsyncComponent<
|
||||
T extends Component = { new (): ComponentPublicInstance },
|
||||
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
|
||||
|
@ -123,28 +123,30 @@ export function defineAsyncComponent<
|
|||
|
||||
__asyncHydrate(el, instance, hydrate) {
|
||||
let patched = false
|
||||
const doHydrate = hydrateStrategy
|
||||
? () => {
|
||||
;(instance.bu || (instance.bu = [])).push(() => (patched = true))
|
||||
const performHydrate = () => {
|
||||
// skip hydration if the component has been patched
|
||||
if (__DEV__ && patched) {
|
||||
if (patched) {
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
|
||||
`Skipping lazy hydration for component '${getComponentName(resolvedComp!) || resolvedComp!.__file}': ` +
|
||||
`it was updated before lazy hydration performed.`,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
hydrate()
|
||||
}
|
||||
const doHydrate = hydrateStrategy
|
||||
? () => {
|
||||
const teardown = hydrateStrategy(performHydrate, cb =>
|
||||
forEachElement(el, cb),
|
||||
)
|
||||
if (teardown) {
|
||||
;(instance.bum || (instance.bum = [])).push(teardown)
|
||||
}
|
||||
;(instance.u || (instance.u = [])).push(() => (patched = true))
|
||||
}
|
||||
: hydrate
|
||||
: performHydrate
|
||||
if (resolvedComp) {
|
||||
doHydrate()
|
||||
} else {
|
||||
|
|
|
@ -301,7 +301,7 @@ export function defineComponent<
|
|||
>
|
||||
|
||||
// implementation, close to no-op
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
export function defineComponent(
|
||||
options: unknown,
|
||||
extraOptions?: ComponentOptions,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { isFunction } from '@vue/shared'
|
||||
import { currentInstance } from './component'
|
||||
import { currentRenderingInstance } from './componentRenderContext'
|
||||
import { currentInstance, getCurrentInstance } from './component'
|
||||
import { currentApp } from './apiCreateApp'
|
||||
import { warn } from './warning'
|
||||
|
||||
|
@ -51,7 +50,7 @@ export function inject(
|
|||
) {
|
||||
// fallback to `currentRenderingInstance` so that this can be called in
|
||||
// a functional component
|
||||
const instance = currentInstance || currentRenderingInstance
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// also support looking up from app-level provides w/ `app.runWithContext()`
|
||||
if (instance || currentApp) {
|
||||
|
@ -90,5 +89,5 @@ export function inject(
|
|||
* user. One example is `useRoute()` in `vue-router`.
|
||||
*/
|
||||
export function hasInjectionContext(): boolean {
|
||||
return !!(currentInstance || currentRenderingInstance || currentApp)
|
||||
return !!(getCurrentInstance() || currentApp)
|
||||
}
|
||||
|
|
|
@ -382,17 +382,17 @@ export function withDefaults<
|
|||
}
|
||||
|
||||
export function useSlots(): SetupContext['slots'] {
|
||||
return getContext().slots
|
||||
return getContext('useSlots').slots
|
||||
}
|
||||
|
||||
export function useAttrs(): SetupContext['attrs'] {
|
||||
return getContext().attrs
|
||||
return getContext('useAttrs').attrs
|
||||
}
|
||||
|
||||
function getContext(): SetupContext {
|
||||
function getContext(calledFunctionName: string): SetupContext {
|
||||
const i = getCurrentInstance()!
|
||||
if (__DEV__ && !i) {
|
||||
warn(`useContext() called without active instance.`)
|
||||
warn(`${calledFunctionName}() called without active instance.`)
|
||||
}
|
||||
return i.setupContext || (i.setupContext = createSetupContext(i))
|
||||
}
|
||||
|
|
|
@ -536,7 +536,7 @@ function installCompatMount(
|
|||
if (__DEV__) {
|
||||
for (let i = 0; i < container.attributes.length; i++) {
|
||||
const attr = container.attributes[i]
|
||||
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
|
||||
if (attr.name !== 'v-cloak' && /^(?:v-|:|@)/.test(attr.name)) {
|
||||
warnDeprecation(DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null)
|
||||
break
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ import {
|
|||
legacyPrependModifier,
|
||||
legacyRenderSlot,
|
||||
legacyRenderStatic,
|
||||
legacyresolveScopedSlots,
|
||||
legacyResolveScopedSlots,
|
||||
} from './renderHelpers'
|
||||
import { resolveFilter } from '../helpers/resolveAssets'
|
||||
import type { Slots } from '../componentSlots'
|
||||
|
@ -183,7 +183,7 @@ export function installCompatInstanceProperties(
|
|||
_b: () => legacyBindObjectProps,
|
||||
_v: () => createTextVNode,
|
||||
_e: () => createCommentVNode,
|
||||
_u: () => legacyresolveScopedSlots,
|
||||
_u: () => legacyResolveScopedSlots,
|
||||
_g: () => legacyBindObjectListeners,
|
||||
_d: () => legacyBindDynamicKeys,
|
||||
_p: () => legacyPrependModifier,
|
||||
|
|
|
@ -87,7 +87,7 @@ type LegacyScopedSlotsData = Array<
|
|||
| LegacyScopedSlotsData
|
||||
>
|
||||
|
||||
export function legacyresolveScopedSlots(
|
||||
export function legacyResolveScopedSlots(
|
||||
fns: LegacyScopedSlotsData,
|
||||
raw?: Record<string, Slot>,
|
||||
// the following are added in 2.6
|
||||
|
|
|
@ -585,13 +585,13 @@ export interface ComponentInternalInstance {
|
|||
* For updating css vars on contained teleports
|
||||
* @internal
|
||||
*/
|
||||
ut?: (vars?: Record<string, string>) => void
|
||||
ut?: (vars?: Record<string, unknown>) => void
|
||||
|
||||
/**
|
||||
* dev only. For style v-bind hydration mismatch checks
|
||||
* @internal
|
||||
*/
|
||||
getCssVars?: () => Record<string, string>
|
||||
getCssVars?: () => Record<string, unknown>
|
||||
|
||||
/**
|
||||
* v2 compat only, for caching mutated $options
|
||||
|
@ -1203,7 +1203,7 @@ export function getComponentPublicInstance(
|
|||
}
|
||||
}
|
||||
|
||||
const classifyRE = /(?:^|[-_])(\w)/g
|
||||
const classifyRE = /(?:^|[-_])\w/g
|
||||
const classify = (str: string): string =>
|
||||
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
||||
|
||||
|
|
|
@ -151,10 +151,14 @@ export function emit(
|
|||
}
|
||||
|
||||
let args = rawArgs
|
||||
const isModelListener = event.startsWith('update:')
|
||||
const isCompatModelListener =
|
||||
__COMPAT__ && compatModelEventPrefix + event in props
|
||||
const isModelListener = isCompatModelListener || event.startsWith('update:')
|
||||
const modifiers = isCompatModelListener
|
||||
? props.modelModifiers
|
||||
: isModelListener && getModelModifiers(props, event.slice(7))
|
||||
|
||||
// for v-model update:xxx events, apply modifiers on args
|
||||
const modifiers = isModelListener && getModelModifiers(props, event.slice(7))
|
||||
if (modifiers) {
|
||||
if (modifiers.trim) {
|
||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||
|
@ -228,12 +232,14 @@ export function emit(
|
|||
}
|
||||
}
|
||||
|
||||
const mixinEmitsCache = new WeakMap<ConcreteComponent, ObjectEmitsOptions>()
|
||||
export function normalizeEmitsOptions(
|
||||
comp: ConcreteComponent,
|
||||
appContext: AppContext,
|
||||
asMixin = false,
|
||||
): ObjectEmitsOptions | null {
|
||||
const cache = appContext.emitsCache
|
||||
const cache =
|
||||
__FEATURE_OPTIONS_API__ && asMixin ? mixinEmitsCache : appContext.emitsCache
|
||||
const cached = cache.get(comp)
|
||||
if (cached !== undefined) {
|
||||
return cached
|
||||
|
|
|
@ -756,6 +756,7 @@ export function applyOptions(instance: ComponentInternalInstance): void {
|
|||
Object.defineProperty(exposed, key, {
|
||||
get: () => publicThis[key],
|
||||
set: val => (publicThis[key] = val),
|
||||
enumerable: true,
|
||||
})
|
||||
})
|
||||
} else if (!instance.exposed) {
|
||||
|
|
|
@ -575,19 +575,20 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
|||
|
||||
has(
|
||||
{
|
||||
_: { data, setupState, accessCache, ctx, appContext, propsOptions },
|
||||
_: { data, setupState, accessCache, ctx, appContext, propsOptions, type },
|
||||
}: ComponentRenderContext,
|
||||
key: string,
|
||||
) {
|
||||
let normalizedProps
|
||||
return (
|
||||
!!accessCache![key] ||
|
||||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
||||
let normalizedProps, cssModules
|
||||
return !!(
|
||||
accessCache![key] ||
|
||||
(data !== EMPTY_OBJ && key[0] !== '$' && hasOwn(data, key)) ||
|
||||
hasSetupBinding(setupState, key) ||
|
||||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
||||
hasOwn(ctx, key) ||
|
||||
hasOwn(publicPropertiesMap, key) ||
|
||||
hasOwn(appContext.config.globalProperties, key)
|
||||
hasOwn(appContext.config.globalProperties, key) ||
|
||||
((cssModules = type.__cssModules) && cssModules[key])
|
||||
)
|
||||
},
|
||||
|
||||
|
|
|
@ -79,14 +79,10 @@ export type RawSlots = {
|
|||
* @internal
|
||||
*/
|
||||
_?: SlotFlags
|
||||
/**
|
||||
* cache indexes for slot content
|
||||
* @internal
|
||||
*/
|
||||
__?: number[]
|
||||
}
|
||||
|
||||
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
||||
const isInternalKey = (key: string) =>
|
||||
key === '_' || key === '_ctx' || key === '$stable'
|
||||
|
||||
const normalizeSlotValue = (value: unknown): VNode[] =>
|
||||
isArray(value)
|
||||
|
|
|
@ -24,7 +24,7 @@ import { SchedulerJobFlags } from '../scheduler'
|
|||
|
||||
type Hook<T = () => void> = T | T[]
|
||||
|
||||
const leaveCbKey: unique symbol = Symbol('_leaveCb')
|
||||
export const leaveCbKey: unique symbol = Symbol('_leaveCb')
|
||||
const enterCbKey: unique symbol = Symbol('_enterCb')
|
||||
|
||||
export interface BaseTransitionProps<HostElement = RendererElement> {
|
||||
|
@ -204,7 +204,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
|||
if (
|
||||
oldInnerChild &&
|
||||
oldInnerChild.type !== Comment &&
|
||||
!isSameVNodeType(innerChild, oldInnerChild) &&
|
||||
!isSameVNodeType(oldInnerChild, innerChild) &&
|
||||
recursiveGetSubtree(instance).type !== Comment
|
||||
) {
|
||||
let leavingHooks = resolveTransitionHooks(
|
||||
|
|
|
@ -235,7 +235,7 @@ function patchSuspense(
|
|||
const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense
|
||||
if (pendingBranch) {
|
||||
suspense.pendingBranch = newBranch
|
||||
if (isSameVNodeType(newBranch, pendingBranch)) {
|
||||
if (isSameVNodeType(pendingBranch, newBranch)) {
|
||||
// same root type but content may have changed.
|
||||
patch(
|
||||
pendingBranch,
|
||||
|
@ -321,7 +321,7 @@ function patchSuspense(
|
|||
)
|
||||
setActiveBranch(suspense, newFallback)
|
||||
}
|
||||
} else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
|
||||
} else if (activeBranch && isSameVNodeType(activeBranch, newBranch)) {
|
||||
// toggled "back" to current active branch
|
||||
patch(
|
||||
activeBranch,
|
||||
|
@ -355,7 +355,7 @@ function patchSuspense(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
|
||||
if (activeBranch && isSameVNodeType(activeBranch, newBranch)) {
|
||||
// root did not change, just normal patch
|
||||
patch(
|
||||
activeBranch,
|
||||
|
|
|
@ -406,18 +406,12 @@ function hydrateTeleport(
|
|||
optimized: boolean,
|
||||
) => Node | null,
|
||||
): Node | null {
|
||||
const target = (vnode.target = resolveTarget<Element>(
|
||||
vnode.props,
|
||||
querySelector,
|
||||
))
|
||||
if (target) {
|
||||
const disabled = isTeleportDisabled(vnode.props)
|
||||
// if multiple teleports rendered to the same target element, we need to
|
||||
// pick up from where the last teleport finished instead of the first node
|
||||
const targetNode =
|
||||
(target as TeleportTargetElement)._lpa || target.firstChild
|
||||
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||
if (disabled) {
|
||||
function hydrateDisabledTeleport(
|
||||
node: Node,
|
||||
vnode: VNode,
|
||||
targetStart: Node | null,
|
||||
targetAnchor: Node | null,
|
||||
) {
|
||||
vnode.anchor = hydrateChildren(
|
||||
nextSibling(node),
|
||||
vnode,
|
||||
|
@ -427,8 +421,28 @@ function hydrateTeleport(
|
|||
slotScopeIds,
|
||||
optimized,
|
||||
)
|
||||
vnode.targetStart = targetNode
|
||||
vnode.targetAnchor = targetNode && nextSibling(targetNode)
|
||||
vnode.targetStart = targetStart
|
||||
vnode.targetAnchor = targetAnchor
|
||||
}
|
||||
|
||||
const target = (vnode.target = resolveTarget<Element>(
|
||||
vnode.props,
|
||||
querySelector,
|
||||
))
|
||||
const disabled = isTeleportDisabled(vnode.props)
|
||||
if (target) {
|
||||
// if multiple teleports rendered to the same target element, we need to
|
||||
// pick up from where the last teleport finished instead of the first node
|
||||
const targetNode =
|
||||
(target as TeleportTargetElement)._lpa || target.firstChild
|
||||
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||
if (disabled) {
|
||||
hydrateDisabledTeleport(
|
||||
node,
|
||||
vnode,
|
||||
targetNode,
|
||||
targetNode && nextSibling(targetNode),
|
||||
)
|
||||
} else {
|
||||
vnode.anchor = nextSibling(node)
|
||||
|
||||
|
@ -470,6 +484,10 @@ function hydrateTeleport(
|
|||
}
|
||||
}
|
||||
updateCssVars(vnode, disabled)
|
||||
} else if (disabled) {
|
||||
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||
hydrateDisabledTeleport(node, vnode, node, nextSibling(node))
|
||||
}
|
||||
}
|
||||
return vnode.anchor && nextSibling(vnode.anchor as Node)
|
||||
}
|
||||
|
|
|
@ -125,7 +125,7 @@ export const devtoolsComponentRemoved = (
|
|||
|
||||
type DevtoolsComponentHook = (component: ComponentInternalInstance) => void
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
function createDevtoolsComponentHook(
|
||||
hook: DevtoolsHooks,
|
||||
): DevtoolsComponentHook {
|
||||
|
|
|
@ -7,6 +7,7 @@ import {
|
|||
type VNodeProps,
|
||||
createVNode,
|
||||
isVNode,
|
||||
setBlockTracking,
|
||||
} from './vnode'
|
||||
import type { Teleport, TeleportProps } from './components/Teleport'
|
||||
import type { Suspense, SuspenseProps } from './components/Suspense'
|
||||
|
@ -201,6 +202,9 @@ export function h<P>(
|
|||
|
||||
// Actual implementation
|
||||
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
|
||||
try {
|
||||
// #6913 disable tracking block in h function
|
||||
setBlockTracking(-1)
|
||||
const l = arguments.length
|
||||
if (l === 2) {
|
||||
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
|
||||
|
@ -222,4 +226,7 @@ export function h(type: any, propsOrChildren?: any, children?: any): VNode {
|
|||
}
|
||||
return createVNode(type, propsOrChildren, children)
|
||||
}
|
||||
} finally {
|
||||
setBlockTracking(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
type InternalRenderFunction,
|
||||
isClassComponent,
|
||||
} from './component'
|
||||
import { queueJob, queuePostFlushCb } from './scheduler'
|
||||
import { SchedulerJobFlags, queueJob, queuePostFlushCb } from './scheduler'
|
||||
import { extend, getGlobalThis } from '@vue/shared'
|
||||
|
||||
type HMRComponent = ComponentOptions | ClassComponent
|
||||
|
@ -31,11 +31,17 @@ export interface HMRRuntime {
|
|||
// Note: for a component to be eligible for HMR it also needs the __hmrId option
|
||||
// to be set so that its instances can be registered / removed.
|
||||
if (__DEV__) {
|
||||
getGlobalThis().__VUE_HMR_RUNTIME__ = {
|
||||
const g = getGlobalThis()
|
||||
// vite-plugin-vue/issues/644, #13202
|
||||
// custom-element libraries bundle Vue to simplify usage outside Vue projects but
|
||||
// it overwrite __VUE_HMR_RUNTIME__, causing HMR to break.
|
||||
if (!g.__VUE_HMR_RUNTIME__) {
|
||||
g.__VUE_HMR_RUNTIME__ = {
|
||||
createRecord: tryWrap(createRecord),
|
||||
rerender: tryWrap(rerender),
|
||||
reload: tryWrap(reload),
|
||||
} as HMRRuntime
|
||||
}
|
||||
}
|
||||
|
||||
const map: Map<
|
||||
|
@ -96,7 +102,10 @@ function rerender(id: string, newRender?: Function): void {
|
|||
instance.renderCache = []
|
||||
// this flag forces child components with slot content to update
|
||||
isHmrUpdating = true
|
||||
// #13771 don't update if the job is already disposed
|
||||
if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
|
||||
instance.update()
|
||||
}
|
||||
isHmrUpdating = false
|
||||
})
|
||||
}
|
||||
|
@ -144,11 +153,15 @@ function reload(id: string, newComp: HMRComponent): void {
|
|||
// components to be unmounted and re-mounted. Queue the update so that we
|
||||
// don't end up forcing the same parent to re-render multiple times.
|
||||
queueJob(() => {
|
||||
// vite-plugin-vue/issues/599
|
||||
// don't update if the job is already disposed
|
||||
if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
|
||||
isHmrUpdating = true
|
||||
instance.parent!.update()
|
||||
isHmrUpdating = false
|
||||
// #6930, #11248 avoid infinite recursion
|
||||
dirtyInstances.delete(instance)
|
||||
}
|
||||
})
|
||||
} else if (instance.appContext.reload) {
|
||||
// root instance mounted via createApp() has a reload method
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
isReservedProp,
|
||||
isString,
|
||||
normalizeClass,
|
||||
normalizeCssVarValue,
|
||||
normalizeStyle,
|
||||
stringifyStyle,
|
||||
} from '@vue/shared'
|
||||
|
@ -945,10 +946,8 @@ function resolveCssVars(
|
|||
) {
|
||||
const cssVars = instance.getCssVars()
|
||||
for (const key in cssVars) {
|
||||
expectedMap.set(
|
||||
`--${getEscapedCssVarName(key, false)}`,
|
||||
String(cssVars[key]),
|
||||
)
|
||||
const value = normalizeCssVarValue(cssVars[key])
|
||||
expectedMap.set(`--${getEscapedCssVarName(key, false)}`, value)
|
||||
}
|
||||
}
|
||||
if (vnode === root && instance.parent) {
|
||||
|
|
|
@ -28,12 +28,10 @@ export function endMeasure(
|
|||
if (instance.appContext.config.performance && isSupported()) {
|
||||
const startTag = `vue-${type}-${instance.uid}`
|
||||
const endTag = startTag + `:end`
|
||||
const measureName = `<${formatComponentName(instance, instance.type)}> ${type}`
|
||||
perf.mark(endTag)
|
||||
perf.measure(
|
||||
`<${formatComponentName(instance, instance.type)}> ${type}`,
|
||||
startTag,
|
||||
endTag,
|
||||
)
|
||||
perf.measure(measureName, startTag, endTag)
|
||||
perf.clearMeasures(measureName)
|
||||
perf.clearMarks(startTag)
|
||||
perf.clearMarks(endTag)
|
||||
}
|
||||
|
|
|
@ -85,7 +85,7 @@ import { initFeatureFlags } from './featureFlags'
|
|||
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||
import { isCompatEnabled } from './compat/compatConfig'
|
||||
import { DeprecationTypes } from './compat/compatConfig'
|
||||
import type { TransitionHooks } from './components/BaseTransition'
|
||||
import { type TransitionHooks, leaveCbKey } from './components/BaseTransition'
|
||||
import type { VueElement } from '@vue/runtime-dom'
|
||||
|
||||
export interface Renderer<HostElement = RendererElement> {
|
||||
|
@ -1226,6 +1226,7 @@ function baseCreateRenderer(
|
|||
if (!initialVNode.el) {
|
||||
const placeholder = (instance.subTree = createVNode(Comment))
|
||||
processCommentNode(null, placeholder, container!, anchor)
|
||||
initialVNode.placeholder = placeholder.el
|
||||
}
|
||||
} else {
|
||||
setupRenderEffect(
|
||||
|
@ -1979,8 +1980,12 @@ function baseCreateRenderer(
|
|||
for (i = toBePatched - 1; i >= 0; i--) {
|
||||
const nextIndex = s2 + i
|
||||
const nextChild = c2[nextIndex] as VNode
|
||||
const anchorVNode = c2[nextIndex + 1] as VNode
|
||||
const anchor =
|
||||
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
|
||||
nextIndex + 1 < l2
|
||||
? // #13559, fallback to el placeholder for unresolved async component
|
||||
anchorVNode.el || anchorVNode.placeholder
|
||||
: parentAnchor
|
||||
if (newIndexToOldIndexMap[i] === 0) {
|
||||
// mount new
|
||||
patch(
|
||||
|
@ -2065,6 +2070,12 @@ function baseCreateRenderer(
|
|||
}
|
||||
}
|
||||
const performLeave = () => {
|
||||
// #13153 move kept-alive node before v-show transition leave finishes
|
||||
// it needs to call the leaving callback to ensure element's `display`
|
||||
// is `none`
|
||||
if (el!._isLeaving) {
|
||||
el
|
||||
}
|
||||
leave(el!, () => {
|
||||
remove()
|
||||
afterLeave && afterLeave()
|
||||
|
@ -2272,17 +2283,7 @@ function baseCreateRenderer(
|
|||
unregisterHMR(instance)
|
||||
}
|
||||
|
||||
const {
|
||||
bum,
|
||||
scope,
|
||||
job,
|
||||
subTree,
|
||||
um,
|
||||
m,
|
||||
a,
|
||||
parent,
|
||||
slots: { __: slotCacheKeys },
|
||||
} = instance
|
||||
const { bum, scope, job, subTree, um, m, a } = instance
|
||||
invalidateMount(m)
|
||||
invalidateMount(a)
|
||||
|
||||
|
@ -2291,13 +2292,6 @@ 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)
|
||||
|
@ -2332,24 +2326,6 @@ function baseCreateRenderer(
|
|||
instance.isUnmounted = true
|
||||
}, parentSuspense)
|
||||
|
||||
// A component with async dep inside a pending suspense is unmounted before
|
||||
// its async dep resolves. This should remove the dep from the suspense, and
|
||||
// cause the suspense to resolve immediately if that was the last dep.
|
||||
if (
|
||||
__FEATURE_SUSPENSE__ &&
|
||||
parentSuspense &&
|
||||
parentSuspense.pendingBranch &&
|
||||
!parentSuspense.isUnmounted &&
|
||||
instance.asyncDep &&
|
||||
!instance.asyncResolved &&
|
||||
instance.suspenseId === parentSuspense.pendingId
|
||||
) {
|
||||
parentSuspense.deps--
|
||||
if (parentSuspense.deps === 0) {
|
||||
parentSuspense.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||
devtoolsComponentRemoved(instance)
|
||||
}
|
||||
|
@ -2508,7 +2484,11 @@ export function traverseStaticChildren(
|
|||
traverseStaticChildren(c1, c2)
|
||||
}
|
||||
// #6852 also inherit for text nodes
|
||||
if (c2.type === Text) {
|
||||
if (
|
||||
c2.type === Text &&
|
||||
// avoid cached text nodes retaining detached dom nodes
|
||||
c2.patchFlag !== PatchFlags.CACHED
|
||||
) {
|
||||
c2.el = c1.el
|
||||
}
|
||||
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
import type { SuspenseBoundary } from './components/Suspense'
|
||||
import type { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode'
|
||||
import type {
|
||||
VNode,
|
||||
VNodeNormalizedRef,
|
||||
VNodeNormalizedRefAtom,
|
||||
VNodeRef,
|
||||
} from './vnode'
|
||||
import {
|
||||
EMPTY_OBJ,
|
||||
NO,
|
||||
ShapeFlags,
|
||||
hasOwn,
|
||||
isArray,
|
||||
|
@ -13,11 +19,12 @@ import { isAsyncWrapper } from './apiAsyncComponent'
|
|||
import { warn } from './warning'
|
||||
import { isRef, toRaw } from '@vue/reactivity'
|
||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||
import type { SchedulerJob } from './scheduler'
|
||||
import { type SchedulerJob, SchedulerJobFlags } from './scheduler'
|
||||
import { queuePostRenderEffect } from './renderer'
|
||||
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
||||
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
||||
|
||||
const pendingSetRefMap = new WeakMap<VNodeNormalizedRef, SchedulerJob>()
|
||||
/**
|
||||
* Function for handling a template ref
|
||||
*/
|
||||
|
@ -77,7 +84,7 @@ export function setRef(
|
|||
const rawSetupState = toRaw(setupState)
|
||||
const canSetSetupRef =
|
||||
setupState === EMPTY_OBJ
|
||||
? () => false
|
||||
? NO
|
||||
: (key: string) => {
|
||||
if (__DEV__) {
|
||||
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
|
||||
|
@ -94,16 +101,27 @@ export function setRef(
|
|||
return hasOwn(rawSetupState, key)
|
||||
}
|
||||
|
||||
const canSetRef = (ref: VNodeRef) => {
|
||||
return !__DEV__ || !knownTemplateRefs.has(ref as any)
|
||||
}
|
||||
|
||||
// dynamic ref changed. unset old ref
|
||||
if (oldRef != null && oldRef !== ref) {
|
||||
invalidatePendingSetRef(oldRawRef!)
|
||||
if (isString(oldRef)) {
|
||||
refs[oldRef] = null
|
||||
if (canSetSetupRef(oldRef)) {
|
||||
setupState[oldRef] = null
|
||||
}
|
||||
} else if (isRef(oldRef)) {
|
||||
if (canSetRef(oldRef)) {
|
||||
oldRef.value = null
|
||||
}
|
||||
|
||||
// this type assertion is valid since `oldRef` has already been asserted to be non-null
|
||||
const oldRawRefAtom = oldRawRef as VNodeNormalizedRefAtom
|
||||
if (oldRawRefAtom.k) refs[oldRawRefAtom.k] = null
|
||||
}
|
||||
}
|
||||
|
||||
if (isFunction(ref)) {
|
||||
|
@ -119,7 +137,9 @@ export function setRef(
|
|||
? canSetSetupRef(ref)
|
||||
? setupState[ref]
|
||||
: refs[ref]
|
||||
: ref.value
|
||||
: canSetRef(ref) || !rawRef.k
|
||||
? ref.value
|
||||
: refs[rawRef.k]
|
||||
if (isUnmount) {
|
||||
isArray(existing) && remove(existing, refValue)
|
||||
} else {
|
||||
|
@ -130,8 +150,11 @@ export function setRef(
|
|||
setupState[ref] = refs[ref]
|
||||
}
|
||||
} else {
|
||||
ref.value = [refValue]
|
||||
if (rawRef.k) refs[rawRef.k] = ref.value
|
||||
const newVal = [refValue]
|
||||
if (canSetRef(ref)) {
|
||||
ref.value = newVal
|
||||
}
|
||||
if (rawRef.k) refs[rawRef.k] = newVal
|
||||
}
|
||||
} else if (!existing.includes(refValue)) {
|
||||
existing.push(refValue)
|
||||
|
@ -143,7 +166,9 @@ export function setRef(
|
|||
setupState[ref] = value
|
||||
}
|
||||
} else if (_isRef) {
|
||||
if (canSetRef(ref)) {
|
||||
ref.value = value
|
||||
}
|
||||
if (rawRef.k) refs[rawRef.k] = value
|
||||
} else if (__DEV__) {
|
||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||
|
@ -153,9 +178,15 @@ export function setRef(
|
|||
// #1789: for non-null values, set them after render
|
||||
// null values means this is unmount and it should not overwrite another
|
||||
// ref with the same key
|
||||
;(doSet as SchedulerJob).id = -1
|
||||
queuePostRenderEffect(doSet, parentSuspense)
|
||||
const job: SchedulerJob = () => {
|
||||
doSet()
|
||||
pendingSetRefMap.delete(rawRef)
|
||||
}
|
||||
job.id = -1
|
||||
pendingSetRefMap.set(rawRef, job)
|
||||
queuePostRenderEffect(job, parentSuspense)
|
||||
} else {
|
||||
invalidatePendingSetRef(rawRef)
|
||||
doSet()
|
||||
}
|
||||
} else if (__DEV__) {
|
||||
|
@ -163,3 +194,11 @@ export function setRef(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function invalidatePendingSetRef(rawRef: VNodeNormalizedRef) {
|
||||
const pendingSetRef = pendingSetRefMap.get(rawRef)
|
||||
if (pendingSetRef) {
|
||||
pendingSetRef.flags! |= SchedulerJobFlags.DISPOSED
|
||||
pendingSetRefMap.delete(rawRef)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,10 +53,15 @@ let currentFlushPromise: Promise<void> | null = null
|
|||
const RECURSION_LIMIT = 100
|
||||
type CountMap = Map<SchedulerJob, number>
|
||||
|
||||
export function nextTick<T = void, R = void>(
|
||||
export function nextTick(): Promise<void>
|
||||
export function nextTick<T, R>(
|
||||
this: T,
|
||||
fn?: (this: T) => R,
|
||||
): Promise<Awaited<R>> {
|
||||
fn: (this: T) => R | Promise<R>,
|
||||
): Promise<R>
|
||||
export function nextTick<T, R>(
|
||||
this: T,
|
||||
fn?: (this: T) => R | Promise<R>,
|
||||
): Promise<void | R> {
|
||||
const p = currentFlushPromise || resolvedPromise
|
||||
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
||||
}
|
||||
|
|
|
@ -196,6 +196,7 @@ export interface VNode<
|
|||
|
||||
// DOM
|
||||
el: HostNode | null
|
||||
placeholder: HostNode | null // async component el placeholder
|
||||
anchor: HostNode | null // fragment anchor
|
||||
target: HostElement | null // teleport target
|
||||
targetStart: HostNode | null // teleport target start anchor
|
||||
|
@ -711,6 +712,8 @@ export function cloneVNode<T, U>(
|
|||
suspense: vnode.suspense,
|
||||
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
|
||||
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
|
||||
placeholder: vnode.placeholder,
|
||||
|
||||
el: vnode.el,
|
||||
anchor: vnode.anchor,
|
||||
ctx: vnode.ctx,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue