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:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
@ -31,4 +31,4 @@ jobs:
|
||||||
- name: Run prettier
|
- name: Run prettier
|
||||||
run: pnpm run format
|
run: pnpm run format
|
||||||
|
|
||||||
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
- uses: autofix-ci/action@635ffb0c9798bd160680f18fd73371e355b85f27
|
||||||
|
|
|
@ -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
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
|
|
@ -21,7 +21,7 @@ jobs:
|
||||||
environment: Release
|
environment: Release
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4
|
uses: pnpm/action-setup@v4
|
||||||
|
@ -36,12 +36,13 @@ jobs:
|
||||||
- name: Install deps
|
- name: Install deps
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
|
- name: Update npm
|
||||||
|
run: npm i -g npm@latest
|
||||||
|
|
||||||
- name: Build and publish
|
- name: Build and publish
|
||||||
id: publish
|
id: publish
|
||||||
run: |
|
run: |
|
||||||
pnpm release --publishOnly
|
pnpm release --publishOnly
|
||||||
env:
|
|
||||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
||||||
|
|
||||||
- name: Create GitHub release
|
- name: Create GitHub release
|
||||||
id: release_tag
|
id: release_tag
|
||||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
|
@ -22,7 +22,7 @@ jobs:
|
||||||
github.event.workflow_run.event == 'pull_request' &&
|
github.event.workflow_run.event == 'pull_request' &&
|
||||||
github.event.workflow_run.conclusion == 'success'
|
github.event.workflow_run.conclusion == 'success'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download Size Data
|
- name: Download Size Data
|
||||||
uses: dawidd6/action-download-artifact@v9
|
uses: dawidd6/action-download-artifact@v11
|
||||||
with:
|
with:
|
||||||
name: size-data
|
name: size-data
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
|
@ -56,7 +56,7 @@ jobs:
|
||||||
path: temp/size/base.txt
|
path: temp/size/base.txt
|
||||||
|
|
||||||
- name: Download Previous Size Data
|
- name: Download Previous Size Data
|
||||||
uses: dawidd6/action-download-artifact@v9
|
uses: dawidd6/action-download-artifact@v11
|
||||||
with:
|
with:
|
||||||
branch: ${{ steps.pr-base.outputs.content }}
|
branch: ${{ steps.pr-base.outputs.content }}
|
||||||
workflow: size-data.yml
|
workflow: size-data.yml
|
||||||
|
|
|
@ -11,7 +11,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
@ -32,7 +32,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
@ -54,7 +54,7 @@ jobs:
|
||||||
e2e-test:
|
e2e-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Setup cache for Chromium binary
|
- name: Setup cache for Chromium binary
|
||||||
uses: actions/cache@v4
|
uses: actions/cache@v4
|
||||||
|
@ -85,7 +85,7 @@ jobs:
|
||||||
env:
|
env:
|
||||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v5
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.1.0
|
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)
|
## [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-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:** 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)
|
* **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)
|
* **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)
|
* **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,
|
"private": true,
|
||||||
"version": "3.5.16",
|
"version": "3.5.21",
|
||||||
"packageManager": "pnpm@10.11.1",
|
"packageManager": "pnpm@10.15.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -17,11 +17,11 @@
|
||||||
"format": "prettier --write --cache .",
|
"format": "prettier --write --cache .",
|
||||||
"format-check": "prettier --check --cache .",
|
"format-check": "prettier --check --cache .",
|
||||||
"test": "vitest",
|
"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-e2e": "node scripts/build.js vue -f global -d && vitest --project e2e",
|
||||||
"test-dts": "run-s build-dts test-dts-only",
|
"test-dts": "run-s build-dts test-dts-only",
|
||||||
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
||||||
"test-coverage": "vitest run --project unit --coverage",
|
"test-coverage": "vitest run --project unit* --coverage",
|
||||||
"prebench": "node scripts/build.js -pf esm-browser reactivity",
|
"prebench": "node scripts/build.js -pf esm-browser reactivity",
|
||||||
"prebench-compare": "node scripts/build.js -pf esm-browser reactivity",
|
"prebench-compare": "node scripts/build.js -pf esm-browser reactivity",
|
||||||
"bench": "vitest bench --project=unit --outputJson=temp/bench.json",
|
"bench": "vitest bench --project=unit --outputJson=temp/bench.json",
|
||||||
|
@ -65,21 +65,21 @@
|
||||||
"@babel/parser": "catalog:",
|
"@babel/parser": "catalog:",
|
||||||
"@babel/types": "catalog:",
|
"@babel/types": "catalog:",
|
||||||
"@rollup/plugin-alias": "^5.1.1",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-commonjs": "^28.0.3",
|
"@rollup/plugin-commonjs": "^28.0.6",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@swc/core": "^1.11.29",
|
"@swc/core": "^1.13.5",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^22.15.29",
|
"@types/node": "^22.17.2",
|
||||||
"@types/semver": "^7.7.0",
|
"@types/semver": "^7.7.0",
|
||||||
"@types/serve-handler": "^6.1.4",
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitest/coverage-v8": "^3.1.4",
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"@vitest/eslint-plugin": "^1.2.1",
|
"@vitest/eslint-plugin": "^1.3.5",
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^5.0.0",
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.25.5",
|
"esbuild": "^0.25.9",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.27.0",
|
"eslint": "^9.27.0",
|
||||||
"eslint-plugin-import-x": "^4.13.1",
|
"eslint-plugin-import-x": "^4.13.1",
|
||||||
|
@ -87,18 +87,18 @@
|
||||||
"jsdom": "^26.1.0",
|
"jsdom": "^26.1.0",
|
||||||
"lint-staged": "^16.0.0",
|
"lint-staged": "^16.0.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.18",
|
||||||
"markdown-table": "^3.0.4",
|
"markdown-table": "^3.0.4",
|
||||||
"marked": "13.0.3",
|
"marked": "13.0.3",
|
||||||
"npm-run-all2": "^7.0.2",
|
"npm-run-all2": "^8.0.4",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~24.9.0",
|
"puppeteer": "~24.17.1",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.41.1",
|
"rollup": "^4.50.0",
|
||||||
"rollup-plugin-dts": "^6.2.1",
|
"rollup-plugin-dts": "^6.2.3",
|
||||||
"rollup-plugin-esbuild": "^6.2.1",
|
"rollup-plugin-esbuild": "^6.2.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
"semver": "^7.7.2",
|
"semver": "^7.7.2",
|
||||||
|
@ -110,6 +110,6 @@
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.32.1",
|
"typescript-eslint": "^8.32.1",
|
||||||
"vite": "catalog:",
|
"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:"
|
"vite": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^4.5.1",
|
"@vue/repl": "^4.7.0",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
import Header from './Header.vue'
|
import Header from './Header.vue'
|
||||||
import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
|
import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
|
||||||
import Monaco from '@vue/repl/monaco-editor'
|
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>>()
|
const replRef = ref<InstanceType<typeof Repl>>()
|
||||||
|
|
||||||
|
@ -115,6 +115,34 @@ onMounted(() => {
|
||||||
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
||||||
window.process = { env: {} }
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -141,20 +169,11 @@ onMounted(() => {
|
||||||
:editorOptions="{ autoSaveText: false }"
|
:editorOptions="{ autoSaveText: false }"
|
||||||
:store="store"
|
:store="store"
|
||||||
:showCompileOutput="true"
|
:showCompileOutput="true"
|
||||||
|
:showSsrOutput="useSSRMode"
|
||||||
|
:showOpenSourceMap="true"
|
||||||
:autoResize="true"
|
:autoResize="true"
|
||||||
:clearConsole="false"
|
:clearConsole="false"
|
||||||
:preview-options="{
|
:preview-options="previewOptions"
|
||||||
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()
|
|
||||||
}`,
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"vue": "latest"
|
"vue": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.4",
|
"@vitejs/plugin-vue": "^6.0.1",
|
||||||
"vite": "^6.3.5"
|
"vite": "^7.1.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"enableNonBrowserBranches": true
|
"enableNonBrowserBranches": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"monaco-editor": "^0.52.2",
|
"monaco-editor": "^0.53.0",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -21,7 +21,7 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("p", null, [
|
_createElementVNode("p", null, [
|
||||||
_createElementVNode("span"),
|
_createElementVNode("span"),
|
||||||
_createElementVNode("span")
|
_createElementVNode("span")
|
||||||
|
@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("span"),
|
_createElementVNode("span"),
|
||||||
_createElementVNode("span")
|
_createElementVNode("span")
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -42,11 +42,11 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
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, [
|
_createElementVNode("div", null, [
|
||||||
_createCommentVNode("comment")
|
_createCommentVNode("comment")
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -58,11 +58,11 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||||
_createTextVNode("foo"),
|
_createTextVNode("foo", -1 /* CACHED */),
|
||||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -74,9 +74,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -147,9 +147,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -161,9 +161,9 @@ return function render(_ctx, _cache) {
|
||||||
with (_ctx) {
|
with (_ctx) {
|
||||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
@ -215,9 +215,9 @@ return function render(_ctx, _cache) {
|
||||||
const _directive_foo = _resolveDirective("foo")
|
const _directive_foo = _resolveDirective("foo")
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
|
_withDirectives((_openBlock(), _createElementBlock("svg", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||||
]))), [
|
]))])), [
|
||||||
[_directive_foo]
|
[_directive_foo]
|
||||||
])
|
])
|
||||||
]))
|
]))
|
||||||
|
@ -401,9 +401,9 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
ok
|
ok
|
||||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
? (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
: _createCommentVNode("v-if", true)
|
: _createCommentVNode("v-if", true)
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
@ -422,7 +422,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||||
_createCommentVNode("comment"),
|
_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: "b" }, [
|
||||||
_createElementVNode("div", { id: "c" }, [
|
_createElementVNode("div", { id: "c" }, [
|
||||||
_createElementVNode("div", { id: "d" }, [
|
_createElementVNode("div", { id: "d" }, [
|
||||||
|
@ -430,7 +430,7 @@ return function render(_ctx, _cache) {
|
||||||
])
|
])
|
||||||
])
|
])
|
||||||
], -1 /* CACHED */)
|
], -1 /* CACHED */)
|
||||||
]))
|
]))])
|
||||||
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
||||||
}
|
}
|
||||||
}"
|
}"
|
||||||
|
@ -448,9 +448,9 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||||
])))
|
]))]))
|
||||||
}), 256 /* UNKEYED_FRAGMENT */))
|
}), 256 /* UNKEYED_FRAGMENT */))
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@ import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
const cachedChildrenArrayMatcher = (
|
const cachedChildrenArrayMatcher = (
|
||||||
tags: string[],
|
tags: string[],
|
||||||
needArraySpread = false,
|
needArraySpread = true,
|
||||||
) => ({
|
) => ({
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
needArraySpread,
|
needArraySpread,
|
||||||
|
@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
{
|
|
||||||
type: NodeTypes.JS_PROPERTY,
|
|
||||||
key: { content: '__' },
|
|
||||||
value: { content: '[0]' },
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ 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', () => {
|
test('error on user key', () => {
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
// dynamic
|
// dynamic
|
||||||
|
|
|
@ -478,7 +478,10 @@ describe('compiler: transform component slots', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
|
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 })
|
const { root } = parseWithSlots(template, { prefixIdentifiers: true })
|
||||||
let flag: any
|
let flag: any
|
||||||
if (root.children[0].type === NodeTypes.FOR) {
|
if (root.children[0].type === NodeTypes.FOR) {
|
||||||
|
@ -491,8 +494,8 @@ describe('compiler: transform component slots', () => {
|
||||||
.children[0] as ComponentNode
|
.children[0] as ComponentNode
|
||||||
flag = (innerComp.codegenNode as VNodeCall).patchFlag
|
flag = (innerComp.codegenNode as VNodeCall).patchFlag
|
||||||
}
|
}
|
||||||
if (shouldForce) {
|
if (expectedPatchFlag) {
|
||||||
expect(flag).toBe(PatchFlags.DYNAMIC_SLOTS)
|
expect(flag).toBe(expectedPatchFlag)
|
||||||
} else {
|
} else {
|
||||||
expect(flag).toBeUndefined()
|
expect(flag).toBeUndefined()
|
||||||
}
|
}
|
||||||
|
@ -502,14 +505,13 @@ describe('compiler: transform component slots', () => {
|
||||||
`<div v-for="i in list">
|
`<div v-for="i in list">
|
||||||
<Comp v-slot="bar">foo</Comp>
|
<Comp v-slot="bar">foo</Comp>
|
||||||
</div>`,
|
</div>`,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assertDynamicSlots(
|
assertDynamicSlots(
|
||||||
`<div v-for="i in list">
|
`<div v-for="i in list">
|
||||||
<Comp v-slot="bar">{{ i }}</Comp>
|
<Comp v-slot="bar">{{ i }}</Comp>
|
||||||
</div>`,
|
</div>`,
|
||||||
true,
|
PatchFlags.DYNAMIC_SLOTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
// reference the component's own slot variable should not force 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="foo">
|
||||||
<Comp v-slot="bar">{{ bar }}</Comp>
|
<Comp v-slot="bar">{{ bar }}</Comp>
|
||||||
</Comp>`,
|
</Comp>`,
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
assertDynamicSlots(
|
assertDynamicSlots(
|
||||||
`<Comp v-slot="foo">
|
`<Comp v-slot="foo">
|
||||||
<Comp v-slot="bar">{{ foo }}</Comp>
|
<Comp v-slot="bar">{{ foo }}</Comp>
|
||||||
</Comp>`,
|
</Comp>`,
|
||||||
true,
|
PatchFlags.DYNAMIC_SLOTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
// #2564
|
// #2564
|
||||||
|
@ -532,14 +533,35 @@ describe('compiler: transform component slots', () => {
|
||||||
`<div v-for="i in list">
|
`<div v-for="i in list">
|
||||||
<Comp v-slot="bar"><button @click="fn(i)" /></Comp>
|
<Comp v-slot="bar"><button @click="fn(i)" /></Comp>
|
||||||
</div>`,
|
</div>`,
|
||||||
true,
|
PatchFlags.DYNAMIC_SLOTS,
|
||||||
)
|
)
|
||||||
|
|
||||||
assertDynamicSlots(
|
assertDynamicSlots(
|
||||||
`<div v-for="i in list">
|
`<div v-for="i in list">
|
||||||
<Comp v-slot="bar"><button @click="fn()" /></Comp>
|
<Comp v-slot="bar"><button @click="fn()" /></Comp>
|
||||||
</div>`,
|
</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 { type Position, createSimpleExpression } from '../src/ast'
|
||||||
import {
|
import {
|
||||||
advancePositionWithClone,
|
advancePositionWithClone,
|
||||||
|
@ -115,3 +120,18 @@ test('toValidAssetId', () => {
|
||||||
'_component_test_2797935797_1',
|
'_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",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.5.16",
|
"version": "3.5.21",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
|
|
@ -122,7 +122,7 @@ export function isReferencedIdentifier(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isReferenced(id, parent)) {
|
if (isReferenced(id, parent, parentStack[parentStack.length - 2])) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,7 +132,8 @@ export function isReferencedIdentifier(
|
||||||
case 'AssignmentExpression':
|
case 'AssignmentExpression':
|
||||||
case 'AssignmentPattern':
|
case 'AssignmentPattern':
|
||||||
return true
|
return true
|
||||||
case 'ObjectPattern':
|
case 'ObjectProperty':
|
||||||
|
return parent.key !== id && isInDestructureAssignment(parent, parentStack)
|
||||||
case 'ArrayPattern':
|
case 'ArrayPattern':
|
||||||
return isInDestructureAssignment(parent, parentStack)
|
return isInDestructureAssignment(parent, parentStack)
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ import {
|
||||||
isCoreComponent,
|
isCoreComponent,
|
||||||
isSimpleIdentifier,
|
isSimpleIdentifier,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
|
isVPre,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
import { decodeHTML } from 'entities/lib/decode.js'
|
import { decodeHTML } from 'entities/lib/decode.js'
|
||||||
import {
|
import {
|
||||||
|
@ -246,7 +247,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
ondirarg(start, end) {
|
ondirarg(start, end) {
|
||||||
if (start === end) return
|
if (start === end) return
|
||||||
const arg = getSlice(start, end)
|
const arg = getSlice(start, end)
|
||||||
if (inVPre) {
|
if (inVPre && !isVPre(currentProp!)) {
|
||||||
;(currentProp as AttributeNode).name += arg
|
;(currentProp as AttributeNode).name += arg
|
||||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
} else {
|
} else {
|
||||||
|
@ -262,7 +263,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
|
|
||||||
ondirmodifier(start, end) {
|
ondirmodifier(start, end) {
|
||||||
const mod = getSlice(start, end)
|
const mod = getSlice(start, end)
|
||||||
if (inVPre) {
|
if (inVPre && !isVPre(currentProp!)) {
|
||||||
;(currentProp as AttributeNode).name += '.' + mod
|
;(currentProp as AttributeNode).name += '.' + mod
|
||||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||||
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
||||||
|
|
|
@ -12,19 +12,22 @@ import {
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
type SlotFunctionExpression,
|
type SlotFunctionExpression,
|
||||||
type SlotsObjectProperty,
|
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
type TemplateNode,
|
type TemplateNode,
|
||||||
type TextCallNode,
|
type TextCallNode,
|
||||||
type VNodeCall,
|
type VNodeCall,
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
createObjectProperty,
|
|
||||||
createSimpleExpression,
|
|
||||||
getVNodeBlockHelper,
|
getVNodeBlockHelper,
|
||||||
getVNodeHelper,
|
getVNodeHelper,
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
import type { TransformContext } from '../transform'
|
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 { findDir, isSlotOutlet } from '../utils'
|
||||||
import {
|
import {
|
||||||
GUARD_REACTIVE_PROPS,
|
GUARD_REACTIVE_PROPS,
|
||||||
|
@ -109,6 +112,15 @@ function walk(
|
||||||
? ConstantTypes.NOT_CONSTANT
|
? ConstantTypes.NOT_CONSTANT
|
||||||
: getConstantType(child, context)
|
: getConstantType(child, context)
|
||||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
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)
|
toCache.push(child)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -142,7 +154,6 @@ function walk(
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedAsArray = false
|
let cachedAsArray = false
|
||||||
const slotCacheKeys = []
|
|
||||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||||
if (
|
if (
|
||||||
node.tagType === ElementTypes.ELEMENT &&
|
node.tagType === ElementTypes.ELEMENT &&
|
||||||
|
@ -166,7 +177,6 @@ function walk(
|
||||||
// default slot
|
// default slot
|
||||||
const slot = getSlotNode(node.codegenNode, 'default')
|
const slot = getSlotNode(node.codegenNode, 'default')
|
||||||
if (slot) {
|
if (slot) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -190,7 +200,6 @@ function walk(
|
||||||
slotName.arg &&
|
slotName.arg &&
|
||||||
getSlotNode(parent.codegenNode, slotName.arg)
|
getSlotNode(parent.codegenNode, slotName.arg)
|
||||||
if (slot) {
|
if (slot) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -201,39 +210,22 @@ function walk(
|
||||||
|
|
||||||
if (!cachedAsArray) {
|
if (!cachedAsArray) {
|
||||||
for (const child of toCache) {
|
for (const child of toCache) {
|
||||||
slotCacheKeys.push(context.cached.length)
|
|
||||||
child.codegenNode = context.cache(child.codegenNode!)
|
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 {
|
function getCacheExpression(value: JSChildNode): CacheExpression {
|
||||||
const exp = context.cache(value)
|
const exp = context.cache(value)
|
||||||
// #6978, #7138, #7114
|
// #6978, #7138, #7114
|
||||||
// a cached children array inside v-for can caused HMR errors since
|
// a cached children array inside v-for can caused HMR errors since
|
||||||
// it might be mutated when mounting the first item
|
// it might be mutated when mounting the first item
|
||||||
if (inFor && context.hmr) {
|
// #13221
|
||||||
exp.needArraySpread = true
|
// 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
|
return exp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
arg.children.unshift(`(`)
|
arg.children.unshift(`(`)
|
||||||
arg.children.push(`) || ""`)
|
arg.children.push(`) || ""`)
|
||||||
} else if (!arg.isStatic) {
|
} else if (!arg.isStatic) {
|
||||||
arg.content = `${arg.content} || ""`
|
arg.content = arg.content ? `${arg.content} || ""` : `""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// .sync is replaced by v-model:arg
|
// .sync is replaced by v-model:arg
|
||||||
|
|
|
@ -36,7 +36,7 @@ import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
||||||
import { PatchFlags } from '@vue/shared'
|
import { PatchFlags } from '@vue/shared'
|
||||||
|
|
||||||
export const transformIf: NodeTransform = createStructuralDirectiveTransform(
|
export const transformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||||
/^(if|else|else-if)$/,
|
/^(?:if|else|else-if)$/,
|
||||||
(node, dir, context) => {
|
(node, dir, context) => {
|
||||||
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
|
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
|
||||||
// #1587: We need to dynamically increment the key based on the current
|
// #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) {
|
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 (
|
if (
|
||||||
dir.name === 'else-if' &&
|
(dir.name === 'else-if' || dir.name === 'else') &&
|
||||||
sibling.branches[sibling.branches.length - 1].condition === undefined
|
sibling.branches[sibling.branches.length - 1].condition === undefined
|
||||||
) {
|
) {
|
||||||
context.onError(
|
context.onError(
|
||||||
|
|
|
@ -16,7 +16,7 @@ const seen = new WeakSet()
|
||||||
export const transformMemo: NodeTransform = (node, context) => {
|
export const transformMemo: NodeTransform = (node, context) => {
|
||||||
if (node.type === NodeTypes.ELEMENT) {
|
if (node.type === NodeTypes.ELEMENT) {
|
||||||
const dir = findDir(node, 'memo')
|
const dir = findDir(node, 'memo')
|
||||||
if (!dir || seen.has(node)) {
|
if (!dir || seen.has(node) || context.inSSR) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
seen.add(node)
|
seen.add(node)
|
||||||
|
|
|
@ -131,9 +131,17 @@ export function buildSlots(
|
||||||
// since it likely uses a scope variable.
|
// since it likely uses a scope variable.
|
||||||
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
|
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
|
||||||
// with `prefixIdentifiers: true`, this can be further optimized to make
|
// 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) {
|
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.
|
// 1. Check for slot with slotProps on component itself.
|
||||||
|
@ -215,7 +223,7 @@ export function buildSlots(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
} else if (
|
} else if (
|
||||||
(vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
|
(vElse = findDir(slotElement, /^else(?:-if)?$/, true /* allowEmpty */))
|
||||||
) {
|
) {
|
||||||
// find adjacent v-if
|
// find adjacent v-if
|
||||||
let j = i
|
let j = i
|
||||||
|
@ -226,7 +234,7 @@ export function buildSlots(
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (prev && isTemplateNode(prev) && findDir(prev, /^(else-)?if$/)) {
|
if (prev && isTemplateNode(prev) && findDir(prev, /^(?:else-)?if$/)) {
|
||||||
__TEST__ && assert(dynamicSlots.length > 0)
|
__TEST__ && assert(dynamicSlots.length > 0)
|
||||||
// attach this slot to previous conditional
|
// attach this slot to previous conditional
|
||||||
let conditional = dynamicSlots[
|
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 =>
|
export const isSimpleIdentifier = (name: string): boolean =>
|
||||||
!nonIdentifierRE.test(name)
|
!nonIdentifierRE.test(name)
|
||||||
|
|
||||||
|
@ -189,7 +189,7 @@ export const isMemberExpression: (
|
||||||
) => boolean = __BROWSER__ ? isMemberExpressionBrowser : isMemberExpressionNode
|
) => boolean = __BROWSER__ ? isMemberExpressionBrowser : isMemberExpressionNode
|
||||||
|
|
||||||
const fnExpRE =
|
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 =>
|
export const isFnExpressionBrowser: (exp: ExpressionNode) => boolean = exp =>
|
||||||
fnExpRE.test(getExpSource(exp))
|
fnExpRE.test(getExpSource(exp))
|
||||||
|
@ -343,6 +343,10 @@ export function isText(
|
||||||
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
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 {
|
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
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
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
||||||
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
||||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
||||||
])))
|
]))]))
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
@ -16,9 +16,9 @@ exports[`stringify static html > escape 1`] = `
|
||||||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<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)
|
_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
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<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)
|
_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
|
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<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)
|
_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
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("select", null, [
|
_createElementVNode("select", null, [
|
||||||
_createElementVNode("option", { value: null }),
|
_createElementVNode("option", { value: null }),
|
||||||
_createElementVNode("option", { value: "1" }),
|
_createElementVNode("option", { value: "1" }),
|
||||||
|
@ -55,7 +55,7 @@ return function render(_ctx, _cache) {
|
||||||
_createElementVNode("option", { value: "1" }),
|
_createElementVNode("option", { value: "1" }),
|
||||||
_createElementVNode("option", { value: "1" })
|
_createElementVNode("option", { value: "1" })
|
||||||
], -1 /* CACHED */)
|
], -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
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("select", null, [
|
_createElementVNode("select", null, [
|
||||||
_createElementVNode("option", { value: 1 }),
|
_createElementVNode("option", { value: 1 }),
|
||||||
_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 }),
|
||||||
_createElementVNode("option", { value: 1 })
|
_createElementVNode("option", { value: 1 })
|
||||||
], -1 /* CACHED */)
|
], -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
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createElementVNode("div", null, [
|
_createElementVNode("div", null, [
|
||||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||||
_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("span", { class: "foo" }, "foo"),
|
||||||
_createElementVNode("img", { src: _imports_0_ })
|
_createElementVNode("img", { src: _imports_0_ })
|
||||||
], -1 /* CACHED */)
|
], -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
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
_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
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
_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
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<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)
|
_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
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
_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
|
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
return function render(_ctx, _cache) {
|
return function render(_ctx, _cache) {
|
||||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||||
_createStaticVNode("<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)
|
_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",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.5.16",
|
"version": "3.5.21",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
@ -42,7 +42,7 @@ if (__TEST__) {
|
||||||
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`DOMErrorCodes need to be updated to ${
|
`DOMErrorCodes need to be updated to ${
|
||||||
ErrorCodes.__EXTEND_POINT__ + 1
|
ErrorCodes.__EXTEND_POINT__
|
||||||
} to match extension point from core ErrorCodes.`,
|
} 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) => {
|
const isStringifiableAttr = (name: string, ns: Namespaces) => {
|
||||||
return (
|
return (
|
||||||
(ns === Namespaces.HTML
|
(ns === Namespaces.HTML
|
||||||
|
|
|
@ -884,9 +884,9 @@ export default {
|
||||||
|
|
||||||
return (_ctx, _push, _parent, _attrs) => {
|
return (_ctx, _push, _parent, _attrs) => {
|
||||||
const _cssVars = { style: {
|
const _cssVars = { style: {
|
||||||
"--xxxxxxxx-count": (count.value),
|
":--xxxxxxxx-count": (count.value),
|
||||||
"--xxxxxxxx-style\\\\.color": (style.color),
|
":--xxxxxxxx-style\\\\.color": (style.color),
|
||||||
"--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
":--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
||||||
}}
|
}}
|
||||||
_push(\`<!--[--><div\${
|
_push(\`<!--[--><div\${
|
||||||
_ssrRenderAttrs(_cssVars)
|
_ssrRenderAttrs(_cssVars)
|
||||||
|
|
|
@ -81,9 +81,9 @@ import _imports_1 from '/bar.png'
|
||||||
|
|
||||||
|
|
||||||
export function render(_ctx, _cache) {
|
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)
|
_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`] = `
|
exports[`compiler sfc: transform srcset > transform srcset 1`] = `
|
||||||
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
import _imports_0 from './logo.png'
|
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'
|
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
||||||
|
|
||||||
export function render(_ctx, _cache) {
|
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)
|
_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(`return (_ctx, _push`)
|
||||||
expect(content).toMatch(`ssrInterpolate`)
|
expect(content).toMatch(`ssrInterpolate`)
|
||||||
expect(content).not.toMatch(`useCssVars`)
|
expect(content).not.toMatch(`useCssVars`)
|
||||||
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
|
expect(content).toMatch(`":--${mockId}-count": (count.value)`)
|
||||||
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
|
expect(content).toMatch(`":--${mockId}-style\\\\.color": (style.color)`)
|
||||||
expect(content).toMatch(
|
expect(content).toMatch(
|
||||||
`"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
`":--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
||||||
)
|
)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
@ -913,6 +913,13 @@ describe('SFC compile <script setup>', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
|
compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
|
||||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
).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`
|
const moduleErrorMsg = `cannot contain ES module exports`
|
||||||
|
@ -1543,4 +1550,19 @@ describe('compileScript', () => {
|
||||||
)
|
)
|
||||||
assertCode(content)
|
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 { }
|
return { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -808,30 +808,4 @@ const props = defineProps({ foo: String })
|
||||||
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
|
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
|
||||||
assertCode(content)
|
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({
|
expect(props).toStrictEqual({
|
||||||
foo: ['Symbol', 'String', 'Number'],
|
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', () => {
|
describe('generics', () => {
|
||||||
test('generic with type literal', () => {
|
test('generic with type literal', () => {
|
||||||
expect(
|
expect(
|
||||||
|
@ -1155,6 +1198,45 @@ describe('resolveType', () => {
|
||||||
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
|
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', () => {
|
test('ts module resolve w/ project reference folder', () => {
|
||||||
const files = {
|
const files = {
|
||||||
'/tsconfig.json': JSON.stringify({
|
'/tsconfig.json': JSON.stringify({
|
||||||
|
@ -1299,6 +1381,33 @@ describe('resolveType', () => {
|
||||||
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
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', () => {
|
test('global types with ambient references', () => {
|
||||||
const files = {
|
const files = {
|
||||||
// with references
|
// with references
|
||||||
|
|
|
@ -72,6 +72,14 @@ describe('compiler sfc: transform srcset', () => {
|
||||||
).toMatchSnapshot()
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('transform empty srcset w/ includeAbsolute: true', () => {
|
||||||
|
expect(
|
||||||
|
compileWithSrcset(`<img srcset=" " />`, {
|
||||||
|
includeAbsolute: true,
|
||||||
|
}).code,
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('transform srcset w/ stringify', () => {
|
test('transform srcset w/ stringify', () => {
|
||||||
const code = compileWithSrcset(
|
const code = compileWithSrcset(
|
||||||
`<div>${src}</div>`,
|
`<div>${src}</div>`,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.5.16",
|
"version": "3.5.21",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
"module": "dist/compiler-sfc.esm-browser.js",
|
"module": "dist/compiler-sfc.esm-browser.js",
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"estree-walker": "catalog:",
|
"estree-walker": "catalog:",
|
||||||
"magic-string": "catalog:",
|
"magic-string": "catalog:",
|
||||||
"postcss": "^8.5.3",
|
"postcss": "^8.5.6",
|
||||||
"source-map-js": "catalog:"
|
"source-map-js": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "10.1.0",
|
"lru-cache": "10.1.0",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "~10.0.1",
|
"minimatch": "~10.0.3",
|
||||||
"postcss-modules": "^6.0.1",
|
"postcss-modules": "^6.0.1",
|
||||||
"postcss-selector-parser": "^7.1.0",
|
"postcss-selector-parser": "^7.1.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.89.1"
|
"sass": "^1.90.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import type {
|
||||||
Declaration,
|
Declaration,
|
||||||
ExportSpecifier,
|
ExportSpecifier,
|
||||||
Identifier,
|
Identifier,
|
||||||
|
LVal,
|
||||||
Node,
|
Node,
|
||||||
ObjectPattern,
|
ObjectPattern,
|
||||||
Statement,
|
Statement,
|
||||||
|
@ -55,7 +56,13 @@ import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
|
||||||
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
||||||
import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
||||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
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 { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||||
import { isImportUsed } from './script/importUsageCheck'
|
import { isImportUsed } from './script/importUsageCheck'
|
||||||
import { processAwait } from './script/topLevelAwait'
|
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 { script, scriptSetup, source, filename } = sfc
|
||||||
const hoistStatic = options.hoistStatic !== false && !script
|
const hoistStatic = options.hoistStatic !== false && !script
|
||||||
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
||||||
const scriptLang = script && script.lang
|
const scriptLang = script && script.lang
|
||||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||||
|
const isJSOrTS =
|
||||||
|
isJS(scriptLang, scriptSetupLang) || isTS(scriptLang, scriptSetupLang)
|
||||||
|
|
||||||
if (!scriptSetup) {
|
if (script && scriptSetup && scriptLang !== scriptSetupLang) {
|
||||||
if (!script) {
|
|
||||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
|
||||||
}
|
|
||||||
// normal <script> only
|
|
||||||
return processNormalScript(ctx, scopeId)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (script && scriptLang !== scriptSetupLang) {
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
|
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
|
||||||
`language type.`,
|
`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
|
// do not process non js/ts script blocks
|
||||||
return scriptSetup
|
return scriptSetup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ctx = new ScriptCompileContext(sfc, options)
|
||||||
|
|
||||||
// metadata that needs to be returned
|
// metadata that needs to be returned
|
||||||
// const ctx.bindingMetadata: BindingMetadata = {}
|
// const ctx.bindingMetadata: BindingMetadata = {}
|
||||||
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
|
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
|
||||||
|
@ -540,7 +557,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
// defineProps
|
// defineProps
|
||||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
|
||||||
if (ctx.propsDestructureRestId) {
|
if (ctx.propsDestructureRestId) {
|
||||||
setupBindings[ctx.propsDestructureRestId] =
|
setupBindings[ctx.propsDestructureRestId] =
|
||||||
BindingTypes.SETUP_REACTIVE_CONST
|
BindingTypes.SETUP_REACTIVE_CONST
|
||||||
|
@ -548,10 +565,10 @@ export function compileScript(
|
||||||
|
|
||||||
// defineEmits
|
// defineEmits
|
||||||
const isDefineEmits =
|
const isDefineEmits =
|
||||||
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
!isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
|
||||||
!isDefineEmits &&
|
!isDefineEmits &&
|
||||||
(processDefineSlots(ctx, init, decl.id) ||
|
(processDefineSlots(ctx, init, decl.id as LVal) ||
|
||||||
processDefineModel(ctx, init, decl.id))
|
processDefineModel(ctx, init, decl.id as LVal))
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isDefineProps &&
|
isDefineProps &&
|
||||||
|
|
|
@ -9,6 +9,7 @@ import type { BindingMetadata } from '../../../compiler-core/src'
|
||||||
import MagicString from 'magic-string'
|
import MagicString from 'magic-string'
|
||||||
import type { TypeScope } from './resolveType'
|
import type { TypeScope } from './resolveType'
|
||||||
import { warn } from '../warn'
|
import { warn } from '../warn'
|
||||||
|
import { isJS, isTS } from './utils'
|
||||||
|
|
||||||
export class ScriptCompileContext {
|
export class ScriptCompileContext {
|
||||||
isJS: boolean
|
isJS: boolean
|
||||||
|
@ -87,16 +88,8 @@ export class ScriptCompileContext {
|
||||||
const scriptLang = script && script.lang
|
const scriptLang = script && script.lang
|
||||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||||
|
|
||||||
this.isJS =
|
this.isJS = isJS(scriptLang, scriptSetupLang)
|
||||||
scriptLang === 'js' ||
|
this.isTS = isTS(scriptLang, scriptSetupLang)
|
||||||
scriptLang === 'jsx' ||
|
|
||||||
scriptSetupLang === 'js' ||
|
|
||||||
scriptSetupLang === 'jsx'
|
|
||||||
this.isTS =
|
|
||||||
scriptLang === 'ts' ||
|
|
||||||
scriptLang === 'tsx' ||
|
|
||||||
scriptSetupLang === 'ts' ||
|
|
||||||
scriptSetupLang === 'tsx'
|
|
||||||
|
|
||||||
const customElement = options.customElement
|
const customElement = options.customElement
|
||||||
const filename = this.descriptor.filename
|
const filename = this.descriptor.filename
|
||||||
|
|
|
@ -114,7 +114,7 @@ export function processDefineModel(
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genModelProps(ctx: ScriptCompileContext) {
|
export function genModelProps(ctx: ScriptCompileContext): string | undefined {
|
||||||
if (!ctx.hasDefineModelCall) return
|
if (!ctx.hasDefineModelCall) return
|
||||||
|
|
||||||
const isProd = !!ctx.options.isProd
|
const isProd = !!ctx.options.isProd
|
||||||
|
|
|
@ -12,10 +12,6 @@ export function processNormalScript(
|
||||||
scopeId: string,
|
scopeId: string,
|
||||||
): SFCScriptBlock {
|
): SFCScriptBlock {
|
||||||
const script = ctx.descriptor.script!
|
const script = ctx.descriptor.script!
|
||||||
if (script.lang && !ctx.isJS && !ctx.isTS) {
|
|
||||||
// do not process non js/ts script blocks
|
|
||||||
return script
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
let content = script.content
|
let content = script.content
|
||||||
let map = script.map
|
let map = script.map
|
||||||
|
|
|
@ -1029,6 +1029,14 @@ function resolveWithTS(
|
||||||
if (configs.length === 1) {
|
if (configs.length === 1) {
|
||||||
matchedConfig = configs[0]
|
matchedConfig = configs[0]
|
||||||
} else {
|
} 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
|
// resolve which config matches the current file
|
||||||
for (const c of configs) {
|
for (const c of configs) {
|
||||||
const base = normalizePath(
|
const base = normalizePath(
|
||||||
|
@ -1039,11 +1047,11 @@ function resolveWithTS(
|
||||||
const excluded: string[] | undefined = c.config.raw?.exclude
|
const excluded: string[] | undefined = c.config.raw?.exclude
|
||||||
if (
|
if (
|
||||||
(!included && (!base || containingFile.startsWith(base))) ||
|
(!included && (!base || containingFile.startsWith(base))) ||
|
||||||
included?.some(p => isMatch(containingFile, joinPaths(base, p)))
|
included?.some(p => isMatch(containingFile, getPattern(base, p)))
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
excluded &&
|
excluded &&
|
||||||
excluded.some(p => isMatch(containingFile, joinPaths(base, p)))
|
excluded.some(p => isMatch(containingFile, getPattern(base, p)))
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1296,7 +1304,12 @@ function recordTypes(
|
||||||
}
|
}
|
||||||
} else if (stmt.type === 'TSModuleDeclaration' && stmt.global) {
|
} else if (stmt.type === 'TSModuleDeclaration' && stmt.global) {
|
||||||
for (const s of (stmt.body as TSModuleBlock).body) {
|
for (const s of (stmt.body as TSModuleBlock).body) {
|
||||||
recordType(s, types, declares)
|
if (s.type === 'ExportNamedDeclaration' && s.declaration) {
|
||||||
|
// Handle export declarations inside declare global
|
||||||
|
recordType(s.declaration, types, declares)
|
||||||
|
} else {
|
||||||
|
recordType(s, types, declares)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1500,6 +1513,7 @@ export function inferRuntimeType(
|
||||||
node: Node & MaybeWithScope,
|
node: Node & MaybeWithScope,
|
||||||
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
||||||
isKeyOf = false,
|
isKeyOf = false,
|
||||||
|
typeParameters?: Record<string, Node>,
|
||||||
): string[] {
|
): string[] {
|
||||||
try {
|
try {
|
||||||
switch (node.type) {
|
switch (node.type) {
|
||||||
|
@ -1589,17 +1603,42 @@ export function inferRuntimeType(
|
||||||
const resolved = resolveTypeReference(ctx, node, scope)
|
const resolved = resolveTypeReference(ctx, node, scope)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
if (resolved.type === 'TSTypeAliasDeclaration') {
|
if (resolved.type === 'TSTypeAliasDeclaration') {
|
||||||
return inferRuntimeType(
|
// #13240
|
||||||
ctx,
|
// Special case for function type aliases to ensure correct runtime behavior
|
||||||
resolved.typeAnnotation,
|
// other type aliases still fallback to unknown as before
|
||||||
resolved._ownerScope,
|
if (resolved.typeAnnotation.type === 'TSFunctionType') {
|
||||||
isKeyOf,
|
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 (node.typeName.type === 'Identifier') {
|
||||||
|
if (typeParameters && typeParameters[node.typeName.name]) {
|
||||||
|
return inferRuntimeType(
|
||||||
|
ctx,
|
||||||
|
typeParameters[node.typeName.name],
|
||||||
|
scope,
|
||||||
|
isKeyOf,
|
||||||
|
typeParameters,
|
||||||
|
)
|
||||||
|
}
|
||||||
if (isKeyOf) {
|
if (isKeyOf) {
|
||||||
switch (node.typeName.name) {
|
switch (node.typeName.name) {
|
||||||
case 'String':
|
case 'String':
|
||||||
|
@ -1732,11 +1771,15 @@ export function inferRuntimeType(
|
||||||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||||
|
|
||||||
case 'TSUnionType':
|
case 'TSUnionType':
|
||||||
return flattenTypes(ctx, node.types, scope, isKeyOf)
|
return flattenTypes(ctx, node.types, scope, isKeyOf, typeParameters)
|
||||||
case 'TSIntersectionType': {
|
case 'TSIntersectionType': {
|
||||||
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
|
return flattenTypes(
|
||||||
t => t !== UNKNOWN_TYPE,
|
ctx,
|
||||||
)
|
node.types,
|
||||||
|
scope,
|
||||||
|
isKeyOf,
|
||||||
|
typeParameters,
|
||||||
|
).filter(t => t !== UNKNOWN_TYPE)
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'TSEnumDeclaration':
|
case 'TSEnumDeclaration':
|
||||||
|
@ -1807,14 +1850,17 @@ function flattenTypes(
|
||||||
types: TSType[],
|
types: TSType[],
|
||||||
scope: TypeScope,
|
scope: TypeScope,
|
||||||
isKeyOf: boolean = false,
|
isKeyOf: boolean = false,
|
||||||
|
typeParameters: Record<string, Node> | undefined = undefined,
|
||||||
): string[] {
|
): string[] {
|
||||||
if (types.length === 1) {
|
if (types.length === 1) {
|
||||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
|
return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
|
||||||
}
|
}
|
||||||
return [
|
return [
|
||||||
...new Set(
|
...new Set(
|
||||||
([] as string[]).concat(
|
([] 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 {
|
export function getEscapedPropName(key: string): string {
|
||||||
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
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
|
return `{\n ${vars
|
||||||
.map(
|
.map(
|
||||||
key =>
|
key =>
|
||||||
`"${isSSR ? `--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
|
// The `:` prefix here is used in `ssrRenderStyle` to distinguish whether
|
||||||
|
// a custom property comes from `ssrCssVars`. If it does, we need to reset
|
||||||
|
// its value to `initial` on the component instance to avoid unintentionally
|
||||||
|
// inheriting the same property value from a different instance of the same
|
||||||
|
// component in the outer scope.
|
||||||
|
`"${isSSR ? `:--` : ``}${genVarName(id, key, isProd, isSSR)}": (${key})`,
|
||||||
)
|
)
|
||||||
.join(',\n ')}\n}`
|
.join(',\n ')}\n}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,9 @@ import {
|
||||||
import selectorParser from 'postcss-selector-parser'
|
import selectorParser from 'postcss-selector-parser'
|
||||||
import { warn } from '../warn'
|
import { warn } from '../warn'
|
||||||
|
|
||||||
const animationNameRE = /^(-\w+-)?animation-name$/
|
const animationNameRE = /^(?:-\w+-)?animation-name$/
|
||||||
const animationRE = /^(-\w+-)?animation$/
|
const animationRE = /^(?:-\w+-)?animation$/
|
||||||
|
const keyframesRE = /^(?:-\w+-)?keyframes$/
|
||||||
|
|
||||||
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||||
const keyframes = Object.create(null)
|
const keyframes = Object.create(null)
|
||||||
|
@ -21,10 +22,7 @@ const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||||
processRule(id, rule)
|
processRule(id, rule)
|
||||||
},
|
},
|
||||||
AtRule(node) {
|
AtRule(node) {
|
||||||
if (
|
if (keyframesRE.test(node.name) && !node.params.endsWith(`-${shortId}`)) {
|
||||||
/-?keyframes$/.test(node.name) &&
|
|
||||||
!node.params.endsWith(`-${shortId}`)
|
|
||||||
) {
|
|
||||||
// register keyframes
|
// register keyframes
|
||||||
keyframes[node.params] = node.params = node.params + '-' + shortId
|
keyframes[node.params] = node.params = node.params + '-' + shortId
|
||||||
}
|
}
|
||||||
|
@ -72,7 +70,7 @@ function processRule(id: string, rule: Rule) {
|
||||||
processedRules.has(rule) ||
|
processedRules.has(rule) ||
|
||||||
(rule.parent &&
|
(rule.parent &&
|
||||||
rule.parent.type === 'atrule' &&
|
rule.parent.type === 'atrule' &&
|
||||||
/-?keyframes$/.test((rule.parent as AtRule).name))
|
keyframesRE.test((rule.parent as AtRule).name))
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ export function isRelativeUrl(url: string): boolean {
|
||||||
return firstChar === '.' || firstChar === '~' || firstChar === '@'
|
return firstChar === '.' || firstChar === '~' || firstChar === '@'
|
||||||
}
|
}
|
||||||
|
|
||||||
const externalRE = /^(https?:)?\/\//
|
const externalRE = /^(?:https?:)?\/\//
|
||||||
export function isExternalUrl(url: string): boolean {
|
export function isExternalUrl(url: string): boolean {
|
||||||
return externalRE.test(url)
|
return externalRE.test(url)
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,7 @@ export const transformSrcset: NodeTransform = (
|
||||||
|
|
||||||
const shouldProcessUrl = (url: string) => {
|
const shouldProcessUrl = (url: string) => {
|
||||||
return (
|
return (
|
||||||
|
url &&
|
||||||
!isExternalUrl(url) &&
|
!isExternalUrl(url) &&
|
||||||
!isDataUrl(url) &&
|
!isDataUrl(url) &&
|
||||||
(options.includeAbsolute || isRelativeUrl(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', () => {
|
describe('built-in fallthroughs', () => {
|
||||||
test('transition', () => {
|
test('transition', () => {
|
||||||
expect(compile(`<transition><div/></transition>`).code)
|
expect(compile(`<transition><div/></transition>`).code)
|
||||||
|
|
|
@ -166,6 +166,132 @@ describe('ssr: v-model', () => {
|
||||||
_push(\`</optgroup></select></div>\`)
|
_push(\`</optgroup></select></div>\`)
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
compileWithWrapper(`
|
||||||
|
<select multiple v-model="model">
|
||||||
|
<optgroup>
|
||||||
|
<option v-for="item in items" :value="item">{{item}}</option>
|
||||||
|
</optgroup>
|
||||||
|
</select>`).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrInterpolate: _ssrInterpolate, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select multiple><optgroup><!--[-->\`)
|
||||||
|
_ssrRenderList(_ctx.items, (item) => {
|
||||||
|
_push(\`<option\${
|
||||||
|
_ssrRenderAttr("value", item)
|
||||||
|
}\${
|
||||||
|
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||||
|
? _ssrLooseContain(_ctx.model, item)
|
||||||
|
: _ssrLooseEqual(_ctx.model, item))) ? " selected" : ""
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(item)
|
||||||
|
}</option>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!--]--></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">', () => {
|
test('<input type="radio">', () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.5.16",
|
"version": "3.5.21",
|
||||||
"description": "@vue/compiler-ssr",
|
"description": "@vue/compiler-ssr",
|
||||||
"main": "dist/compiler-ssr.cjs.js",
|
"main": "dist/compiler-ssr.cjs.js",
|
||||||
"types": "dist/compiler-ssr.d.ts",
|
"types": "dist/compiler-ssr.d.ts",
|
||||||
|
|
|
@ -29,7 +29,7 @@ if (__TEST__) {
|
||||||
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`SSRErrorCodes need to be updated to ${
|
`SSRErrorCodes need to be updated to ${
|
||||||
DOMErrorCodes.__EXTEND_POINT__ + 1
|
DOMErrorCodes.__EXTEND_POINT__
|
||||||
} to match extension point from core DOMErrorCodes.`,
|
} to match extension point from core DOMErrorCodes.`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import {
|
||||||
|
|
||||||
// Plugin for the first transform pass, which simply constructs the AST node
|
// Plugin for the first transform pass, which simply constructs the AST node
|
||||||
export const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform(
|
export const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||||
/^(if|else|else-if)$/,
|
/^(?:if|else|else-if)$/,
|
||||||
processIf,
|
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) {
|
function processOption(plainNode: PlainElementNode) {
|
||||||
if (plainNode.tag === 'option') {
|
if (plainNode.tag === 'option') {
|
||||||
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
|
if (plainNode.props.findIndex(p => p.name === 'selected') === -1) {
|
||||||
|
@ -65,9 +77,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else if (plainNode.tag === 'optgroup') {
|
} else if (plainNode.tag === 'optgroup') {
|
||||||
plainNode.children.forEach(option =>
|
processSelectChildren(plainNode.children)
|
||||||
processOption(option as PlainElementNode),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,18 +173,7 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
checkDuplicatedValue()
|
checkDuplicatedValue()
|
||||||
node.children = [createInterpolation(model, model.loc)]
|
node.children = [createInterpolation(model, model.loc)]
|
||||||
} else if (node.tag === 'select') {
|
} else if (node.tag === 'select') {
|
||||||
const processChildren = (children: TemplateChildNode[]) => {
|
processSelectChildren(node.children)
|
||||||
children.forEach(child => {
|
|
||||||
if (child.type === NodeTypes.ELEMENT) {
|
|
||||||
processOption(child as PlainElementNode)
|
|
||||||
} else if (child.type === NodeTypes.FOR) {
|
|
||||||
processChildren(child.children)
|
|
||||||
} else if (child.type === NodeTypes.IF) {
|
|
||||||
child.branches.forEach(b => processChildren(b.children))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
processChildren(node.children)
|
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createDOMCompilerError(
|
createDOMCompilerError(
|
||||||
|
|
|
@ -498,9 +498,10 @@ describe('reactivity/readonly', () => {
|
||||||
const r = ref(false)
|
const r = ref(false)
|
||||||
const ror = readonly(r)
|
const ror = readonly(r)
|
||||||
const obj = reactive({ ror })
|
const obj = reactive({ ror })
|
||||||
expect(() => {
|
obj.ror = true
|
||||||
obj.ror = true
|
expect(
|
||||||
}).toThrow()
|
`Set operation on key "ror" failed: target is readonly.`,
|
||||||
|
).toHaveBeenWarned()
|
||||||
expect(obj.ror).toBe(false)
|
expect(obj.ror).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.5.16",
|
"version": "3.5.21",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
@ -107,7 +107,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
|
||||||
return reactiveReadArray(this).join(separator)
|
return reactiveReadArray(this).join(separator)
|
||||||
},
|
},
|
||||||
|
|
||||||
// keys() iterator only reads `length`, no optimisation required
|
// keys() iterator only reads `length`, no optimization required
|
||||||
|
|
||||||
lastIndexOf(...args: unknown[]) {
|
lastIndexOf(...args: unknown[]) {
|
||||||
return searchProxy(this, 'lastIndexOf', args)
|
return searchProxy(this, 'lastIndexOf', args)
|
||||||
|
@ -200,7 +200,7 @@ function iterator(
|
||||||
wrapValue: (value: any) => unknown,
|
wrapValue: (value: any) => unknown,
|
||||||
) {
|
) {
|
||||||
// note that taking ARRAY_ITERATE dependency here is not strictly equivalent
|
// 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:
|
// creating the iterator does not access any array property:
|
||||||
// it is only when .next() is called that length and indexes are accessed.
|
// 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,
|
// 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 (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
||||||
if (isOldValueReadonly) {
|
if (isOldValueReadonly) {
|
||||||
return false
|
if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
||||||
|
target[key],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
} else {
|
} else {
|
||||||
oldValue.value = value
|
oldValue.value = value
|
||||||
return true
|
return true
|
||||||
|
|
|
@ -125,7 +125,7 @@ function createInstrumentations(
|
||||||
get size() {
|
get size() {
|
||||||
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
|
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
|
||||||
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||||
return Reflect.get(target, 'size', target)
|
return target.size
|
||||||
},
|
},
|
||||||
has(this: CollectionTypes, key: unknown): boolean {
|
has(this: CollectionTypes, key: unknown): boolean {
|
||||||
const target = this[ReactiveFlags.RAW]
|
const target = this[ReactiveFlags.RAW]
|
||||||
|
|
|
@ -275,7 +275,7 @@ export function proxyRefs<T extends object>(
|
||||||
objectWithRefs: T,
|
objectWithRefs: T,
|
||||||
): ShallowUnwrapRef<T> {
|
): ShallowUnwrapRef<T> {
|
||||||
return isReactive(objectWithRefs)
|
return isReactive(objectWithRefs)
|
||||||
? objectWithRefs
|
? (objectWithRefs as ShallowUnwrapRef<T>)
|
||||||
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
|
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -331,17 +331,17 @@ export function watch(
|
||||||
export function traverse(
|
export function traverse(
|
||||||
value: unknown,
|
value: unknown,
|
||||||
depth: number = Infinity,
|
depth: number = Infinity,
|
||||||
seen?: Set<unknown>,
|
seen?: Map<unknown, number>,
|
||||||
): unknown {
|
): unknown {
|
||||||
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
|
|
||||||
seen = seen || new Set()
|
seen = seen || new Map()
|
||||||
if (seen.has(value)) {
|
if ((seen.get(value) || 0) >= depth) {
|
||||||
return value
|
return value
|
||||||
}
|
}
|
||||||
seen.add(value)
|
seen.set(value, depth)
|
||||||
depth--
|
depth--
|
||||||
if (isRef(value)) {
|
if (isRef(value)) {
|
||||||
traverse(value.value, depth, seen)
|
traverse(value.value, depth, seen)
|
||||||
|
|
|
@ -1689,6 +1689,57 @@ describe('api: watch', () => {
|
||||||
expect(cb).toHaveBeenCalledTimes(4)
|
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 () => {
|
test('pause / resume', async () => {
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
const cb = vi.fn()
|
const cb = vi.fn()
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
type ComponentPublicInstance,
|
type ComponentPublicInstance,
|
||||||
|
createApp,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
@ -598,4 +599,45 @@ describe('component: emit', () => {
|
||||||
render(h(ComponentC), el)
|
render(h(ComponentC), el)
|
||||||
expect(renderFn).toHaveBeenCalledTimes(1)
|
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() {
|
data() {
|
||||||
return {
|
return {
|
||||||
foo: 0,
|
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() {
|
setup() {
|
||||||
return {
|
return {
|
||||||
bar: 1,
|
bar: 1,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
__cssModules: {
|
||||||
|
$style: {},
|
||||||
|
cssStyles: {},
|
||||||
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
instanceProxy = this
|
instanceProxy = this
|
||||||
},
|
},
|
||||||
|
@ -181,6 +194,7 @@ describe('component: proxy', () => {
|
||||||
|
|
||||||
const app = createApp(Comp, { msg: 'hello' })
|
const app = createApp(Comp, { msg: 'hello' })
|
||||||
app.config.globalProperties.global = 1
|
app.config.globalProperties.global = 1
|
||||||
|
app.config.globalProperties.$global = 1
|
||||||
|
|
||||||
app.mount(nodeOps.createElement('div'))
|
app.mount(nodeOps.createElement('div'))
|
||||||
|
|
||||||
|
@ -188,12 +202,20 @@ describe('component: proxy', () => {
|
||||||
expect('msg' in instanceProxy).toBe(true)
|
expect('msg' in instanceProxy).toBe(true)
|
||||||
// data
|
// data
|
||||||
expect('foo' in instanceProxy).toBe(true)
|
expect('foo' in instanceProxy).toBe(true)
|
||||||
// ctx
|
expect('$foo' in instanceProxy).toBe(false)
|
||||||
|
// setupState
|
||||||
expect('bar' in instanceProxy).toBe(true)
|
expect('bar' in instanceProxy).toBe(true)
|
||||||
|
// ctx
|
||||||
|
expect('cmp' in instanceProxy).toBe(true)
|
||||||
|
expect('$cmp' in instanceProxy).toBe(true)
|
||||||
// public properties
|
// public properties
|
||||||
expect('$el' in instanceProxy).toBe(true)
|
expect('$el' in instanceProxy).toBe(true)
|
||||||
|
// CSS modules
|
||||||
|
expect('$style' in instanceProxy).toBe(true)
|
||||||
|
expect('cssStyles' in instanceProxy).toBe(true)
|
||||||
// global properties
|
// global properties
|
||||||
expect('global' in instanceProxy).toBe(true)
|
expect('global' in instanceProxy).toBe(true)
|
||||||
|
expect('$global' in instanceProxy).toBe(true)
|
||||||
|
|
||||||
// non-existent
|
// non-existent
|
||||||
expect('$foobar' in instanceProxy).toBe(false)
|
expect('$foobar' in instanceProxy).toBe(false)
|
||||||
|
@ -202,11 +224,15 @@ describe('component: proxy', () => {
|
||||||
// #4962 triggering getter should not cause non-existent property to
|
// #4962 triggering getter should not cause non-existent property to
|
||||||
// pass the has check
|
// pass the has check
|
||||||
instanceProxy.baz
|
instanceProxy.baz
|
||||||
|
instanceProxy.$baz
|
||||||
expect('baz' in instanceProxy).toBe(false)
|
expect('baz' in instanceProxy).toBe(false)
|
||||||
|
expect('$baz' in instanceProxy).toBe(false)
|
||||||
|
|
||||||
// set non-existent (goes into proxyTarget sink)
|
// set non-existent (goes into proxyTarget sink)
|
||||||
instanceProxy.baz = 1
|
instanceProxy.baz = 1
|
||||||
expect('baz' in instanceProxy).toBe(true)
|
expect('baz' in instanceProxy).toBe(true)
|
||||||
|
instanceProxy.$baz = 1
|
||||||
|
expect('$baz' in instanceProxy).toBe(true)
|
||||||
|
|
||||||
// dev mode ownKeys check for console inspection
|
// dev mode ownKeys check for console inspection
|
||||||
// should only expose own keys
|
// should only expose own keys
|
||||||
|
@ -214,7 +240,10 @@ describe('component: proxy', () => {
|
||||||
'msg',
|
'msg',
|
||||||
'bar',
|
'bar',
|
||||||
'foo',
|
'foo',
|
||||||
|
'cmp',
|
||||||
|
'$cmp',
|
||||||
'baz',
|
'baz',
|
||||||
|
'$baz',
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,8 @@ import {
|
||||||
nodeOps,
|
nodeOps,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
serializeInner,
|
||||||
|
useSlots,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { createBlock, normalizeVNode } from '../src/vnode'
|
import { createBlock, normalizeVNode } from '../src/vnode'
|
||||||
import { createSlots } from '../src/helpers/createSlots'
|
import { createSlots } from '../src/helpers/createSlots'
|
||||||
|
@ -42,6 +44,25 @@ describe('component: slots', () => {
|
||||||
expect(slots).toMatchObject({})
|
expect(slots).toMatchObject({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('initSlots: ensure compiler marker non-enumerable', () => {
|
||||||
|
const Comp = {
|
||||||
|
render() {
|
||||||
|
const slots = useSlots()
|
||||||
|
// Only user-defined slots should be enumerable
|
||||||
|
expect(Object.keys(slots)).toEqual(['foo'])
|
||||||
|
|
||||||
|
// Internal compiler markers must still exist but be non-enumerable
|
||||||
|
expect(slots).toHaveProperty('_')
|
||||||
|
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
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)', () => {
|
test('initSlots: should normalize object slots (when value is null, string, array)', () => {
|
||||||
const { slots } = renderWithSlots({
|
const { slots } = renderWithSlots({
|
||||||
_inner: '_inner',
|
_inner: '_inner',
|
||||||
|
@ -50,6 +71,10 @@ describe('component: slots', () => {
|
||||||
footer: ['f1', 'f2'],
|
footer: ['f1', 'f2'],
|
||||||
})
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
'[Vue warn]: Non-function value encountered for slot "_inner". Prefer function slots for better performance.',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
|
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
|
@ -58,8 +83,8 @@ describe('component: slots', () => {
|
||||||
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
|
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
|
||||||
).toHaveBeenWarned()
|
).toHaveBeenWarned()
|
||||||
|
|
||||||
expect(slots).not.toHaveProperty('_inner')
|
|
||||||
expect(slots).not.toHaveProperty('foo')
|
expect(slots).not.toHaveProperty('foo')
|
||||||
|
expect(slots._inner()).toMatchObject([normalizeVNode('_inner')])
|
||||||
expect(slots.header()).toMatchObject([normalizeVNode('header')])
|
expect(slots.header()).toMatchObject([normalizeVNode('header')])
|
||||||
expect(slots.footer()).toMatchObject([
|
expect(slots.footer()).toMatchObject([
|
||||||
normalizeVNode('f1'),
|
normalizeVNode('f1'),
|
||||||
|
@ -418,4 +443,22 @@ describe('component: slots', () => {
|
||||||
'Slot "default" invoked outside of the render function',
|
'Slot "default" invoked outside of the render function',
|
||||||
).toHaveBeenWarned()
|
).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,
|
onUnmounted,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
renderList,
|
||||||
|
renderSlot,
|
||||||
resolveDynamicComponent,
|
resolveDynamicComponent,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
|
@ -2161,6 +2163,80 @@ describe('Suspense', () => {
|
||||||
await Promise.all(deps)
|
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', () => {
|
describe('warnings', () => {
|
||||||
// base function to check if a combination of slots warns or not
|
// base function to check if a combination of slots warns or not
|
||||||
function baseCheckWarn(
|
function baseCheckWarn(
|
||||||
|
@ -2230,5 +2306,57 @@ describe('Suspense', () => {
|
||||||
fallback: [h('div'), h('div')],
|
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)
|
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)', () => {
|
test('should work when used as direct ref value (compiled in prod mode)', () => {
|
||||||
__DEV__ = false
|
__DEV__ = false
|
||||||
try {
|
try {
|
||||||
|
@ -125,4 +253,65 @@ describe('useTemplateRef', () => {
|
||||||
__DEV__ = true
|
__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()
|
await timeout()
|
||||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
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 () => {
|
test('hydrate safely when property used by async setup changed before render', async () => {
|
||||||
const toggle = ref(true)
|
const toggle = ref(true)
|
||||||
|
|
||||||
|
@ -1677,6 +1740,35 @@ describe('SSR hydration', () => {
|
||||||
expect(`mismatch`).not.toHaveBeenWarned()
|
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', () => {
|
test('transition appear with v-if', () => {
|
||||||
const show = false
|
const show = false
|
||||||
const { vnode, container } = mountWithHydration(
|
const { vnode, container } = mountWithHydration(
|
||||||
|
@ -2265,6 +2357,30 @@ describe('SSR hydration', () => {
|
||||||
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
|
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', () => {
|
test('escape css var name', () => {
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
|
container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
render,
|
render,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
|
watch,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
|
|
||||||
describe('api: template refs', () => {
|
describe('api: template refs', () => {
|
||||||
|
@ -179,6 +180,89 @@ describe('api: template refs', () => {
|
||||||
expect(el.value).toBe(null)
|
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 () => {
|
it('unset old ref when new ref is absent', async () => {
|
||||||
const root1 = nodeOps.createElement('div')
|
const root1 = nodeOps.createElement('div')
|
||||||
const root2 = nodeOps.createElement('div')
|
const root2 = nodeOps.createElement('div')
|
||||||
|
|
|
@ -553,18 +553,6 @@ describe('vnode', () => {
|
||||||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
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
|
// #1039
|
||||||
// <component :is="foo">{{ bar }}</component>
|
// <component :is="foo">{{ bar }}</component>
|
||||||
// - content is compiled as slot
|
// - content is compiled as slot
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-core",
|
"name": "@vue/runtime-core",
|
||||||
"version": "3.5.16",
|
"version": "3.5.21",
|
||||||
"description": "@vue/runtime-core",
|
"description": "@vue/runtime-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-core.esm-bundler.js",
|
"module": "dist/runtime-core.esm-bundler.js",
|
||||||
|
|
|
@ -43,7 +43,7 @@ export interface AsyncComponentOptions<T = any> {
|
||||||
export const isAsyncWrapper = (i: ComponentInternalInstance | VNode): boolean =>
|
export const isAsyncWrapper = (i: ComponentInternalInstance | VNode): boolean =>
|
||||||
!!(i.type as ComponentOptions).__asyncLoader
|
!!(i.type as ComponentOptions).__asyncLoader
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*@__NO_SIDE_EFFECTS__*/
|
||||||
export function defineAsyncComponent<
|
export function defineAsyncComponent<
|
||||||
T extends Component = { new (): ComponentPublicInstance },
|
T extends Component = { new (): ComponentPublicInstance },
|
||||||
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
|
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
|
||||||
|
@ -123,28 +123,30 @@ export function defineAsyncComponent<
|
||||||
|
|
||||||
__asyncHydrate(el, instance, hydrate) {
|
__asyncHydrate(el, instance, hydrate) {
|
||||||
let patched = false
|
let patched = false
|
||||||
|
;(instance.bu || (instance.bu = [])).push(() => (patched = true))
|
||||||
|
const performHydrate = () => {
|
||||||
|
// skip hydration if the component has been patched
|
||||||
|
if (patched) {
|
||||||
|
if (__DEV__) {
|
||||||
|
warn(
|
||||||
|
`Skipping lazy hydration for component '${getComponentName(resolvedComp!) || resolvedComp!.__file}': ` +
|
||||||
|
`it was updated before lazy hydration performed.`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hydrate()
|
||||||
|
}
|
||||||
const doHydrate = hydrateStrategy
|
const doHydrate = hydrateStrategy
|
||||||
? () => {
|
? () => {
|
||||||
const performHydrate = () => {
|
|
||||||
// skip hydration if the component has been patched
|
|
||||||
if (__DEV__ && patched) {
|
|
||||||
warn(
|
|
||||||
`Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
|
|
||||||
`it was updated before lazy hydration performed.`,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
hydrate()
|
|
||||||
}
|
|
||||||
const teardown = hydrateStrategy(performHydrate, cb =>
|
const teardown = hydrateStrategy(performHydrate, cb =>
|
||||||
forEachElement(el, cb),
|
forEachElement(el, cb),
|
||||||
)
|
)
|
||||||
if (teardown) {
|
if (teardown) {
|
||||||
;(instance.bum || (instance.bum = [])).push(teardown)
|
;(instance.bum || (instance.bum = [])).push(teardown)
|
||||||
}
|
}
|
||||||
;(instance.u || (instance.u = [])).push(() => (patched = true))
|
|
||||||
}
|
}
|
||||||
: hydrate
|
: performHydrate
|
||||||
if (resolvedComp) {
|
if (resolvedComp) {
|
||||||
doHydrate()
|
doHydrate()
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -301,7 +301,7 @@ export function defineComponent<
|
||||||
>
|
>
|
||||||
|
|
||||||
// implementation, close to no-op
|
// implementation, close to no-op
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*@__NO_SIDE_EFFECTS__*/
|
||||||
export function defineComponent(
|
export function defineComponent(
|
||||||
options: unknown,
|
options: unknown,
|
||||||
extraOptions?: ComponentOptions,
|
extraOptions?: ComponentOptions,
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import { isFunction } from '@vue/shared'
|
import { isFunction } from '@vue/shared'
|
||||||
import { currentInstance } from './component'
|
import { currentInstance, getCurrentInstance } from './component'
|
||||||
import { currentRenderingInstance } from './componentRenderContext'
|
|
||||||
import { currentApp } from './apiCreateApp'
|
import { currentApp } from './apiCreateApp'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
|
|
||||||
|
@ -51,7 +50,7 @@ export function inject(
|
||||||
) {
|
) {
|
||||||
// fallback to `currentRenderingInstance` so that this can be called in
|
// fallback to `currentRenderingInstance` so that this can be called in
|
||||||
// a functional component
|
// a functional component
|
||||||
const instance = currentInstance || currentRenderingInstance
|
const instance = getCurrentInstance()
|
||||||
|
|
||||||
// also support looking up from app-level provides w/ `app.runWithContext()`
|
// also support looking up from app-level provides w/ `app.runWithContext()`
|
||||||
if (instance || currentApp) {
|
if (instance || currentApp) {
|
||||||
|
@ -90,5 +89,5 @@ export function inject(
|
||||||
* user. One example is `useRoute()` in `vue-router`.
|
* user. One example is `useRoute()` in `vue-router`.
|
||||||
*/
|
*/
|
||||||
export function hasInjectionContext(): boolean {
|
export function hasInjectionContext(): boolean {
|
||||||
return !!(currentInstance || currentRenderingInstance || currentApp)
|
return !!(getCurrentInstance() || currentApp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -382,17 +382,17 @@ export function withDefaults<
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSlots(): SetupContext['slots'] {
|
export function useSlots(): SetupContext['slots'] {
|
||||||
return getContext().slots
|
return getContext('useSlots').slots
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAttrs(): SetupContext['attrs'] {
|
export function useAttrs(): SetupContext['attrs'] {
|
||||||
return getContext().attrs
|
return getContext('useAttrs').attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContext(): SetupContext {
|
function getContext(calledFunctionName: string): SetupContext {
|
||||||
const i = getCurrentInstance()!
|
const i = getCurrentInstance()!
|
||||||
if (__DEV__ && !i) {
|
if (__DEV__ && !i) {
|
||||||
warn(`useContext() called without active instance.`)
|
warn(`${calledFunctionName}() called without active instance.`)
|
||||||
}
|
}
|
||||||
return i.setupContext || (i.setupContext = createSetupContext(i))
|
return i.setupContext || (i.setupContext = createSetupContext(i))
|
||||||
}
|
}
|
||||||
|
|
|
@ -536,7 +536,7 @@ function installCompatMount(
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
for (let i = 0; i < container.attributes.length; i++) {
|
for (let i = 0; i < container.attributes.length; i++) {
|
||||||
const attr = container.attributes[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)
|
warnDeprecation(DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ import {
|
||||||
legacyPrependModifier,
|
legacyPrependModifier,
|
||||||
legacyRenderSlot,
|
legacyRenderSlot,
|
||||||
legacyRenderStatic,
|
legacyRenderStatic,
|
||||||
legacyresolveScopedSlots,
|
legacyResolveScopedSlots,
|
||||||
} from './renderHelpers'
|
} from './renderHelpers'
|
||||||
import { resolveFilter } from '../helpers/resolveAssets'
|
import { resolveFilter } from '../helpers/resolveAssets'
|
||||||
import type { Slots } from '../componentSlots'
|
import type { Slots } from '../componentSlots'
|
||||||
|
@ -183,7 +183,7 @@ export function installCompatInstanceProperties(
|
||||||
_b: () => legacyBindObjectProps,
|
_b: () => legacyBindObjectProps,
|
||||||
_v: () => createTextVNode,
|
_v: () => createTextVNode,
|
||||||
_e: () => createCommentVNode,
|
_e: () => createCommentVNode,
|
||||||
_u: () => legacyresolveScopedSlots,
|
_u: () => legacyResolveScopedSlots,
|
||||||
_g: () => legacyBindObjectListeners,
|
_g: () => legacyBindObjectListeners,
|
||||||
_d: () => legacyBindDynamicKeys,
|
_d: () => legacyBindDynamicKeys,
|
||||||
_p: () => legacyPrependModifier,
|
_p: () => legacyPrependModifier,
|
||||||
|
|
|
@ -87,7 +87,7 @@ type LegacyScopedSlotsData = Array<
|
||||||
| LegacyScopedSlotsData
|
| LegacyScopedSlotsData
|
||||||
>
|
>
|
||||||
|
|
||||||
export function legacyresolveScopedSlots(
|
export function legacyResolveScopedSlots(
|
||||||
fns: LegacyScopedSlotsData,
|
fns: LegacyScopedSlotsData,
|
||||||
raw?: Record<string, Slot>,
|
raw?: Record<string, Slot>,
|
||||||
// the following are added in 2.6
|
// the following are added in 2.6
|
||||||
|
|
|
@ -585,13 +585,13 @@ export interface ComponentInternalInstance {
|
||||||
* For updating css vars on contained teleports
|
* For updating css vars on contained teleports
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
ut?: (vars?: Record<string, string>) => void
|
ut?: (vars?: Record<string, unknown>) => void
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* dev only. For style v-bind hydration mismatch checks
|
* dev only. For style v-bind hydration mismatch checks
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
getCssVars?: () => Record<string, string>
|
getCssVars?: () => Record<string, unknown>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* v2 compat only, for caching mutated $options
|
* v2 compat only, for caching mutated $options
|
||||||
|
@ -1203,7 +1203,7 @@ export function getComponentPublicInstance(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const classifyRE = /(?:^|[-_])(\w)/g
|
const classifyRE = /(?:^|[-_])\w/g
|
||||||
const classify = (str: string): string =>
|
const classify = (str: string): string =>
|
||||||
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
|
||||||
|
|
||||||
|
|
|
@ -151,10 +151,14 @@ export function emit(
|
||||||
}
|
}
|
||||||
|
|
||||||
let args = rawArgs
|
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
|
// for v-model update:xxx events, apply modifiers on args
|
||||||
const modifiers = isModelListener && getModelModifiers(props, event.slice(7))
|
|
||||||
if (modifiers) {
|
if (modifiers) {
|
||||||
if (modifiers.trim) {
|
if (modifiers.trim) {
|
||||||
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
args = rawArgs.map(a => (isString(a) ? a.trim() : a))
|
||||||
|
@ -228,12 +232,14 @@ export function emit(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const mixinEmitsCache = new WeakMap<ConcreteComponent, ObjectEmitsOptions>()
|
||||||
export function normalizeEmitsOptions(
|
export function normalizeEmitsOptions(
|
||||||
comp: ConcreteComponent,
|
comp: ConcreteComponent,
|
||||||
appContext: AppContext,
|
appContext: AppContext,
|
||||||
asMixin = false,
|
asMixin = false,
|
||||||
): ObjectEmitsOptions | null {
|
): ObjectEmitsOptions | null {
|
||||||
const cache = appContext.emitsCache
|
const cache =
|
||||||
|
__FEATURE_OPTIONS_API__ && asMixin ? mixinEmitsCache : appContext.emitsCache
|
||||||
const cached = cache.get(comp)
|
const cached = cache.get(comp)
|
||||||
if (cached !== undefined) {
|
if (cached !== undefined) {
|
||||||
return cached
|
return cached
|
||||||
|
|
|
@ -756,6 +756,7 @@ export function applyOptions(instance: ComponentInternalInstance): void {
|
||||||
Object.defineProperty(exposed, key, {
|
Object.defineProperty(exposed, key, {
|
||||||
get: () => publicThis[key],
|
get: () => publicThis[key],
|
||||||
set: val => (publicThis[key] = val),
|
set: val => (publicThis[key] = val),
|
||||||
|
enumerable: true,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else if (!instance.exposed) {
|
} else if (!instance.exposed) {
|
||||||
|
|
|
@ -575,19 +575,20 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
|
||||||
|
|
||||||
has(
|
has(
|
||||||
{
|
{
|
||||||
_: { data, setupState, accessCache, ctx, appContext, propsOptions },
|
_: { data, setupState, accessCache, ctx, appContext, propsOptions, type },
|
||||||
}: ComponentRenderContext,
|
}: ComponentRenderContext,
|
||||||
key: string,
|
key: string,
|
||||||
) {
|
) {
|
||||||
let normalizedProps
|
let normalizedProps, cssModules
|
||||||
return (
|
return !!(
|
||||||
!!accessCache![key] ||
|
accessCache![key] ||
|
||||||
(data !== EMPTY_OBJ && hasOwn(data, key)) ||
|
(data !== EMPTY_OBJ && key[0] !== '$' && hasOwn(data, key)) ||
|
||||||
hasSetupBinding(setupState, key) ||
|
hasSetupBinding(setupState, key) ||
|
||||||
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
((normalizedProps = propsOptions[0]) && hasOwn(normalizedProps, key)) ||
|
||||||
hasOwn(ctx, key) ||
|
hasOwn(ctx, key) ||
|
||||||
hasOwn(publicPropertiesMap, 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
|
* @internal
|
||||||
*/
|
*/
|
||||||
_?: SlotFlags
|
_?: 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[] =>
|
const normalizeSlotValue = (value: unknown): VNode[] =>
|
||||||
isArray(value)
|
isArray(value)
|
||||||
|
|
|
@ -24,7 +24,7 @@ import { SchedulerJobFlags } from '../scheduler'
|
||||||
|
|
||||||
type Hook<T = () => void> = T | T[]
|
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')
|
const enterCbKey: unique symbol = Symbol('_enterCb')
|
||||||
|
|
||||||
export interface BaseTransitionProps<HostElement = RendererElement> {
|
export interface BaseTransitionProps<HostElement = RendererElement> {
|
||||||
|
@ -204,7 +204,7 @@ const BaseTransitionImpl: ComponentOptions = {
|
||||||
if (
|
if (
|
||||||
oldInnerChild &&
|
oldInnerChild &&
|
||||||
oldInnerChild.type !== Comment &&
|
oldInnerChild.type !== Comment &&
|
||||||
!isSameVNodeType(innerChild, oldInnerChild) &&
|
!isSameVNodeType(oldInnerChild, innerChild) &&
|
||||||
recursiveGetSubtree(instance).type !== Comment
|
recursiveGetSubtree(instance).type !== Comment
|
||||||
) {
|
) {
|
||||||
let leavingHooks = resolveTransitionHooks(
|
let leavingHooks = resolveTransitionHooks(
|
||||||
|
|
|
@ -235,7 +235,7 @@ function patchSuspense(
|
||||||
const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense
|
const { activeBranch, pendingBranch, isInFallback, isHydrating } = suspense
|
||||||
if (pendingBranch) {
|
if (pendingBranch) {
|
||||||
suspense.pendingBranch = newBranch
|
suspense.pendingBranch = newBranch
|
||||||
if (isSameVNodeType(newBranch, pendingBranch)) {
|
if (isSameVNodeType(pendingBranch, newBranch)) {
|
||||||
// same root type but content may have changed.
|
// same root type but content may have changed.
|
||||||
patch(
|
patch(
|
||||||
pendingBranch,
|
pendingBranch,
|
||||||
|
@ -321,7 +321,7 @@ function patchSuspense(
|
||||||
)
|
)
|
||||||
setActiveBranch(suspense, newFallback)
|
setActiveBranch(suspense, newFallback)
|
||||||
}
|
}
|
||||||
} else if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
|
} else if (activeBranch && isSameVNodeType(activeBranch, newBranch)) {
|
||||||
// toggled "back" to current active branch
|
// toggled "back" to current active branch
|
||||||
patch(
|
patch(
|
||||||
activeBranch,
|
activeBranch,
|
||||||
|
@ -355,7 +355,7 @@ function patchSuspense(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (activeBranch && isSameVNodeType(newBranch, activeBranch)) {
|
if (activeBranch && isSameVNodeType(activeBranch, newBranch)) {
|
||||||
// root did not change, just normal patch
|
// root did not change, just normal patch
|
||||||
patch(
|
patch(
|
||||||
activeBranch,
|
activeBranch,
|
||||||
|
|
|
@ -406,29 +406,43 @@ function hydrateTeleport(
|
||||||
optimized: boolean,
|
optimized: boolean,
|
||||||
) => Node | null,
|
) => Node | null,
|
||||||
): Node | null {
|
): Node | null {
|
||||||
|
function hydrateDisabledTeleport(
|
||||||
|
node: Node,
|
||||||
|
vnode: VNode,
|
||||||
|
targetStart: Node | null,
|
||||||
|
targetAnchor: Node | null,
|
||||||
|
) {
|
||||||
|
vnode.anchor = hydrateChildren(
|
||||||
|
nextSibling(node),
|
||||||
|
vnode,
|
||||||
|
parentNode(node)!,
|
||||||
|
parentComponent,
|
||||||
|
parentSuspense,
|
||||||
|
slotScopeIds,
|
||||||
|
optimized,
|
||||||
|
)
|
||||||
|
vnode.targetStart = targetStart
|
||||||
|
vnode.targetAnchor = targetAnchor
|
||||||
|
}
|
||||||
|
|
||||||
const target = (vnode.target = resolveTarget<Element>(
|
const target = (vnode.target = resolveTarget<Element>(
|
||||||
vnode.props,
|
vnode.props,
|
||||||
querySelector,
|
querySelector,
|
||||||
))
|
))
|
||||||
|
const disabled = isTeleportDisabled(vnode.props)
|
||||||
if (target) {
|
if (target) {
|
||||||
const disabled = isTeleportDisabled(vnode.props)
|
|
||||||
// if multiple teleports rendered to the same target element, we need to
|
// if multiple teleports rendered to the same target element, we need to
|
||||||
// pick up from where the last teleport finished instead of the first node
|
// pick up from where the last teleport finished instead of the first node
|
||||||
const targetNode =
|
const targetNode =
|
||||||
(target as TeleportTargetElement)._lpa || target.firstChild
|
(target as TeleportTargetElement)._lpa || target.firstChild
|
||||||
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||||
if (disabled) {
|
if (disabled) {
|
||||||
vnode.anchor = hydrateChildren(
|
hydrateDisabledTeleport(
|
||||||
nextSibling(node),
|
node,
|
||||||
vnode,
|
vnode,
|
||||||
parentNode(node)!,
|
targetNode,
|
||||||
parentComponent,
|
targetNode && nextSibling(targetNode),
|
||||||
parentSuspense,
|
|
||||||
slotScopeIds,
|
|
||||||
optimized,
|
|
||||||
)
|
)
|
||||||
vnode.targetStart = targetNode
|
|
||||||
vnode.targetAnchor = targetNode && nextSibling(targetNode)
|
|
||||||
} else {
|
} else {
|
||||||
vnode.anchor = nextSibling(node)
|
vnode.anchor = nextSibling(node)
|
||||||
|
|
||||||
|
@ -470,6 +484,10 @@ function hydrateTeleport(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateCssVars(vnode, disabled)
|
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)
|
return vnode.anchor && nextSibling(vnode.anchor as Node)
|
||||||
}
|
}
|
||||||
|
|
|
@ -125,7 +125,7 @@ export const devtoolsComponentRemoved = (
|
||||||
|
|
||||||
type DevtoolsComponentHook = (component: ComponentInternalInstance) => void
|
type DevtoolsComponentHook = (component: ComponentInternalInstance) => void
|
||||||
|
|
||||||
/*! #__NO_SIDE_EFFECTS__ */
|
/*@__NO_SIDE_EFFECTS__*/
|
||||||
function createDevtoolsComponentHook(
|
function createDevtoolsComponentHook(
|
||||||
hook: DevtoolsHooks,
|
hook: DevtoolsHooks,
|
||||||
): DevtoolsComponentHook {
|
): DevtoolsComponentHook {
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
type VNodeProps,
|
type VNodeProps,
|
||||||
createVNode,
|
createVNode,
|
||||||
isVNode,
|
isVNode,
|
||||||
|
setBlockTracking,
|
||||||
} from './vnode'
|
} from './vnode'
|
||||||
import type { Teleport, TeleportProps } from './components/Teleport'
|
import type { Teleport, TeleportProps } from './components/Teleport'
|
||||||
import type { Suspense, SuspenseProps } from './components/Suspense'
|
import type { Suspense, SuspenseProps } from './components/Suspense'
|
||||||
|
@ -201,25 +202,31 @@ export function h<P>(
|
||||||
|
|
||||||
// Actual implementation
|
// Actual implementation
|
||||||
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
|
export function h(type: any, propsOrChildren?: any, children?: any): VNode {
|
||||||
const l = arguments.length
|
try {
|
||||||
if (l === 2) {
|
// #6913 disable tracking block in h function
|
||||||
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
|
setBlockTracking(-1)
|
||||||
// single vnode without props
|
const l = arguments.length
|
||||||
if (isVNode(propsOrChildren)) {
|
if (l === 2) {
|
||||||
return createVNode(type, null, [propsOrChildren])
|
if (isObject(propsOrChildren) && !isArray(propsOrChildren)) {
|
||||||
|
// single vnode without props
|
||||||
|
if (isVNode(propsOrChildren)) {
|
||||||
|
return createVNode(type, null, [propsOrChildren])
|
||||||
|
}
|
||||||
|
// props without children
|
||||||
|
return createVNode(type, propsOrChildren)
|
||||||
|
} else {
|
||||||
|
// omit props
|
||||||
|
return createVNode(type, null, propsOrChildren)
|
||||||
}
|
}
|
||||||
// props without children
|
|
||||||
return createVNode(type, propsOrChildren)
|
|
||||||
} else {
|
} else {
|
||||||
// omit props
|
if (l > 3) {
|
||||||
return createVNode(type, null, propsOrChildren)
|
children = Array.prototype.slice.call(arguments, 2)
|
||||||
|
} else if (l === 3 && isVNode(children)) {
|
||||||
|
children = [children]
|
||||||
|
}
|
||||||
|
return createVNode(type, propsOrChildren, children)
|
||||||
}
|
}
|
||||||
} else {
|
} finally {
|
||||||
if (l > 3) {
|
setBlockTracking(1)
|
||||||
children = Array.prototype.slice.call(arguments, 2)
|
|
||||||
} else if (l === 3 && isVNode(children)) {
|
|
||||||
children = [children]
|
|
||||||
}
|
|
||||||
return createVNode(type, propsOrChildren, children)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import {
|
||||||
type InternalRenderFunction,
|
type InternalRenderFunction,
|
||||||
isClassComponent,
|
isClassComponent,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { queueJob, queuePostFlushCb } from './scheduler'
|
import { SchedulerJobFlags, queueJob, queuePostFlushCb } from './scheduler'
|
||||||
import { extend, getGlobalThis } from '@vue/shared'
|
import { extend, getGlobalThis } from '@vue/shared'
|
||||||
|
|
||||||
type HMRComponent = ComponentOptions | ClassComponent
|
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
|
// 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.
|
// to be set so that its instances can be registered / removed.
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
getGlobalThis().__VUE_HMR_RUNTIME__ = {
|
const g = getGlobalThis()
|
||||||
createRecord: tryWrap(createRecord),
|
// vite-plugin-vue/issues/644, #13202
|
||||||
rerender: tryWrap(rerender),
|
// custom-element libraries bundle Vue to simplify usage outside Vue projects but
|
||||||
reload: tryWrap(reload),
|
// it overwrite __VUE_HMR_RUNTIME__, causing HMR to break.
|
||||||
} as HMRRuntime
|
if (!g.__VUE_HMR_RUNTIME__) {
|
||||||
|
g.__VUE_HMR_RUNTIME__ = {
|
||||||
|
createRecord: tryWrap(createRecord),
|
||||||
|
rerender: tryWrap(rerender),
|
||||||
|
reload: tryWrap(reload),
|
||||||
|
} as HMRRuntime
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const map: Map<
|
const map: Map<
|
||||||
|
@ -96,7 +102,10 @@ function rerender(id: string, newRender?: Function): void {
|
||||||
instance.renderCache = []
|
instance.renderCache = []
|
||||||
// this flag forces child components with slot content to update
|
// this flag forces child components with slot content to update
|
||||||
isHmrUpdating = true
|
isHmrUpdating = true
|
||||||
instance.update()
|
// #13771 don't update if the job is already disposed
|
||||||
|
if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
|
||||||
|
instance.update()
|
||||||
|
}
|
||||||
isHmrUpdating = false
|
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
|
// 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.
|
// don't end up forcing the same parent to re-render multiple times.
|
||||||
queueJob(() => {
|
queueJob(() => {
|
||||||
isHmrUpdating = true
|
// vite-plugin-vue/issues/599
|
||||||
instance.parent!.update()
|
// don't update if the job is already disposed
|
||||||
isHmrUpdating = false
|
if (!(instance.job.flags! & SchedulerJobFlags.DISPOSED)) {
|
||||||
// #6930, #11248 avoid infinite recursion
|
isHmrUpdating = true
|
||||||
dirtyInstances.delete(instance)
|
instance.parent!.update()
|
||||||
|
isHmrUpdating = false
|
||||||
|
// #6930, #11248 avoid infinite recursion
|
||||||
|
dirtyInstances.delete(instance)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
} else if (instance.appContext.reload) {
|
} else if (instance.appContext.reload) {
|
||||||
// root instance mounted via createApp() has a reload method
|
// root instance mounted via createApp() has a reload method
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
isReservedProp,
|
isReservedProp,
|
||||||
isString,
|
isString,
|
||||||
normalizeClass,
|
normalizeClass,
|
||||||
|
normalizeCssVarValue,
|
||||||
normalizeStyle,
|
normalizeStyle,
|
||||||
stringifyStyle,
|
stringifyStyle,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
@ -945,10 +946,8 @@ function resolveCssVars(
|
||||||
) {
|
) {
|
||||||
const cssVars = instance.getCssVars()
|
const cssVars = instance.getCssVars()
|
||||||
for (const key in cssVars) {
|
for (const key in cssVars) {
|
||||||
expectedMap.set(
|
const value = normalizeCssVarValue(cssVars[key])
|
||||||
`--${getEscapedCssVarName(key, false)}`,
|
expectedMap.set(`--${getEscapedCssVarName(key, false)}`, value)
|
||||||
String(cssVars[key]),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (vnode === root && instance.parent) {
|
if (vnode === root && instance.parent) {
|
||||||
|
|
|
@ -28,12 +28,10 @@ export function endMeasure(
|
||||||
if (instance.appContext.config.performance && isSupported()) {
|
if (instance.appContext.config.performance && isSupported()) {
|
||||||
const startTag = `vue-${type}-${instance.uid}`
|
const startTag = `vue-${type}-${instance.uid}`
|
||||||
const endTag = startTag + `:end`
|
const endTag = startTag + `:end`
|
||||||
|
const measureName = `<${formatComponentName(instance, instance.type)}> ${type}`
|
||||||
perf.mark(endTag)
|
perf.mark(endTag)
|
||||||
perf.measure(
|
perf.measure(measureName, startTag, endTag)
|
||||||
`<${formatComponentName(instance, instance.type)}> ${type}`,
|
perf.clearMeasures(measureName)
|
||||||
startTag,
|
|
||||||
endTag,
|
|
||||||
)
|
|
||||||
perf.clearMarks(startTag)
|
perf.clearMarks(startTag)
|
||||||
perf.clearMarks(endTag)
|
perf.clearMarks(endTag)
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ import { initFeatureFlags } from './featureFlags'
|
||||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
import { isCompatEnabled } from './compat/compatConfig'
|
import { isCompatEnabled } from './compat/compatConfig'
|
||||||
import { DeprecationTypes } 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'
|
import type { VueElement } from '@vue/runtime-dom'
|
||||||
|
|
||||||
export interface Renderer<HostElement = RendererElement> {
|
export interface Renderer<HostElement = RendererElement> {
|
||||||
|
@ -1226,6 +1226,7 @@ function baseCreateRenderer(
|
||||||
if (!initialVNode.el) {
|
if (!initialVNode.el) {
|
||||||
const placeholder = (instance.subTree = createVNode(Comment))
|
const placeholder = (instance.subTree = createVNode(Comment))
|
||||||
processCommentNode(null, placeholder, container!, anchor)
|
processCommentNode(null, placeholder, container!, anchor)
|
||||||
|
initialVNode.placeholder = placeholder.el
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
setupRenderEffect(
|
setupRenderEffect(
|
||||||
|
@ -1979,8 +1980,12 @@ function baseCreateRenderer(
|
||||||
for (i = toBePatched - 1; i >= 0; i--) {
|
for (i = toBePatched - 1; i >= 0; i--) {
|
||||||
const nextIndex = s2 + i
|
const nextIndex = s2 + i
|
||||||
const nextChild = c2[nextIndex] as VNode
|
const nextChild = c2[nextIndex] as VNode
|
||||||
|
const anchorVNode = c2[nextIndex + 1] as VNode
|
||||||
const anchor =
|
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) {
|
if (newIndexToOldIndexMap[i] === 0) {
|
||||||
// mount new
|
// mount new
|
||||||
patch(
|
patch(
|
||||||
|
@ -2065,6 +2070,12 @@ function baseCreateRenderer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const performLeave = () => {
|
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!, () => {
|
leave(el!, () => {
|
||||||
remove()
|
remove()
|
||||||
afterLeave && afterLeave()
|
afterLeave && afterLeave()
|
||||||
|
@ -2272,17 +2283,7 @@ function baseCreateRenderer(
|
||||||
unregisterHMR(instance)
|
unregisterHMR(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const { bum, scope, job, subTree, um, m, a } = instance
|
||||||
bum,
|
|
||||||
scope,
|
|
||||||
job,
|
|
||||||
subTree,
|
|
||||||
um,
|
|
||||||
m,
|
|
||||||
a,
|
|
||||||
parent,
|
|
||||||
slots: { __: slotCacheKeys },
|
|
||||||
} = instance
|
|
||||||
invalidateMount(m)
|
invalidateMount(m)
|
||||||
invalidateMount(a)
|
invalidateMount(a)
|
||||||
|
|
||||||
|
@ -2291,13 +2292,6 @@ function baseCreateRenderer(
|
||||||
invokeArrayFns(bum)
|
invokeArrayFns(bum)
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove slots content from parent renderCache
|
|
||||||
if (parent && isArray(slotCacheKeys)) {
|
|
||||||
slotCacheKeys.forEach(v => {
|
|
||||||
parent.renderCache[v] = undefined
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
||||||
|
@ -2332,24 +2326,6 @@ function baseCreateRenderer(
|
||||||
instance.isUnmounted = true
|
instance.isUnmounted = true
|
||||||
}, parentSuspense)
|
}, 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__) {
|
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
|
||||||
devtoolsComponentRemoved(instance)
|
devtoolsComponentRemoved(instance)
|
||||||
}
|
}
|
||||||
|
@ -2508,7 +2484,11 @@ export function traverseStaticChildren(
|
||||||
traverseStaticChildren(c1, c2)
|
traverseStaticChildren(c1, c2)
|
||||||
}
|
}
|
||||||
// #6852 also inherit for text nodes
|
// #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
|
c2.el = c1.el
|
||||||
}
|
}
|
||||||
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
|
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
import type { SuspenseBoundary } from './components/Suspense'
|
import type { SuspenseBoundary } from './components/Suspense'
|
||||||
import type { VNode, VNodeNormalizedRef, VNodeNormalizedRefAtom } from './vnode'
|
import type {
|
||||||
|
VNode,
|
||||||
|
VNodeNormalizedRef,
|
||||||
|
VNodeNormalizedRefAtom,
|
||||||
|
VNodeRef,
|
||||||
|
} from './vnode'
|
||||||
import {
|
import {
|
||||||
EMPTY_OBJ,
|
EMPTY_OBJ,
|
||||||
|
NO,
|
||||||
ShapeFlags,
|
ShapeFlags,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
isArray,
|
isArray,
|
||||||
|
@ -13,11 +19,12 @@ import { isAsyncWrapper } from './apiAsyncComponent'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { isRef, toRaw } from '@vue/reactivity'
|
import { isRef, toRaw } from '@vue/reactivity'
|
||||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||||
import type { SchedulerJob } from './scheduler'
|
import { type SchedulerJob, SchedulerJobFlags } from './scheduler'
|
||||||
import { queuePostRenderEffect } from './renderer'
|
import { queuePostRenderEffect } from './renderer'
|
||||||
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
import { type ComponentOptions, getComponentPublicInstance } from './component'
|
||||||
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
import { knownTemplateRefs } from './helpers/useTemplateRef'
|
||||||
|
|
||||||
|
const pendingSetRefMap = new WeakMap<VNodeNormalizedRef, SchedulerJob>()
|
||||||
/**
|
/**
|
||||||
* Function for handling a template ref
|
* Function for handling a template ref
|
||||||
*/
|
*/
|
||||||
|
@ -77,7 +84,7 @@ export function setRef(
|
||||||
const rawSetupState = toRaw(setupState)
|
const rawSetupState = toRaw(setupState)
|
||||||
const canSetSetupRef =
|
const canSetSetupRef =
|
||||||
setupState === EMPTY_OBJ
|
setupState === EMPTY_OBJ
|
||||||
? () => false
|
? NO
|
||||||
: (key: string) => {
|
: (key: string) => {
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
|
if (hasOwn(rawSetupState, key) && !isRef(rawSetupState[key])) {
|
||||||
|
@ -94,15 +101,26 @@ export function setRef(
|
||||||
return hasOwn(rawSetupState, key)
|
return hasOwn(rawSetupState, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const canSetRef = (ref: VNodeRef) => {
|
||||||
|
return !__DEV__ || !knownTemplateRefs.has(ref as any)
|
||||||
|
}
|
||||||
|
|
||||||
// dynamic ref changed. unset old ref
|
// dynamic ref changed. unset old ref
|
||||||
if (oldRef != null && oldRef !== ref) {
|
if (oldRef != null && oldRef !== ref) {
|
||||||
|
invalidatePendingSetRef(oldRawRef!)
|
||||||
if (isString(oldRef)) {
|
if (isString(oldRef)) {
|
||||||
refs[oldRef] = null
|
refs[oldRef] = null
|
||||||
if (canSetSetupRef(oldRef)) {
|
if (canSetSetupRef(oldRef)) {
|
||||||
setupState[oldRef] = null
|
setupState[oldRef] = null
|
||||||
}
|
}
|
||||||
} else if (isRef(oldRef)) {
|
} else if (isRef(oldRef)) {
|
||||||
oldRef.value = null
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -119,7 +137,9 @@ export function setRef(
|
||||||
? canSetSetupRef(ref)
|
? canSetSetupRef(ref)
|
||||||
? setupState[ref]
|
? setupState[ref]
|
||||||
: refs[ref]
|
: refs[ref]
|
||||||
: ref.value
|
: canSetRef(ref) || !rawRef.k
|
||||||
|
? ref.value
|
||||||
|
: refs[rawRef.k]
|
||||||
if (isUnmount) {
|
if (isUnmount) {
|
||||||
isArray(existing) && remove(existing, refValue)
|
isArray(existing) && remove(existing, refValue)
|
||||||
} else {
|
} else {
|
||||||
|
@ -130,8 +150,11 @@ export function setRef(
|
||||||
setupState[ref] = refs[ref]
|
setupState[ref] = refs[ref]
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ref.value = [refValue]
|
const newVal = [refValue]
|
||||||
if (rawRef.k) refs[rawRef.k] = ref.value
|
if (canSetRef(ref)) {
|
||||||
|
ref.value = newVal
|
||||||
|
}
|
||||||
|
if (rawRef.k) refs[rawRef.k] = newVal
|
||||||
}
|
}
|
||||||
} else if (!existing.includes(refValue)) {
|
} else if (!existing.includes(refValue)) {
|
||||||
existing.push(refValue)
|
existing.push(refValue)
|
||||||
|
@ -143,7 +166,9 @@ export function setRef(
|
||||||
setupState[ref] = value
|
setupState[ref] = value
|
||||||
}
|
}
|
||||||
} else if (_isRef) {
|
} else if (_isRef) {
|
||||||
ref.value = value
|
if (canSetRef(ref)) {
|
||||||
|
ref.value = value
|
||||||
|
}
|
||||||
if (rawRef.k) refs[rawRef.k] = value
|
if (rawRef.k) refs[rawRef.k] = value
|
||||||
} else if (__DEV__) {
|
} else if (__DEV__) {
|
||||||
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
warn('Invalid template ref type:', ref, `(${typeof ref})`)
|
||||||
|
@ -153,9 +178,15 @@ export function setRef(
|
||||||
// #1789: for non-null values, set them after render
|
// #1789: for non-null values, set them after render
|
||||||
// null values means this is unmount and it should not overwrite another
|
// null values means this is unmount and it should not overwrite another
|
||||||
// ref with the same key
|
// ref with the same key
|
||||||
;(doSet as SchedulerJob).id = -1
|
const job: SchedulerJob = () => {
|
||||||
queuePostRenderEffect(doSet, parentSuspense)
|
doSet()
|
||||||
|
pendingSetRefMap.delete(rawRef)
|
||||||
|
}
|
||||||
|
job.id = -1
|
||||||
|
pendingSetRefMap.set(rawRef, job)
|
||||||
|
queuePostRenderEffect(job, parentSuspense)
|
||||||
} else {
|
} else {
|
||||||
|
invalidatePendingSetRef(rawRef)
|
||||||
doSet()
|
doSet()
|
||||||
}
|
}
|
||||||
} else if (__DEV__) {
|
} 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
|
const RECURSION_LIMIT = 100
|
||||||
type CountMap = Map<SchedulerJob, number>
|
type CountMap = Map<SchedulerJob, number>
|
||||||
|
|
||||||
export function nextTick<T = void, R = void>(
|
export function nextTick(): Promise<void>
|
||||||
|
export function nextTick<T, R>(
|
||||||
this: T,
|
this: T,
|
||||||
fn?: (this: T) => R,
|
fn: (this: T) => R | Promise<R>,
|
||||||
): Promise<Awaited<R>> {
|
): Promise<R>
|
||||||
|
export function nextTick<T, R>(
|
||||||
|
this: T,
|
||||||
|
fn?: (this: T) => R | Promise<R>,
|
||||||
|
): Promise<void | R> {
|
||||||
const p = currentFlushPromise || resolvedPromise
|
const p = currentFlushPromise || resolvedPromise
|
||||||
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
return fn ? p.then(this ? fn.bind(this) : fn) : p
|
||||||
}
|
}
|
||||||
|
|
|
@ -196,6 +196,7 @@ export interface VNode<
|
||||||
|
|
||||||
// DOM
|
// DOM
|
||||||
el: HostNode | null
|
el: HostNode | null
|
||||||
|
placeholder: HostNode | null // async component el placeholder
|
||||||
anchor: HostNode | null // fragment anchor
|
anchor: HostNode | null // fragment anchor
|
||||||
target: HostElement | null // teleport target
|
target: HostElement | null // teleport target
|
||||||
targetStart: HostNode | null // teleport target start anchor
|
targetStart: HostNode | null // teleport target start anchor
|
||||||
|
@ -711,6 +712,8 @@ export function cloneVNode<T, U>(
|
||||||
suspense: vnode.suspense,
|
suspense: vnode.suspense,
|
||||||
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
|
ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
|
||||||
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
|
ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
|
||||||
|
placeholder: vnode.placeholder,
|
||||||
|
|
||||||
el: vnode.el,
|
el: vnode.el,
|
||||||
anchor: vnode.anchor,
|
anchor: vnode.anchor,
|
||||||
ctx: vnode.ctx,
|
ctx: vnode.ctx,
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue