mirror of https://github.com/vuejs/core.git
Compare commits
145 Commits
v3.6.0-alp
...
main
| Author | SHA1 | Date |
|---|---|---|
|
|
e9c676ff2b | |
|
|
e131369833 | |
|
|
90ce838a94 | |
|
|
11ec51aa5a | |
|
|
5cf0097f33 | |
|
|
f411c6604c | |
|
|
dc4dd594fb | |
|
|
40c4b2a876 | |
|
|
e6544ac292 | |
|
|
75d44c7189 | |
|
|
dcc6f36257 | |
|
|
8fbe48fe39 | |
|
|
6cbdf7823b | |
|
|
006a0c1011 | |
|
|
b8aab3d209 | |
|
|
84ca349fef | |
|
|
8ca2b3fbb7 | |
|
|
5689884c8e | |
|
|
b3cca2611c | |
|
|
8ec7cb12e4 | |
|
|
c13e674fb9 | |
|
|
1df8990504 | |
|
|
d715e5f6f1 | |
|
|
475539c154 | |
|
|
cd7c9a371c | |
|
|
c35e880f7f | |
|
|
90d3ff4dec | |
|
|
7065cee4fd | |
|
|
f00e5c7885 | |
|
|
2d65306949 | |
|
|
45547e69b2 | |
|
|
079010a38c | |
|
|
2dbe30177f | |
|
|
c16f8a94c7 | |
|
|
5a8aa0b2ba | |
|
|
1be5ddfe87 | |
|
|
d44a5a98c8 | |
|
|
c8a99172cc | |
|
|
b46481a47f | |
|
|
8593647e37 | |
|
|
f2487d86ea | |
|
|
b374ec7ca9 | |
|
|
9612b95220 | |
|
|
5953c9ff90 | |
|
|
565741a9b2 | |
|
|
47e628df1c | |
|
|
6b68f72673 | |
|
|
8bb8fb2362 | |
|
|
c4a88cdd0d | |
|
|
e388f1a09f | |
|
|
fda47ac702 | |
|
|
5e1e791880 | |
|
|
95c1975604 | |
|
|
4b7170625d | |
|
|
9c279517b9 | |
|
|
aba7feda17 | |
|
|
ba7f7f90f6 | |
|
|
5358bca4a8 | |
|
|
836b82976f | |
|
|
8620a616eb | |
|
|
2078f8b756 | |
|
|
abd563822a | |
|
|
b555f02eed | |
|
|
8c1f61d050 | |
|
|
e5a6fe42ea | |
|
|
75220c7995 | |
|
|
4b6cb1f52a | |
|
|
5d75a170c8 | |
|
|
55922ff316 | |
|
|
1e8b65aa49 | |
|
|
f2699a5cb3 | |
|
|
99d54b28b4 | |
|
|
15fc75f403 | |
|
|
4810f1489f | |
|
|
7171defb45 | |
|
|
26bce3dc6c | |
|
|
842a392ae5 | |
|
|
1392734ae5 | |
|
|
8696e346b4 | |
|
|
93ba107672 | |
|
|
00978f7d14 | |
|
|
ef20b86b36 | |
|
|
35da3c6dcb | |
|
|
8f6b505051 | |
|
|
e322436887 | |
|
|
d11cdd4a01 | |
|
|
ce9e6d1f4c | |
|
|
bbf0f4cc44 | |
|
|
a28794edfa | |
|
|
63279661e8 | |
|
|
233b1250ce | |
|
|
24fccb4ee4 | |
|
|
3aa782df38 | |
|
|
1031e8de08 | |
|
|
0f916d8c39 | |
|
|
952886e299 | |
|
|
a48ffdad65 | |
|
|
cde15b07bf | |
|
|
20b888bd59 | |
|
|
0a202d890f | |
|
|
d9dd628800 | |
|
|
4a2953f57b | |
|
|
19a0cbd431 | |
|
|
40d8d61c64 | |
|
|
5bdb2b4693 | |
|
|
be7c7e57ac | |
|
|
40654d4aa4 | |
|
|
10edfb5fc0 | |
|
|
2a0382ca7a | |
|
|
5eed143dd1 | |
|
|
a8713159ee | |
|
|
0562548ab3 | |
|
|
d7283f3b7f | |
|
|
3190b179b0 | |
|
|
7f60ef83e7 | |
|
|
6e5143d963 | |
|
|
1498821ed9 | |
|
|
439e1a543e | |
|
|
7420564b20 | |
|
|
8963b7979a | |
|
|
c875019d49 | |
|
|
31f798581c | |
|
|
911e67045e | |
|
|
c486536105 | |
|
|
7343f7c95f | |
|
|
8cfc10a80b | |
|
|
7f2994393d | |
|
|
9b029239ed | |
|
|
d8e40ef7e1 | |
|
|
90573b06bf | |
|
|
c5f7db1154 | |
|
|
a9269c642b | |
|
|
00695a5b41 | |
|
|
da1f8d7987 | |
|
|
0b6616a9c1 | |
|
|
42b272da57 | |
|
|
e60edc06f2 | |
|
|
21b685ad9d | |
|
|
ce933390ad | |
|
|
d3af67e878 | |
|
|
e0e8221d7f | |
|
|
347ef1d3f5 | |
|
|
f97c4d4e6e | |
|
|
a0bd1f518e | |
|
|
01a122283f |
|
|
@ -11,13 +11,13 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
|
|
|||
|
|
@ -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,13 +20,13 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ jobs:
|
|||
if: github.repository == 'vuejs/core' && github.event.issue.pull_request && startsWith(github.event.comment.body, '/ecosystem-ci run')
|
||||
steps:
|
||||
- name: Check user permission
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
script: |
|
||||
const user = context.payload.sender.login
|
||||
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
throw new Error('not allowed')
|
||||
}
|
||||
- name: Get PR info
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: get-pr-data
|
||||
with:
|
||||
script: |
|
||||
|
|
@ -62,7 +62,7 @@ jobs:
|
|||
commit: pr.head.sha
|
||||
}
|
||||
- name: Trigger run
|
||||
uses: actions/github-script@v7
|
||||
uses: actions/github-script@v8
|
||||
id: trigger
|
||||
env:
|
||||
COMMENT: ${{ github.event.comment.body }}
|
||||
|
|
|
|||
|
|
@ -21,13 +21,13 @@ jobs:
|
|||
environment: Release
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
|
|
@ -36,12 +36,13 @@ jobs:
|
|||
- name: Install deps
|
||||
run: pnpm install
|
||||
|
||||
- name: Update npm
|
||||
run: npm i -g npm@latest
|
||||
|
||||
- name: Build and publish
|
||||
id: publish
|
||||
run: |
|
||||
pnpm release --publishOnly
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
- name: Create GitHub release
|
||||
id: release_tag
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
|
|
@ -45,7 +45,7 @@ jobs:
|
|||
echo ${{ github.base_ref }} > ./temp/size/base.txt
|
||||
|
||||
- name: Upload Size Data
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: size-data
|
||||
path: temp/size
|
||||
|
|
|
|||
|
|
@ -22,13 +22,13 @@ jobs:
|
|||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: pnpm
|
||||
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
run: pnpm install
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
|
|
@ -56,7 +56,7 @@ jobs:
|
|||
path: temp/size/base.txt
|
||||
|
||||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v9
|
||||
uses: dawidd6/action-download-artifact@v11
|
||||
with:
|
||||
branch: ${{ steps.pr-base.outputs.content }}
|
||||
workflow: size-data.yml
|
||||
|
|
|
|||
|
|
@ -11,13 +11,13 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
@ -32,13 +32,13 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
@ -54,7 +54,7 @@ jobs:
|
|||
e2e-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Setup cache for Chromium binary
|
||||
uses: actions/cache@v4
|
||||
|
|
@ -63,10 +63,10 @@ jobs:
|
|||
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
@ -85,13 +85,13 @@ jobs:
|
|||
env:
|
||||
PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v4.1.0
|
||||
uses: pnpm/action-setup@v4.2.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
cache: 'pnpm'
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
"cSpell.enabledLanguageIds": ["markdown", "plaintext", "text", "yml"],
|
||||
|
||||
// Use prettier to format typescript, javascript and JSON files
|
||||
// Use prettier to format TypeScript, JavaScript and JSON files
|
||||
"[typescript]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
|
|
|
|||
143
CHANGELOG.md
143
CHANGELOG.md
|
|
@ -1,3 +1,144 @@
|
|||
## [3.5.24](https://github.com/vuejs/core/compare/v3.5.23...v3.5.24) (2025-11-07)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix(compiler-core): correctly handle ts type assertions in expression…" (#14062) ([11ec51a](https://github.com/vuejs/core/commit/11ec51aa5a7914745fee10ed2b9f9464fab4d02c)), closes [#14062](https://github.com/vuejs/core/issues/14062) [#14060](https://github.com/vuejs/core/issues/14060)
|
||||
|
||||
|
||||
|
||||
## [3.5.23](https://github.com/vuejs/core/compare/v3.5.22...v3.5.23) (2025-11-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** correctly handle ts type assertions in expressions ([#13397](https://github.com/vuejs/core/issues/13397)) ([e6544ac](https://github.com/vuejs/core/commit/e6544ac292b5b473274f87cdb83ebeac3e7e61a4)), closes [#13395](https://github.com/vuejs/core/issues/13395)
|
||||
* **compiler-core:** fix v-bind shorthand handling for in-DOM templates ([#13933](https://github.com/vuejs/core/issues/13933)) ([b3cca26](https://github.com/vuejs/core/commit/b3cca2611c656b85f0c4e737b9ec248d2627dded)), closes [#13930](https://github.com/vuejs/core/issues/13930)
|
||||
* **compiler-sfc:** resolve numeric literals and template literals without expressions as static property key ([#13998](https://github.com/vuejs/core/issues/13998)) ([75d44c7](https://github.com/vuejs/core/commit/75d44c718981f91843e197265cc68e82fe2532dd))
|
||||
* **compiler-ssr:** textarea with v-text directive SSR ([#13975](https://github.com/vuejs/core/issues/13975)) ([006a0c1](https://github.com/vuejs/core/commit/006a0c1011a224bcbf21195c6df76812c3a7e757))
|
||||
* **compiler:** using guard instead of non-nullish assertion ([#13982](https://github.com/vuejs/core/issues/13982)) ([dcc6f36](https://github.com/vuejs/core/commit/dcc6f362577ed86ccad31c2623c6cf75137dd27a))
|
||||
* **custom-element:** batch custom element prop patching ([#13478](https://github.com/vuejs/core/issues/13478)) ([c13e674](https://github.com/vuejs/core/commit/c13e674fb9f92ab9339d28a862d18de460faf56e)), closes [#12619](https://github.com/vuejs/core/issues/12619)
|
||||
* **custom-element:** optimize slot retrieval to avoid duplicates ([#13961](https://github.com/vuejs/core/issues/13961)) ([84ca349](https://github.com/vuejs/core/commit/84ca349fef73f6f55fc98299fcfa5c1eeef721db)), closes [#13955](https://github.com/vuejs/core/issues/13955)
|
||||
* **hydration:** avoid mismatch during hydrate text with newlines in interpolation ([#9232](https://github.com/vuejs/core/issues/9232)) ([6cbdf78](https://github.com/vuejs/core/commit/6cbdf7823b0c961190bee5b7c117b7f2bbeb832f)), closes [#9229](https://github.com/vuejs/core/issues/9229)
|
||||
* **runtime-core:** pass props and children to loadingComponent ([#13997](https://github.com/vuejs/core/issues/13997)) ([40c4b2a](https://github.com/vuejs/core/commit/40c4b2a876ce606973521dfc3024e26bfc10953a))
|
||||
* **runtime-dom:** ensure iframe sandbox is handled as an attribute to prevent unintended behavior ([#13950](https://github.com/vuejs/core/issues/13950)) ([5689884](https://github.com/vuejs/core/commit/5689884c8e32cda6a802ac36b4d23218f67b38ed)), closes [#13946](https://github.com/vuejs/core/issues/13946)
|
||||
* **suspense:** clear placeholder and fallback el after resolve to enable GC ([#13928](https://github.com/vuejs/core/issues/13928)) ([f411c66](https://github.com/vuejs/core/commit/f411c6604c12c531883aa0d30b81a7f69092f8a6))
|
||||
* **transition-group:** use offsetLeft and offsetTop instead of getBoundingClientRect to avoid transform scale affect animation ([#6108](https://github.com/vuejs/core/issues/6108)) ([dc4dd59](https://github.com/vuejs/core/commit/dc4dd594fbecce6ed7f44ffa69dc8b5d022287b6)), closes [#6105](https://github.com/vuejs/core/issues/6105)
|
||||
* **v-model:** handle number modifier on change ([#13959](https://github.com/vuejs/core/issues/13959)) ([8fbe48f](https://github.com/vuejs/core/commit/8fbe48fe396d830999afd07f9413d899157d5f5e)), closes [#13958](https://github.com/vuejs/core/issues/13958)
|
||||
|
||||
|
||||
|
||||
## [3.5.22](https://github.com/vuejs/core/compare/v3.5.21...v3.5.22) (2025-09-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** identifiers in switch-case should not be inferred as references ([#13923](https://github.com/vuejs/core/issues/13923)) ([5953c9f](https://github.com/vuejs/core/commit/5953c9ff90090e128372f645d377bd99137a5fb4))
|
||||
* **compiler-dom:** nodes with v-once shouldn't be stringified ([#13878](https://github.com/vuejs/core/issues/13878)) ([95c1975](https://github.com/vuejs/core/commit/95c197560409f5d39a0d376c0a43d89a47a604e8))
|
||||
* **compiler-sfc:** add support for `@vue-ignore` in runtime type resolution ([#13906](https://github.com/vuejs/core/issues/13906)) ([ba7f7f9](https://github.com/vuejs/core/commit/ba7f7f90f689f6e7e0417a192d081db542de28ec))
|
||||
* **compiler-sfc:** enhance inferRuntimeType to support TSMappedType with indexed access ([#13848](https://github.com/vuejs/core/issues/13848)) ([e388f1a](https://github.com/vuejs/core/commit/e388f1a09fde78cf006450f060813d972ac8c23d)), closes [#13847](https://github.com/vuejs/core/issues/13847)
|
||||
* **compiler-sfc:** ensure css custom properties do not start with a digit ([#13870](https://github.com/vuejs/core/issues/13870)) ([9c27951](https://github.com/vuejs/core/commit/9c279517b9bc1f4c250c555ec9b9eb6104756d56))
|
||||
* **compiler-sfc:** ensure props bindings register before compiling template ([#13922](https://github.com/vuejs/core/issues/13922)) ([abd5638](https://github.com/vuejs/core/commit/abd563822abafe63047f7b599bff266380ee2b64)), closes [#13920](https://github.com/vuejs/core/issues/13920)
|
||||
* **compiler-ssr:** ensure v-show has a higher priority in SSR ([#12171](https://github.com/vuejs/core/issues/12171)) ([836b829](https://github.com/vuejs/core/commit/836b82976ffb7aa0ea9cbe417bef07deae3ca47c)), closes [#12162](https://github.com/vuejs/core/issues/12162)
|
||||
* **custom-element:** properly mount multiple Teleports in custom element component w/ shadowRoot false ([#13900](https://github.com/vuejs/core/issues/13900)) ([5e1e791](https://github.com/vuejs/core/commit/5e1e791880238380a1038ae2c505e206ceb34d77)), closes [#13899](https://github.com/vuejs/core/issues/13899)
|
||||
* **custom-element:** set prop runs pending mutations before disconnect ([#13897](https://github.com/vuejs/core/issues/13897)) ([c4a88cd](https://github.com/vuejs/core/commit/c4a88cdd0dfed3ef46a8aa9be448c01781fdc4f0)), closes [#13315](https://github.com/vuejs/core/issues/13315)
|
||||
* **custom-element:** use `PatchFlags.BAIL` for slot when props are present ([#13907](https://github.com/vuejs/core/issues/13907)) ([5358bca](https://github.com/vuejs/core/commit/5358bca4a80cf52d19ed91967eeaa025a786083d)), closes [#13904](https://github.com/vuejs/core/issues/13904)
|
||||
* **reactivity:** respect readonly during ref unwrapping ([#13905](https://github.com/vuejs/core/issues/13905)) ([aba7fed](https://github.com/vuejs/core/commit/aba7feda1703e69e5a7c37f784718de0371adadc)), closes [#13903](https://github.com/vuejs/core/issues/13903)
|
||||
* **reactivity:** update iterator to check for completion instead of value presence ([#13761](https://github.com/vuejs/core/issues/13761)) ([2078f8b](https://github.com/vuejs/core/commit/2078f8b7565cf637f47fcd5b0abdfb2b264225bb))
|
||||
* **runtime-core:** simplify block-tracking disabling in `h` helper ([#13841](https://github.com/vuejs/core/issues/13841)) ([75220c7](https://github.com/vuejs/core/commit/75220c7995a13a483ae9599a739075be1c8e17f8))
|
||||
* **transition-group:** run `forceReflow` on the correct document (fix [#13849](https://github.com/vuejs/core/issues/13849)) ([#13853](https://github.com/vuejs/core/issues/13853)) ([1be5ddf](https://github.com/vuejs/core/commit/1be5ddfe878c8bfddaa2c50e82105b247f50b9ba))
|
||||
* **types:** more precise types for Events and added missing definitions ([#9675](https://github.com/vuejs/core/issues/9675)) ([8bb8fb2](https://github.com/vuejs/core/commit/8bb8fb236257c03bfa0bccadcfffe3eb4592f71b))
|
||||
* **types:** set dom stub type to `never` instead of `{}` ([#13915](https://github.com/vuejs/core/issues/13915)) ([8620a61](https://github.com/vuejs/core/commit/8620a616eb02a64fe32dd52d9be68e360687ef9d)), closes [#11564](https://github.com/vuejs/core/issues/11564)
|
||||
* **types:** widen directive arg type from string to any ([#13758](https://github.com/vuejs/core/issues/13758)) ([4b71706](https://github.com/vuejs/core/commit/4b7170625d0bc93b26a3343aeda98850c1138f82)), closes [#13757](https://github.com/vuejs/core/issues/13757)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **custom-element:** allow specifying additional options for `shadowRoot` in custom elements ([#12965](https://github.com/vuejs/core/issues/12965)) ([47e628d](https://github.com/vuejs/core/commit/47e628df1ce1914c5677010ad5bddd18d037cb3c)), closes [#12964](https://github.com/vuejs/core/issues/12964)
|
||||
|
||||
|
||||
### Reverts
|
||||
|
||||
* Revert "fix(hmr): prevent __VUE_HMR_RUNTIME__ from being overwritten by vue runtime in 3rd-party libraries" (#13925) ([6b68f72](https://github.com/vuejs/core/commit/6b68f72673dac5db349f26eeefb2f2e0e342586b)), closes [#13925](https://github.com/vuejs/core/issues/13925)
|
||||
|
||||
|
||||
|
||||
## [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)
|
||||
|
||||
|
||||
|
|
@ -253,7 +394,7 @@
|
|||
|
||||
* **compiler-core:** fix handling of delimiterOpen in VPre ([#11915](https://github.com/vuejs/core/issues/11915)) ([706d4ac](https://github.com/vuejs/core/commit/706d4ac1d0210b2d9134b3228280187fe02fc971)), closes [#11913](https://github.com/vuejs/core/issues/11913)
|
||||
* **compiler-dom:** fix stringify static edge for partially eligible chunks in cached parent ([1d99d61](https://github.com/vuejs/core/commit/1d99d61c1bd77f9ea6743f6214a82add8346a121)), closes [#11879](https://github.com/vuejs/core/issues/11879) [#11890](https://github.com/vuejs/core/issues/11890)
|
||||
* **compiler-dom:** should ignore leading newline in <textarea> per spec ([3c4bf76](https://github.com/vuejs/core/commit/3c4bf7627649ec1e3220f8c4e4163c20d2afb367))
|
||||
* **compiler-dom:** should ignore leading newline in `<textarea>` per spec ([3c4bf76](https://github.com/vuejs/core/commit/3c4bf7627649ec1e3220f8c4e4163c20d2afb367))
|
||||
* **compiler-sfc:** nested css supports atrule and comment ([#11899](https://github.com/vuejs/core/issues/11899)) ([0e7bc71](https://github.com/vuejs/core/commit/0e7bc717e6640644f062957ec5031506f0dab215)), closes [#11896](https://github.com/vuejs/core/issues/11896)
|
||||
* **custom-element:** handle nested customElement mount w/ shadowRoot false ([#11861](https://github.com/vuejs/core/issues/11861)) ([f2d8019](https://github.com/vuejs/core/commit/f2d801918841e7673ff3f048d0d895592a2f7e23)), closes [#11851](https://github.com/vuejs/core/issues/11851) [#11871](https://github.com/vuejs/core/issues/11871)
|
||||
* **hmr:** reload async child wrapped in Suspense + KeepAlive ([#11907](https://github.com/vuejs/core/issues/11907)) ([10a2c60](https://github.com/vuejs/core/commit/10a2c6053bd30d160d0214bb3566f540187e6874)), closes [#11868](https://github.com/vuejs/core/issues/11868)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import importX from 'eslint-plugin-import-x'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig } from 'eslint/config'
|
||||
import vitest from '@vitest/eslint-plugin'
|
||||
import { builtinModules } from 'node:module'
|
||||
|
||||
|
|
@ -12,7 +13,7 @@ const banConstEnum = {
|
|||
'Please use non-const enums. This project automatically inlines enums.',
|
||||
}
|
||||
|
||||
export default tseslint.config(
|
||||
export default defineConfig(
|
||||
{
|
||||
files: ['**/*.js', '**/*.ts', '**/*.tsx'],
|
||||
extends: [tseslint.configs.base],
|
||||
|
|
@ -60,7 +61,10 @@ export default tseslint.config(
|
|||
],
|
||||
// This rule enforces the preference for using '@ts-expect-error' comments in TypeScript
|
||||
// code to indicate intentional type errors, improving code clarity and maintainability.
|
||||
'@typescript-eslint/prefer-ts-expect-error': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
'error',
|
||||
{ minimumDescriptionLength: 0 },
|
||||
],
|
||||
// Enforce the use of 'import type' for importing types
|
||||
'@typescript-eslint/consistent-type-imports': [
|
||||
'error',
|
||||
|
|
|
|||
46
package.json
46
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.17",
|
||||
"packageManager": "pnpm@10.12.4",
|
||||
"version": "3.5.24",
|
||||
"packageManager": "pnpm@10.20.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
|
|
@ -17,11 +17,11 @@
|
|||
"format": "prettier --write --cache .",
|
||||
"format-check": "prettier --check --cache .",
|
||||
"test": "vitest",
|
||||
"test-unit": "vitest --project unit",
|
||||
"test-unit": "vitest --project unit*",
|
||||
"test-e2e": "node scripts/build.js vue -f global -d && vitest --project e2e",
|
||||
"test-dts": "run-s build-dts test-dts-only",
|
||||
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
||||
"test-coverage": "vitest run --project unit --coverage",
|
||||
"test-coverage": "vitest run --project unit* --coverage",
|
||||
"prebench": "node scripts/build.js -pf esm-browser reactivity",
|
||||
"prebench-compare": "node scripts/build.js -pf esm-browser reactivity",
|
||||
"bench": "vitest bench --project=unit --outputJson=temp/bench.json",
|
||||
|
|
@ -65,44 +65,44 @@
|
|||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.6",
|
||||
"@rollup/plugin-commonjs": "^28.0.9",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||
"@rollup/plugin-node-resolve": "^16.0.3",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.12.9",
|
||||
"@swc/core": "^1.14.0",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^22.16.0",
|
||||
"@types/semver": "^7.7.0",
|
||||
"@types/node": "^22.19.0",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitest/coverage-v8": "^3.1.4",
|
||||
"@vitest/eslint-plugin": "^1.2.1",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@vitest/eslint-plugin": "^1.4.0",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.25.5",
|
||||
"esbuild": "^0.25.12",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.27.0",
|
||||
"eslint-plugin-import-x": "^4.13.1",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^26.1.0",
|
||||
"jsdom": "^27.1.0",
|
||||
"lint-staged": "^16.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.17",
|
||||
"magic-string": "^0.30.21",
|
||||
"markdown-table": "^3.0.4",
|
||||
"marked": "13.0.3",
|
||||
"npm-run-all2": "^7.0.2",
|
||||
"npm-run-all2": "^8.0.4",
|
||||
"picocolors": "^1.1.1",
|
||||
"prettier": "^3.5.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pretty-bytes": "^7.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~24.9.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.44.1",
|
||||
"rollup-plugin-dts": "^6.2.1",
|
||||
"puppeteer": "~24.28.0",
|
||||
"rimraf": "^6.1.0",
|
||||
"rollup": "^4.52.5",
|
||||
"rollup-plugin-dts": "^6.2.3",
|
||||
"rollup-plugin-esbuild": "^6.2.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.7.2",
|
||||
"serve": "^14.2.4",
|
||||
"semver": "^7.7.3",
|
||||
"serve": "^14.2.5",
|
||||
"serve-handler": "^6.1.6",
|
||||
"simple-git-hooks": "^2.13.0",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
|
|
@ -110,6 +110,6 @@
|
|||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^3.1.4"
|
||||
"vitest": "^3.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ type ExtractBinding<T> = T extends (
|
|||
declare function testDirective<
|
||||
Value,
|
||||
Modifiers extends string = string,
|
||||
Arg extends string = string,
|
||||
Arg = any,
|
||||
>(): ExtractBinding<Directive<any, Value, Modifiers, Arg>>
|
||||
|
||||
describe('vmodel', () => {
|
||||
|
|
@ -44,7 +44,7 @@ describe('custom', () => {
|
|||
value: number
|
||||
oldValue: number | null
|
||||
arg?: 'Arg'
|
||||
modifiers: Record<'a' | 'b', boolean>
|
||||
modifiers: Partial<Record<'a' | 'b', boolean>>
|
||||
// @ts-expect-error
|
||||
}>(testDirective<number, 'a' | 'b', 'Argx'>())
|
||||
|
||||
|
|
@ -52,7 +52,29 @@ describe('custom', () => {
|
|||
value: number
|
||||
oldValue: number | null
|
||||
arg?: 'Arg'
|
||||
modifiers: Record<'a' | 'b', boolean>
|
||||
modifiers: Partial<Record<'a' | 'b', boolean>>
|
||||
// @ts-expect-error
|
||||
}>(testDirective<string, 'a' | 'b', 'Arg'>())
|
||||
|
||||
expectType<{
|
||||
value: number
|
||||
oldValue: number | null
|
||||
arg?: HTMLElement
|
||||
modifiers: Partial<Record<'a' | 'b', boolean>>
|
||||
}>(testDirective<number, 'a' | 'b', HTMLElement>())
|
||||
|
||||
expectType<{
|
||||
value: number
|
||||
oldValue: number | null
|
||||
arg?: HTMLElement
|
||||
modifiers: Partial<Record<'a' | 'b', boolean>>
|
||||
// @ts-expect-error
|
||||
}>(testDirective<number, 'a' | 'b', string>())
|
||||
|
||||
expectType<{
|
||||
value: number
|
||||
oldValue: number | null
|
||||
arg?: HTMLElement
|
||||
modifiers: Partial<Record<'a' | 'b', boolean>>
|
||||
}>(testDirective<number, 'a' | 'b'>())
|
||||
})
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
import { nextTick } from 'vue'
|
||||
import { describe, expectType } from './utils'
|
||||
|
||||
describe('nextTick', async () => {
|
||||
expectType<Promise<void>>(nextTick())
|
||||
expectType<Promise<string>>(nextTick(() => 'foo'))
|
||||
expectType<Promise<string>>(nextTick(() => Promise.resolve('foo')))
|
||||
expectType<Promise<string>>(
|
||||
nextTick(() => Promise.resolve(Promise.resolve('foo'))),
|
||||
)
|
||||
|
||||
expectType<void>(await nextTick())
|
||||
expectType<string>(await nextTick(() => 'foo'))
|
||||
expectType<string>(await nextTick(() => Promise.resolve('foo')))
|
||||
expectType<string>(
|
||||
await nextTick(() => Promise.resolve(Promise.resolve('foo'))),
|
||||
)
|
||||
|
||||
nextTick().then(value => {
|
||||
expectType<void>(value)
|
||||
})
|
||||
nextTick(() => 'foo').then(value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
nextTick(() => Promise.resolve('foo')).then(value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
nextTick(() => Promise.resolve(Promise.resolve('foo'))).then(value => {
|
||||
expectType<string>(value)
|
||||
})
|
||||
})
|
||||
|
|
@ -13,7 +13,7 @@
|
|||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.6.1",
|
||||
"@vue/repl": "^4.7.0",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
import Header from './Header.vue'
|
||||
import { Repl, useStore, SFCOptions, useVueImportMap } from '@vue/repl'
|
||||
import Monaco from '@vue/repl/monaco-editor'
|
||||
import { ref, watchEffect, onMounted, computed } from 'vue'
|
||||
import { ref, watchEffect, onMounted, computed, watch } from 'vue'
|
||||
|
||||
const replRef = ref<InstanceType<typeof Repl>>()
|
||||
|
||||
|
|
@ -115,6 +115,34 @@ onMounted(() => {
|
|||
// @ts-expect-error process shim for old versions of @vue/compiler-sfc dependency
|
||||
window.process = { env: {} }
|
||||
})
|
||||
|
||||
const isVaporSupported = ref(false)
|
||||
watch(
|
||||
() => store.vueVersion,
|
||||
(version, oldVersion) => {
|
||||
const [major, minor] = (version || store.compiler.version)
|
||||
.split('.')
|
||||
.map((v: string) => parseInt(v, 10))
|
||||
isVaporSupported.value = major > 3 || (major === 3 && minor >= 6)
|
||||
if (oldVersion) reloadPage()
|
||||
},
|
||||
{ immediate: true, flush: 'pre' },
|
||||
)
|
||||
|
||||
const previewOptions = computed(() => ({
|
||||
customCode: {
|
||||
importCode: `import { initCustomFormatter${isVaporSupported.value ? ', vaporInteropPlugin' : ''} } from 'vue'`,
|
||||
useCode: `
|
||||
${isVaporSupported.value ? 'app.use(vaporInteropPlugin)' : ''}
|
||||
if (window.devtoolsFormatters) {
|
||||
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
||||
window.devtoolsFormatters.splice(index, 1)
|
||||
initCustomFormatter()
|
||||
} else {
|
||||
initCustomFormatter()
|
||||
}`,
|
||||
},
|
||||
}))
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
@ -145,18 +173,7 @@ onMounted(() => {
|
|||
:showOpenSourceMap="true"
|
||||
:autoResize="true"
|
||||
:clearConsole="false"
|
||||
:preview-options="{
|
||||
customCode: {
|
||||
importCode: `import { initCustomFormatter } from 'vue'`,
|
||||
useCode: `if (window.devtoolsFormatters) {
|
||||
const index = window.devtoolsFormatters.findIndex((v) => v.__vue_custom_formatter)
|
||||
window.devtoolsFormatters.splice(index, 1)
|
||||
initCustomFormatter()
|
||||
} else {
|
||||
initCustomFormatter()
|
||||
}`,
|
||||
},
|
||||
}"
|
||||
:preview-options="previewOptions"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"vue": "latest"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.2.4",
|
||||
"vite": "^6.3.5"
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"vite": "^7.1.12"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
"enableNonBrowserBranches": true
|
||||
},
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.52.2",
|
||||
"monaco-editor": "^0.54.0",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -21,7 +21,7 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
|
|
@ -30,7 +30,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -42,11 +42,11 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createCommentVNode("comment")
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -58,11 +58,11 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||
_createTextVNode("foo"),
|
||||
_createTextVNode("foo", -1 /* CACHED */),
|
||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -74,9 +74,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -147,9 +147,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -161,9 +161,9 @@ return function render(_ctx, _cache) {
|
|||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -215,9 +215,9 @@ return function render(_ctx, _cache) {
|
|||
const _directive_foo = _resolveDirective("foo")
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||
]))), [
|
||||
]))])), [
|
||||
[_directive_foo]
|
||||
])
|
||||
]))
|
||||
|
|
@ -401,9 +401,9 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
ok
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
: _createCommentVNode("v-if", true)
|
||||
]))
|
||||
}
|
||||
|
|
@ -422,7 +422,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_createCommentVNode("comment"),
|
||||
_createElementVNode("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", { id: "b" }, [
|
||||
_createElementVNode("div", { id: "c" }, [
|
||||
_createElementVNode("div", { id: "d" }, [
|
||||
|
|
@ -430,7 +430,7 @@ return function render(_ctx, _cache) {
|
|||
])
|
||||
])
|
||||
], -1 /* CACHED */)
|
||||
]))
|
||||
]))])
|
||||
], 2112 /* STABLE_FRAGMENT, DEV_ROOT_FRAGMENT */))
|
||||
}
|
||||
}"
|
||||
|
|
@ -448,9 +448,9 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ import { PatchFlags } from '@vue/shared'
|
|||
|
||||
const cachedChildrenArrayMatcher = (
|
||||
tags: string[],
|
||||
needArraySpread = false,
|
||||
needArraySpread = true,
|
||||
) => ({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
needArraySpread,
|
||||
|
|
@ -170,11 +170,6 @@ describe('compiler: cacheStatic transform', () => {
|
|||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
key: { content: '__' },
|
||||
value: { content: '[0]' },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
|
@ -202,11 +197,6 @@ describe('compiler: cacheStatic transform', () => {
|
|||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_PROPERTY,
|
||||
key: { content: '__' },
|
||||
value: { content: '[0]' },
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -716,4 +716,42 @@ describe('compiler: expression transform', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('switch case variable declarations', () => {
|
||||
test('should handle const declarations in switch case without braces', () => {
|
||||
const { code } = compile(
|
||||
`{{ (() => { switch (1) { case 1: const foo = "bar"; return \`\${foo}\`; } })() }}`,
|
||||
)
|
||||
|
||||
expect(code).toMatch(`const foo = "bar";`)
|
||||
expect(code).toMatch(`return \`\${foo}\`;`)
|
||||
expect(code).not.toMatch(`_ctx.foo`)
|
||||
})
|
||||
|
||||
test('should handle const declarations in switch case with braces (existing behavior)', () => {
|
||||
const { code } = compile(
|
||||
`{{ (() => {
|
||||
switch (true) {
|
||||
case true: {
|
||||
const foo = "bar";
|
||||
return \`\${foo}\`;
|
||||
}
|
||||
}
|
||||
})() }}`,
|
||||
)
|
||||
|
||||
expect(code).toMatch(`const foo = "bar";`)
|
||||
expect(code).toMatch(`return \`\${foo}\`;`)
|
||||
expect(code).not.toMatch(`_ctx.foo`)
|
||||
})
|
||||
|
||||
test('should parse switch case test as local scoped variables', () => {
|
||||
const { code } = compile(
|
||||
`{{ (() => { switch (foo) { case bar: return \`\${bar}\`; } })() }}`,
|
||||
)
|
||||
|
||||
expect(code).toMatch('_ctx.foo')
|
||||
expect(code).toMatch(`_ctx.bar`)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
helperNameMap,
|
||||
} from '../../src/runtimeHelpers'
|
||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'
|
||||
|
||||
function parseWithVBind(
|
||||
template: string,
|
||||
|
|
@ -25,6 +26,7 @@ function parseWithVBind(
|
|||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [
|
||||
transformVBindShorthand,
|
||||
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||
transformElement,
|
||||
],
|
||||
|
|
@ -110,6 +112,27 @@ describe('compiler: transform v-bind', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('no expression (shorthand) in-DOM templates', () => {
|
||||
try {
|
||||
__BROWSER__ = true
|
||||
// :id in in-DOM templates will be parsed into :id="" by browser
|
||||
const node = parseWithVBind(`<div :id="" />`)
|
||||
const props = (node.codegenNode as VNodeCall).props as ObjectExpression
|
||||
expect(props.properties[0]).toMatchObject({
|
||||
key: {
|
||||
content: `id`,
|
||||
isStatic: true,
|
||||
},
|
||||
value: {
|
||||
content: `id`,
|
||||
isStatic: false,
|
||||
},
|
||||
})
|
||||
} finally {
|
||||
__BROWSER__ = false
|
||||
}
|
||||
})
|
||||
|
||||
test('dynamic arg', () => {
|
||||
const node = parseWithVBind(`<div v-bind:[id]="id"/>`)
|
||||
const props = (node.codegenNode as VNodeCall).props as CallExpression
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { type CompilerOptions, generate } from '../../src'
|
|||
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher } from '../testUtils'
|
||||
import { transformVBindShorthand } from '../../src/transforms/transformVBindShorthand'
|
||||
|
||||
export function parseWithForTransform(
|
||||
template: string,
|
||||
|
|
@ -32,6 +33,7 @@ export function parseWithForTransform(
|
|||
const ast = parse(template, options)
|
||||
transform(ast, {
|
||||
nodeTransforms: [
|
||||
transformVBindShorthand,
|
||||
transformIf,
|
||||
transformFor,
|
||||
...(options.prefixIdentifiers ? [transformExpression] : []),
|
||||
|
|
|
|||
|
|
@ -17,7 +17,12 @@ import {
|
|||
type VNodeCall,
|
||||
} from '../../src/ast'
|
||||
import { ErrorCodes } from '../../src/errors'
|
||||
import { type CompilerOptions, TO_HANDLERS, generate } from '../../src'
|
||||
import {
|
||||
type CompilerOptions,
|
||||
TO_HANDLERS,
|
||||
generate,
|
||||
transformVBindShorthand,
|
||||
} from '../../src'
|
||||
import {
|
||||
CREATE_COMMENT,
|
||||
FRAGMENT,
|
||||
|
|
@ -35,7 +40,12 @@ function parseWithIfTransform(
|
|||
) {
|
||||
const ast = parse(template, options)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformIf, transformSlotOutlet, transformElement],
|
||||
nodeTransforms: [
|
||||
transformVBindShorthand,
|
||||
transformIf,
|
||||
transformSlotOutlet,
|
||||
transformElement,
|
||||
],
|
||||
...options,
|
||||
})
|
||||
if (!options.onError) {
|
||||
|
|
@ -209,6 +219,16 @@ describe('compiler: v-if', () => {
|
|||
content: `_ctx.ok`,
|
||||
})
|
||||
})
|
||||
|
||||
//#11321
|
||||
test('v-if + :key shorthand', () => {
|
||||
const { node } = parseWithIfTransform(`<div v-if="ok" :key></div>`)
|
||||
expect(node.type).toBe(NodeTypes.IF)
|
||||
expect(node.branches[0].userKey).toMatchObject({
|
||||
arg: { content: 'key' },
|
||||
exp: { content: 'key' },
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
|
|
@ -301,6 +321,25 @@ describe('compiler: v-if', () => {
|
|||
])
|
||||
})
|
||||
|
||||
test('error on adjacent v-else', () => {
|
||||
const onError = vi.fn()
|
||||
|
||||
const {
|
||||
node: { branches },
|
||||
} = parseWithIfTransform(
|
||||
`<div v-if="false"/><div v-else/><div v-else/>`,
|
||||
{ onError },
|
||||
0,
|
||||
)
|
||||
|
||||
expect(onError.mock.calls[0]).toMatchObject([
|
||||
{
|
||||
code: ErrorCodes.X_V_ELSE_NO_ADJACENT_IF,
|
||||
loc: branches[branches.length - 1].loc,
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('error on user key', () => {
|
||||
const onError = vi.fn()
|
||||
// dynamic
|
||||
|
|
|
|||
|
|
@ -478,7 +478,10 @@ describe('compiler: transform component slots', () => {
|
|||
})
|
||||
|
||||
test('should only force dynamic slots when actually using scope vars w/ prefixIdentifiers: true', () => {
|
||||
function assertDynamicSlots(template: string, shouldForce: boolean) {
|
||||
function assertDynamicSlots(
|
||||
template: string,
|
||||
expectedPatchFlag?: PatchFlags,
|
||||
) {
|
||||
const { root } = parseWithSlots(template, { prefixIdentifiers: true })
|
||||
let flag: any
|
||||
if (root.children[0].type === NodeTypes.FOR) {
|
||||
|
|
@ -491,8 +494,8 @@ describe('compiler: transform component slots', () => {
|
|||
.children[0] as ComponentNode
|
||||
flag = (innerComp.codegenNode as VNodeCall).patchFlag
|
||||
}
|
||||
if (shouldForce) {
|
||||
expect(flag).toBe(PatchFlags.DYNAMIC_SLOTS)
|
||||
if (expectedPatchFlag) {
|
||||
expect(flag).toBe(expectedPatchFlag)
|
||||
} else {
|
||||
expect(flag).toBeUndefined()
|
||||
}
|
||||
|
|
@ -502,14 +505,13 @@ describe('compiler: transform component slots', () => {
|
|||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar">foo</Comp>
|
||||
</div>`,
|
||||
false,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar">{{ i }}</Comp>
|
||||
</div>`,
|
||||
true,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
// reference the component's own slot variable should not force dynamic slots
|
||||
|
|
@ -517,14 +519,13 @@ describe('compiler: transform component slots', () => {
|
|||
`<Comp v-slot="foo">
|
||||
<Comp v-slot="bar">{{ bar }}</Comp>
|
||||
</Comp>`,
|
||||
false,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<Comp v-slot="foo">
|
||||
<Comp v-slot="bar">{{ foo }}</Comp>
|
||||
</Comp>`,
|
||||
true,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
// #2564
|
||||
|
|
@ -532,14 +533,35 @@ describe('compiler: transform component slots', () => {
|
|||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar"><button @click="fn(i)" /></Comp>
|
||||
</div>`,
|
||||
true,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot="bar"><button @click="fn()" /></Comp>
|
||||
</div>`,
|
||||
false,
|
||||
)
|
||||
|
||||
// #9380
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp :i="i">foo</Comp>
|
||||
</div>`,
|
||||
PatchFlags.PROPS,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot="{ value = i }"><button @click="fn()" /></Comp>
|
||||
</div>`,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
|
||||
assertDynamicSlots(
|
||||
`<div v-for="i in list">
|
||||
<Comp v-slot:[i]><button @click="fn()" /></Comp>
|
||||
</div>`,
|
||||
PatchFlags.DYNAMIC_SLOTS,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,9 @@
|
|||
import type { ExpressionNode, TransformContext } from '../src'
|
||||
import { babelParse, walkIdentifiers } from '@vue/compiler-sfc'
|
||||
import {
|
||||
type ExpressionNode,
|
||||
type TransformContext,
|
||||
isReferencedIdentifier,
|
||||
} from '../src'
|
||||
import { type Position, createSimpleExpression } from '../src/ast'
|
||||
import {
|
||||
advancePositionWithClone,
|
||||
|
|
@ -115,3 +120,18 @@ test('toValidAssetId', () => {
|
|||
'_component_test_2797935797_1',
|
||||
)
|
||||
})
|
||||
|
||||
describe('isReferencedIdentifier', () => {
|
||||
test('identifiers in function parameters should not be inferred as references', () => {
|
||||
expect.assertions(4)
|
||||
const ast = babelParse(`(({ title }) => [])`)
|
||||
walkIdentifiers(
|
||||
ast.program.body[0],
|
||||
(node, parent, parentStack, isReference) => {
|
||||
expect(isReference).toBe(false)
|
||||
expect(isReferencedIdentifier(node, parent, parentStack)).toBe(false)
|
||||
},
|
||||
true,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.5.17",
|
||||
"version": "3.5.24",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import type {
|
|||
Node,
|
||||
ObjectProperty,
|
||||
Program,
|
||||
SwitchCase,
|
||||
SwitchStatement,
|
||||
} from '@babel/types'
|
||||
import { walk } from 'estree-walker'
|
||||
|
||||
|
|
@ -80,14 +82,31 @@ export function walkIdentifiers(
|
|||
markScopeIdentifier(node, id, knownIds),
|
||||
)
|
||||
}
|
||||
} else if (node.type === 'SwitchStatement') {
|
||||
if (node.scopeIds) {
|
||||
node.scopeIds.forEach(id => markKnownIds(id, knownIds))
|
||||
} else {
|
||||
// record switch case block-level local variables
|
||||
walkSwitchStatement(node, false, id =>
|
||||
markScopeIdentifier(node, id, knownIds),
|
||||
)
|
||||
}
|
||||
} else if (node.type === 'CatchClause' && node.param) {
|
||||
for (const id of extractIdentifiers(node.param)) {
|
||||
markScopeIdentifier(node, id, knownIds)
|
||||
if (node.scopeIds) {
|
||||
node.scopeIds.forEach(id => markKnownIds(id, knownIds))
|
||||
} else {
|
||||
for (const id of extractIdentifiers(node.param)) {
|
||||
markScopeIdentifier(node, id, knownIds)
|
||||
}
|
||||
}
|
||||
} else if (isForStatement(node)) {
|
||||
walkForStatement(node, false, id =>
|
||||
markScopeIdentifier(node, id, knownIds),
|
||||
)
|
||||
if (node.scopeIds) {
|
||||
node.scopeIds.forEach(id => markKnownIds(id, knownIds))
|
||||
} else {
|
||||
walkForStatement(node, false, id =>
|
||||
markScopeIdentifier(node, id, knownIds),
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
leave(node: Node & { scopeIds?: Set<string> }, parent: Node | null) {
|
||||
|
|
@ -122,7 +141,7 @@ export function isReferencedIdentifier(
|
|||
return false
|
||||
}
|
||||
|
||||
if (isReferenced(id, parent)) {
|
||||
if (isReferenced(id, parent, parentStack[parentStack.length - 2])) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -132,7 +151,8 @@ export function isReferencedIdentifier(
|
|||
case 'AssignmentExpression':
|
||||
case 'AssignmentPattern':
|
||||
return true
|
||||
case 'ObjectPattern':
|
||||
case 'ObjectProperty':
|
||||
return parent.key !== id && isInDestructureAssignment(parent, parentStack)
|
||||
case 'ArrayPattern':
|
||||
return isInDestructureAssignment(parent, parentStack)
|
||||
}
|
||||
|
|
@ -186,10 +206,11 @@ export function walkFunctionParams(
|
|||
}
|
||||
|
||||
export function walkBlockDeclarations(
|
||||
block: BlockStatement | Program,
|
||||
block: BlockStatement | SwitchCase | Program,
|
||||
onIdent: (node: Identifier) => void,
|
||||
): void {
|
||||
for (const stmt of block.body) {
|
||||
const body = block.type === 'SwitchCase' ? block.consequent : block.body
|
||||
for (const stmt of body) {
|
||||
if (stmt.type === 'VariableDeclaration') {
|
||||
if (stmt.declare) continue
|
||||
for (const decl of stmt.declarations) {
|
||||
|
|
@ -205,6 +226,8 @@ export function walkBlockDeclarations(
|
|||
onIdent(stmt.id)
|
||||
} else if (isForStatement(stmt)) {
|
||||
walkForStatement(stmt, true, onIdent)
|
||||
} else if (stmt.type === 'SwitchStatement') {
|
||||
walkSwitchStatement(stmt, true, onIdent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -238,6 +261,28 @@ function walkForStatement(
|
|||
}
|
||||
}
|
||||
|
||||
function walkSwitchStatement(
|
||||
stmt: SwitchStatement,
|
||||
isVar: boolean,
|
||||
onIdent: (id: Identifier) => void,
|
||||
) {
|
||||
for (const cs of stmt.cases) {
|
||||
for (const stmt of cs.consequent) {
|
||||
if (
|
||||
stmt.type === 'VariableDeclaration' &&
|
||||
(stmt.kind === 'var' ? isVar : !isVar)
|
||||
) {
|
||||
for (const decl of stmt.declarations) {
|
||||
for (const id of extractIdentifiers(decl.id)) {
|
||||
onIdent(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walkBlockDeclarations(cs, onIdent)
|
||||
}
|
||||
}
|
||||
|
||||
export function extractIdentifiers(
|
||||
param: Node,
|
||||
nodes: Identifier[] = [],
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ import { transformModel } from './transforms/vModel'
|
|||
import { transformFilter } from './compat/transformFilter'
|
||||
import { ErrorCodes, createCompilerError, defaultOnError } from './errors'
|
||||
import { transformMemo } from './transforms/vMemo'
|
||||
import { transformVBindShorthand } from './transforms/transformVBindShorthand'
|
||||
|
||||
export type TransformPreset = [
|
||||
NodeTransform[],
|
||||
|
|
@ -33,6 +34,7 @@ export function getBaseTransformPreset(
|
|||
): TransformPreset {
|
||||
return [
|
||||
[
|
||||
transformVBindShorthand,
|
||||
transformOnce,
|
||||
transformIf,
|
||||
transformMemo,
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ export {
|
|||
buildDirectiveArgs,
|
||||
type PropsExpression,
|
||||
} from './transforms/transformElement'
|
||||
export { transformVBindShorthand } from './transforms/transformVBindShorthand'
|
||||
export { processSlotOutlet } from './transforms/transformSlotOutlet'
|
||||
export { getConstantType } from './transforms/cacheStatic'
|
||||
export { generateCodeFrame } from '@vue/shared'
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ import {
|
|||
isCoreComponent,
|
||||
isSimpleIdentifier,
|
||||
isStaticArgOf,
|
||||
isVPre,
|
||||
} from './utils'
|
||||
import { decodeHTML } from 'entities/lib/decode.js'
|
||||
import {
|
||||
|
|
@ -246,7 +247,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
ondirarg(start, end) {
|
||||
if (start === end) return
|
||||
const arg = getSlice(start, end)
|
||||
if (inVPre) {
|
||||
if (inVPre && !isVPre(currentProp!)) {
|
||||
;(currentProp as AttributeNode).name += arg
|
||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||
} else {
|
||||
|
|
@ -262,7 +263,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
|
||||
ondirmodifier(start, end) {
|
||||
const mod = getSlice(start, end)
|
||||
if (inVPre) {
|
||||
if (inVPre && !isVPre(currentProp!)) {
|
||||
;(currentProp as AttributeNode).name += '.' + mod
|
||||
setLocEnd((currentProp as AttributeNode).nameLoc, end)
|
||||
} else if ((currentProp as DirectiveNode).name === 'slot') {
|
||||
|
|
@ -1053,7 +1054,7 @@ export function baseParse(input: string, options?: ParserOptions): RootNode {
|
|||
`[@vue/compiler-core] decodeEntities option is passed but will be ` +
|
||||
`ignored in non-browser builds.`,
|
||||
)
|
||||
} else if (__BROWSER__ && !currentOptions.decodeEntities) {
|
||||
} else if (__BROWSER__ && !__TEST__ && !currentOptions.decodeEntities) {
|
||||
throw new Error(
|
||||
`[@vue/compiler-core] decodeEntities option is required in browser builds.`,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,19 +12,22 @@ import {
|
|||
type RootNode,
|
||||
type SimpleExpressionNode,
|
||||
type SlotFunctionExpression,
|
||||
type SlotsObjectProperty,
|
||||
type TemplateChildNode,
|
||||
type TemplateNode,
|
||||
type TextCallNode,
|
||||
type VNodeCall,
|
||||
createArrayExpression,
|
||||
createObjectProperty,
|
||||
createSimpleExpression,
|
||||
getVNodeBlockHelper,
|
||||
getVNodeHelper,
|
||||
} from '../ast'
|
||||
import type { TransformContext } from '../transform'
|
||||
import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
|
||||
import {
|
||||
PatchFlagNames,
|
||||
PatchFlags,
|
||||
isArray,
|
||||
isString,
|
||||
isSymbol,
|
||||
} from '@vue/shared'
|
||||
import { findDir, isSlotOutlet } from '../utils'
|
||||
import {
|
||||
GUARD_REACTIVE_PROPS,
|
||||
|
|
@ -109,6 +112,15 @@ function walk(
|
|||
? ConstantTypes.NOT_CONSTANT
|
||||
: getConstantType(child, context)
|
||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
||||
if (
|
||||
child.codegenNode.type === NodeTypes.JS_CALL_EXPRESSION &&
|
||||
child.codegenNode.arguments.length > 0
|
||||
) {
|
||||
child.codegenNode.arguments.push(
|
||||
PatchFlags.CACHED +
|
||||
(__DEV__ ? ` /* ${PatchFlagNames[PatchFlags.CACHED]} */` : ``),
|
||||
)
|
||||
}
|
||||
toCache.push(child)
|
||||
continue
|
||||
}
|
||||
|
|
@ -142,7 +154,6 @@ function walk(
|
|||
}
|
||||
|
||||
let cachedAsArray = false
|
||||
const slotCacheKeys = []
|
||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||
if (
|
||||
node.tagType === ElementTypes.ELEMENT &&
|
||||
|
|
@ -166,7 +177,6 @@ function walk(
|
|||
// default slot
|
||||
const slot = getSlotNode(node.codegenNode, 'default')
|
||||
if (slot) {
|
||||
slotCacheKeys.push(context.cached.length)
|
||||
slot.returns = getCacheExpression(
|
||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||
)
|
||||
|
|
@ -190,7 +200,6 @@ function walk(
|
|||
slotName.arg &&
|
||||
getSlotNode(parent.codegenNode, slotName.arg)
|
||||
if (slot) {
|
||||
slotCacheKeys.push(context.cached.length)
|
||||
slot.returns = getCacheExpression(
|
||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||
)
|
||||
|
|
@ -201,39 +210,22 @@ function walk(
|
|||
|
||||
if (!cachedAsArray) {
|
||||
for (const child of toCache) {
|
||||
slotCacheKeys.push(context.cached.length)
|
||||
child.codegenNode = context.cache(child.codegenNode!)
|
||||
}
|
||||
}
|
||||
|
||||
// put the slot cached keys on the slot object, so that the cache
|
||||
// can be removed when component unmounting to prevent memory leaks
|
||||
if (
|
||||
slotCacheKeys.length &&
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.COMPONENT &&
|
||||
node.codegenNode &&
|
||||
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
node.codegenNode.children &&
|
||||
!isArray(node.codegenNode.children) &&
|
||||
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||
) {
|
||||
node.codegenNode.children.properties.push(
|
||||
createObjectProperty(
|
||||
`__`,
|
||||
createSimpleExpression(JSON.stringify(slotCacheKeys), false),
|
||||
) as SlotsObjectProperty,
|
||||
)
|
||||
}
|
||||
|
||||
function getCacheExpression(value: JSChildNode): CacheExpression {
|
||||
const exp = context.cache(value)
|
||||
// #6978, #7138, #7114
|
||||
// a cached children array inside v-for can caused HMR errors since
|
||||
// it might be mutated when mounting the first item
|
||||
if (inFor && context.hmr) {
|
||||
exp.needArraySpread = true
|
||||
}
|
||||
// #13221
|
||||
// fix memory leak in cached array:
|
||||
// cached vnodes get replaced by cloned ones during mountChildren,
|
||||
// which bind DOM elements. These DOM references persist after unmount,
|
||||
// preventing garbage collection. Array spread avoids mutating cached
|
||||
// array, preventing memory leaks.
|
||||
exp.needArraySpread = true
|
||||
return exp
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import { camelize } from '@vue/shared'
|
||||
import {
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
createSimpleExpression,
|
||||
} from '../ast'
|
||||
import type { NodeTransform } from '../transform'
|
||||
import { ErrorCodes, createCompilerError } from '../errors'
|
||||
import { validFirstIdentCharRE } from '../utils'
|
||||
|
||||
export const transformVBindShorthand: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
for (const prop of node.props) {
|
||||
// same-name shorthand - :arg is expanded to :arg="arg"
|
||||
if (
|
||||
prop.type === NodeTypes.DIRECTIVE &&
|
||||
prop.name === 'bind' &&
|
||||
(!prop.exp ||
|
||||
// #13930 :foo in in-DOM templates will be parsed into :foo="" by browser
|
||||
(__BROWSER__ &&
|
||||
prop.exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||
!prop.exp.content.trim())) &&
|
||||
prop.arg
|
||||
) {
|
||||
const arg = prop.arg
|
||||
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
|
||||
// only simple expression is allowed for same-name shorthand
|
||||
context.onError(
|
||||
createCompilerError(
|
||||
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
|
||||
arg.loc,
|
||||
),
|
||||
)
|
||||
prop.exp = createSimpleExpression('', true, arg.loc)
|
||||
} else {
|
||||
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||
if (
|
||||
validFirstIdentCharRE.test(propName[0]) ||
|
||||
// allow hyphen first char for https://github.com/vuejs/language-tools/pull/3424
|
||||
propName[0] === '-'
|
||||
) {
|
||||
prop.exp = createSimpleExpression(propName, false, arg.loc)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,13 @@
|
|||
import type { DirectiveTransform, TransformContext } from '../transform'
|
||||
import type { DirectiveTransform } from '../transform'
|
||||
import {
|
||||
type DirectiveNode,
|
||||
type ExpressionNode,
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
createObjectProperty,
|
||||
createSimpleExpression,
|
||||
} from '../ast'
|
||||
import { ErrorCodes, createCompilerError } from '../errors'
|
||||
import { camelize } from '@vue/shared'
|
||||
import { CAMELIZE } from '../runtimeHelpers'
|
||||
import { processExpression } from './transformExpression'
|
||||
|
||||
// v-bind without arg is handled directly in ./transformElement.ts due to its affecting
|
||||
// codegen for the entire props object. This transform here is only for v-bind
|
||||
|
|
@ -40,32 +37,11 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
// same-name shorthand - :arg is expanded to :arg="arg"
|
||||
if (!exp) {
|
||||
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION || !arg.isStatic) {
|
||||
// only simple expression is allowed for same-name shorthand
|
||||
context.onError(
|
||||
createCompilerError(
|
||||
ErrorCodes.X_V_BIND_INVALID_SAME_NAME_ARGUMENT,
|
||||
arg.loc,
|
||||
),
|
||||
)
|
||||
return {
|
||||
props: [
|
||||
createObjectProperty(arg, createSimpleExpression('', true, loc)),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
transformBindShorthand(dir, context)
|
||||
exp = dir.exp!
|
||||
}
|
||||
|
||||
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
||||
arg.children.unshift(`(`)
|
||||
arg.children.push(`) || ""`)
|
||||
} else if (!arg.isStatic) {
|
||||
arg.content = `${arg.content} || ""`
|
||||
arg.content = arg.content ? `${arg.content} || ""` : `""`
|
||||
}
|
||||
|
||||
// .sync is replaced by v-model:arg
|
||||
|
|
@ -92,20 +68,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
|
||||
return {
|
||||
props: [createObjectProperty(arg, exp)],
|
||||
}
|
||||
}
|
||||
|
||||
export const transformBindShorthand = (
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext,
|
||||
): void => {
|
||||
const arg = dir.arg!
|
||||
|
||||
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||
dir.exp = createSimpleExpression(propName, false, arg.loc)
|
||||
if (!__BROWSER__) {
|
||||
dir.exp = processExpression(dir.exp, context)
|
||||
props: [createObjectProperty(arg, exp!)],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ import {
|
|||
import { processExpression } from './transformExpression'
|
||||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { transformBindShorthand } from './vBind'
|
||||
|
||||
export const transformFor: NodeTransform = createStructuralDirectiveTransform(
|
||||
'for',
|
||||
|
|
@ -64,10 +63,6 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
|
|||
const memo = findDir(node, 'memo')
|
||||
const keyProp = findProp(node, `key`, false, true)
|
||||
const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
|
||||
if (isDirKey && !keyProp.exp) {
|
||||
// resolve :key shorthand #10882
|
||||
transformBindShorthand(keyProp, context)
|
||||
}
|
||||
let keyExp =
|
||||
keyProp &&
|
||||
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
|||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
export const transformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
/^(?:if|else|else-if)$/,
|
||||
(node, dir, context) => {
|
||||
return processIf(node, dir, context, (ifNode, branch, isRoot) => {
|
||||
// #1587: We need to dynamically increment the key based on the current
|
||||
|
|
@ -141,9 +141,9 @@ export function processIf(
|
|||
}
|
||||
|
||||
if (sibling && sibling.type === NodeTypes.IF) {
|
||||
// Check if v-else was followed by v-else-if
|
||||
// Check if v-else was followed by v-else-if or there are two adjacent v-else
|
||||
if (
|
||||
dir.name === 'else-if' &&
|
||||
(dir.name === 'else-if' || dir.name === 'else') &&
|
||||
sibling.branches[sibling.branches.length - 1].condition === undefined
|
||||
) {
|
||||
context.onError(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ const seen = new WeakSet()
|
|||
export const transformMemo: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.ELEMENT) {
|
||||
const dir = findDir(node, 'memo')
|
||||
if (!dir || seen.has(node)) {
|
||||
if (!dir || seen.has(node) || context.inSSR) {
|
||||
return
|
||||
}
|
||||
seen.add(node)
|
||||
|
|
|
|||
|
|
@ -131,9 +131,17 @@ export function buildSlots(
|
|||
// since it likely uses a scope variable.
|
||||
let hasDynamicSlots = context.scopes.vSlot > 0 || context.scopes.vFor > 0
|
||||
// with `prefixIdentifiers: true`, this can be further optimized to make
|
||||
// it dynamic only when the slot actually uses the scope variables.
|
||||
// it dynamic when
|
||||
// 1. the slot arg or exp uses the scope variables.
|
||||
// 2. the slot children use the scope variables.
|
||||
if (!__BROWSER__ && !context.ssr && context.prefixIdentifiers) {
|
||||
hasDynamicSlots = hasScopeRef(node, context.identifiers)
|
||||
hasDynamicSlots =
|
||||
node.props.some(
|
||||
prop =>
|
||||
isVSlot(prop) &&
|
||||
(hasScopeRef(prop.arg, context.identifiers) ||
|
||||
hasScopeRef(prop.exp, context.identifiers)),
|
||||
) || children.some(child => hasScopeRef(child, context.identifiers))
|
||||
}
|
||||
|
||||
// 1. Check for slot with slotProps on component itself.
|
||||
|
|
@ -215,7 +223,7 @@ export function buildSlots(
|
|||
),
|
||||
)
|
||||
} else if (
|
||||
(vElse = findDir(slotElement, /^else(-if)?$/, true /* allowEmpty */))
|
||||
(vElse = findDir(slotElement, /^else(?:-if)?$/, true /* allowEmpty */))
|
||||
) {
|
||||
// find adjacent v-if
|
||||
let j = i
|
||||
|
|
@ -226,7 +234,7 @@ export function buildSlots(
|
|||
break
|
||||
}
|
||||
}
|
||||
if (prev && isTemplateNode(prev) && findDir(prev, /^(else-)?if$/)) {
|
||||
if (prev && isTemplateNode(prev) && findDir(prev, /^(?:else-)?if$/)) {
|
||||
__TEST__ && assert(dynamicSlots.length > 0)
|
||||
// attach this slot to previous conditional
|
||||
let conditional = dynamicSlots[
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ export function isCoreComponent(tag: string): symbol | void {
|
|||
}
|
||||
}
|
||||
|
||||
const nonIdentifierRE = /^\d|[^\$\w\xA0-\uFFFF]/
|
||||
const nonIdentifierRE = /^$|^\d|[^\$\w\xA0-\uFFFF]/
|
||||
export const isSimpleIdentifier = (name: string): boolean =>
|
||||
!nonIdentifierRE.test(name)
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ enum MemberExpLexState {
|
|||
inString,
|
||||
}
|
||||
|
||||
const validFirstIdentCharRE = /[A-Za-z_$\xA0-\uFFFF]/
|
||||
export const validFirstIdentCharRE: RegExp = /[A-Za-z_$\xA0-\uFFFF]/
|
||||
const validIdentCharRE = /[\.\?\w$\xA0-\uFFFF]/
|
||||
const whitespaceRE = /\s+[.[]\s*|\s*[.[]\s+/g
|
||||
|
||||
|
|
@ -189,7 +189,7 @@ export const isMemberExpression: (
|
|||
) => boolean = __BROWSER__ ? isMemberExpressionBrowser : isMemberExpressionNode
|
||||
|
||||
const fnExpRE =
|
||||
/^\s*(async\s*)?(\([^)]*?\)|[\w$_]+)\s*(:[^=]+)?=>|^\s*(async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||
/^\s*(?:async\s*)?(?:\([^)]*?\)|[\w$_]+)\s*(?::[^=]+)?=>|^\s*(?:async\s+)?function(?:\s+[\w$]+)?\s*\(/
|
||||
|
||||
export const isFnExpressionBrowser: (exp: ExpressionNode) => boolean = exp =>
|
||||
fnExpRE.test(getExpSource(exp))
|
||||
|
|
@ -343,6 +343,10 @@ export function isText(
|
|||
return node.type === NodeTypes.INTERPOLATION || node.type === NodeTypes.TEXT
|
||||
}
|
||||
|
||||
export function isVPre(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'pre'
|
||||
}
|
||||
|
||||
export function isVSlot(p: ElementNode['props'][0]): p is DirectiveNode {
|
||||
return p.type === NodeTypes.DIRECTIVE && p.name === 'slot'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,11 +4,29 @@ exports[`stringify static html > eligible content (elements > 20) + non-eligible
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
||||
_createElementVNode("div", { key: "1" }, "1", -1 /* CACHED */),
|
||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > eligible content + v-once node 1`] = `
|
||||
"const { setBlockTracking: _setBlockTracking, toDisplayString: _toDisplayString, createTextVNode: _createTextVNode, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1, true),
|
||||
(_cache[0] = _createElementVNode("div", null, [
|
||||
_createTextVNode(_toDisplayString(_ctx.msg), 1 /* TEXT */)
|
||||
])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
),
|
||||
_cache[1] || (_cache[1] = _createStaticVNode("<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>", 5))
|
||||
]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -16,9 +34,9 @@ exports[`stringify static html > escape 1`] = `
|
|||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -26,9 +44,9 @@ exports[`stringify static html > serializing constant bindings 1`] = `
|
|||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -36,9 +54,9 @@ exports[`stringify static html > serializing template string style 1`] = `
|
|||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div style=\\"color:red;\\"><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span><span class=\\"foo bar\\">1 + false</span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -46,7 +64,7 @@ exports[`stringify static html > should bail for <option> elements with null val
|
|||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("select", null, [
|
||||
_createElementVNode("option", { value: null }),
|
||||
_createElementVNode("option", { value: "1" }),
|
||||
|
|
@ -55,7 +73,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("option", { value: "1" }),
|
||||
_createElementVNode("option", { value: "1" })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -63,7 +81,7 @@ exports[`stringify static html > should bail for <option> elements with number v
|
|||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("select", null, [
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
|
|
@ -71,7 +89,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -95,7 +113,7 @@ exports[`stringify static html > should bail on bindings that are cached but not
|
|||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
|
|
@ -104,7 +122,7 @@ return function render(_ctx, _cache) {
|
|||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("img", { src: _imports_0_ })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -112,9 +130,9 @@ exports[`stringify static html > should work for <option> elements with string v
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -122,9 +140,9 @@ exports[`stringify static html > should work for multiple adjacent nodes 1`] = `
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -132,9 +150,9 @@ exports[`stringify static html > should work on eligible content (elements > 20)
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -142,9 +160,9 @@ exports[`stringify static html > should work on eligible content (elements with
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
@ -152,9 +170,9 @@ exports[`stringify static html > should work with bindings that are non-static b
|
|||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,22 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model > input with v-bind shorthand type after v-model should use dynamic model 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { vModelDynamic: _vModelDynamic, withDirectives: _withDirectives, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return _withDirectives((_openBlock(), _createElementBlock("input", {
|
||||
"onUpdate:modelValue": $event => ((model) = $event)
|
||||
}, null, 8 /* PROPS */, ["onUpdate:modelValue"])), [
|
||||
[_vModelDynamic, model]
|
||||
])
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: transform v-model > modifiers > .lazy 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
|
|
|
|||
|
|
@ -525,4 +525,14 @@ describe('stringify static html', () => {
|
|||
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('eligible content + v-once node', () => {
|
||||
const { code } = compileWithStringify(
|
||||
`<div>
|
||||
<div v-once>{{ msg }}</div>
|
||||
${repeat(`<span class="foo">foo</span>`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT)}
|
||||
</div>`,
|
||||
)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
generate,
|
||||
baseParse as parse,
|
||||
transform,
|
||||
transformVBindShorthand,
|
||||
} from '@vue/compiler-core'
|
||||
import { transformModel } from '../../src/transforms/vModel'
|
||||
import { transformElement } from '../../../compiler-core/src/transforms/transformElement'
|
||||
|
|
@ -18,7 +19,7 @@ import {
|
|||
function transformWithModel(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformElement],
|
||||
nodeTransforms: [transformVBindShorthand, transformElement],
|
||||
directiveTransforms: {
|
||||
model: transformModel,
|
||||
},
|
||||
|
|
@ -63,6 +64,14 @@ describe('compiler: transform v-model', () => {
|
|||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
// #13169
|
||||
test('input with v-bind shorthand type after v-model should use dynamic model', () => {
|
||||
const root = transformWithModel('<input v-model="model" :type/>')
|
||||
|
||||
expect(root.helpers).toContain(V_MODEL_DYNAMIC)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('input w/ dynamic v-bind', () => {
|
||||
const root = transformWithModel('<input v-bind="obj" v-model="model" />')
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.17",
|
||||
"version": "3.5.24",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ if (__TEST__) {
|
|||
if (DOMErrorCodes.X_V_HTML_NO_EXPRESSION < ErrorCodes.__EXTEND_POINT__) {
|
||||
throw new Error(
|
||||
`DOMErrorCodes need to be updated to ${
|
||||
ErrorCodes.__EXTEND_POINT__ + 1
|
||||
ErrorCodes.__EXTEND_POINT__
|
||||
} to match extension point from core ErrorCodes.`,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
type TextCallNode,
|
||||
type TransformContext,
|
||||
createCallExpression,
|
||||
findDir,
|
||||
isStaticArgOf,
|
||||
} from '@vue/compiler-core'
|
||||
import {
|
||||
|
|
@ -184,7 +185,7 @@ const getCachedNode = (
|
|||
}
|
||||
}
|
||||
|
||||
const dataAriaRE = /^(data|aria)-/
|
||||
const dataAriaRE = /^(?:data|aria)-/
|
||||
const isStringifiableAttr = (name: string, ns: Namespaces) => {
|
||||
return (
|
||||
(ns === Namespaces.HTML
|
||||
|
|
@ -213,6 +214,11 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
|||
return false
|
||||
}
|
||||
|
||||
// v-once nodes should not be stringified
|
||||
if (node.type === NodeTypes.ELEMENT && findDir(node, 'once', true)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (node.type === NodeTypes.TEXT_CALL) {
|
||||
return [1, 0]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,6 +12,27 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`prefixing props edge case in inline mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { unref as _unref, openBlock as _openBlock, createBlock as _createBlock } from "vue"
|
||||
|
||||
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
Foo: { type: Object, required: true }
|
||||
},
|
||||
setup(__props: any) {
|
||||
|
||||
|
||||
|
||||
return (_ctx: any,_cache: any) => {
|
||||
return (_openBlock(), _createBlock(_unref(__props["Foo"]).Bar))
|
||||
}
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`should not hoist srcset URLs in SSR mode 1`] = `
|
||||
"import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from "vue"
|
||||
import { ssrRenderAttr as _ssrRenderAttr, ssrRenderComponent as _ssrRenderComponent } from "vue/server-renderer"
|
||||
|
|
|
|||
|
|
@ -81,9 +81,9 @@ import _imports_1 from '/bar.png'
|
|||
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,16 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform srcset > transform empty srcset w/ includeAbsolute: true 1`] = `
|
||||
"import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
const _hoisted_1 = { srcset: " " }
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("img", _hoisted_1))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler sfc: transform srcset > transform srcset 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
import _imports_0 from './logo.png'
|
||||
|
|
@ -228,8 +238,8 @@ const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
|
|||
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
||||
])))
|
||||
]))]))
|
||||
}"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -913,6 +913,13 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(() =>
|
||||
compile(`<script>foo()</script><script setup lang="ts">bar()</script>`),
|
||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||
|
||||
// #13193 must check lang before parsing with babel
|
||||
expect(() =>
|
||||
compile(
|
||||
`<script lang="ts">const a = 1</script><script setup lang="tsx">const Comp = () => <p>test</p></script>`,
|
||||
),
|
||||
).toThrow(`<script> and <script setup> must have the same language type`)
|
||||
})
|
||||
|
||||
const moduleErrorMsg = `cannot contain ES module exports`
|
||||
|
|
@ -1543,4 +1550,19 @@ describe('compileScript', () => {
|
|||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('should not compile unrecognized language', () => {
|
||||
const { content, lang, scriptAst } = compile(
|
||||
`<script lang="coffee">
|
||||
export default
|
||||
data: ->
|
||||
myVal: 0
|
||||
</script>`,
|
||||
)
|
||||
expect(content).toMatch(`export default
|
||||
data: ->
|
||||
myVal: 0`)
|
||||
expect(lang).toBe('coffee')
|
||||
expect(scriptAst).not.toBeDefined()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ describe('resolveType', () => {
|
|||
foo: number // property
|
||||
bar(): void // method
|
||||
'baz': string // string literal key
|
||||
[\`qux\`]: boolean // template literal key
|
||||
123: symbol // numeric literal key
|
||||
(e: 'foo'): void // call signature
|
||||
(e: 'bar'): void
|
||||
}>()`)
|
||||
|
|
@ -27,6 +29,8 @@ describe('resolveType', () => {
|
|||
foo: ['Number'],
|
||||
bar: ['Function'],
|
||||
baz: ['String'],
|
||||
qux: ['Boolean'],
|
||||
123: ['Symbol'],
|
||||
})
|
||||
expect(calls?.length).toBe(2)
|
||||
})
|
||||
|
|
@ -149,6 +153,17 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('TSPropertySignature with ignore', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Foo = string
|
||||
defineProps<{ foo: /* @vue-ignore */ Foo }>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Unknown'],
|
||||
})
|
||||
})
|
||||
|
||||
// #7553
|
||||
test('union type', () => {
|
||||
expect(
|
||||
|
|
@ -184,7 +199,7 @@ describe('resolveType', () => {
|
|||
type T = 'foo' | 'bar'
|
||||
type S = 'x' | 'y'
|
||||
defineProps<{
|
||||
[\`_\${T}_\${S}_\`]: string
|
||||
[K in \`_\${T}_\${S}_\`]: string
|
||||
}>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
|
|
@ -538,7 +553,7 @@ describe('resolveType', () => {
|
|||
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Symbol', 'String', 'Number'],
|
||||
bar: [UNKNOWN_TYPE],
|
||||
bar: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -731,6 +746,22 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('TSMappedType with indexed access', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
type Prettify<T> = { [K in keyof T]: T[K] } & {}
|
||||
type Side = 'top' | 'right' | 'bottom' | 'left'
|
||||
type AlignedPlacement = \`\${Side}-\${Alignment}\`
|
||||
type Alignment = 'start' | 'end'
|
||||
type Placement = Prettify<Side | AlignedPlacement>
|
||||
defineProps<{placement?: Placement}>()
|
||||
`,
|
||||
)
|
||||
expect(props).toStrictEqual({
|
||||
placement: ['String', 'Object'],
|
||||
})
|
||||
})
|
||||
|
||||
describe('type alias declaration', () => {
|
||||
// #13240
|
||||
test('function type', () => {
|
||||
|
|
@ -749,7 +780,7 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('fallback to Unknown', () => {
|
||||
test('with intersection type', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Brand<T> = T & {};
|
||||
|
|
@ -758,7 +789,18 @@ describe('resolveType', () => {
|
|||
}>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: [UNKNOWN_TYPE],
|
||||
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'],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -1187,6 +1229,45 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
|
||||
})
|
||||
|
||||
// #13484
|
||||
test('ts module resolve w/ project reference & extends & ${configDir}', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
files: [],
|
||||
references: [{ path: './tsconfig.app.json' }],
|
||||
}),
|
||||
'/tsconfig.app.json': JSON.stringify({
|
||||
extends: ['./tsconfigs/base.json'],
|
||||
}),
|
||||
'/tsconfigs/base.json': JSON.stringify({
|
||||
compilerOptions: {
|
||||
paths: {
|
||||
'@/*': ['${configDir}/src/*'],
|
||||
},
|
||||
},
|
||||
include: ['${configDir}/src/**/*.ts', '${configDir}/src/**/*.vue'],
|
||||
}),
|
||||
'/src/types.ts':
|
||||
'export type BaseProps = { foo?: string, bar?: string }',
|
||||
}
|
||||
|
||||
const { props, deps } = resolve(
|
||||
`
|
||||
import { BaseProps } from '@/types.ts';
|
||||
defineProps<BaseProps>()
|
||||
`,
|
||||
files,
|
||||
{},
|
||||
'/src/components/Foo.vue',
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['String'],
|
||||
bar: ['String'],
|
||||
})
|
||||
expect(deps && [...deps]).toStrictEqual(['/src/types.ts'])
|
||||
})
|
||||
|
||||
test('ts module resolve w/ project reference folder', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
|
|
@ -1331,6 +1412,33 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(Object.keys(files))
|
||||
})
|
||||
|
||||
test('global types with named exports', () => {
|
||||
const files = {
|
||||
'/global.d.ts': `
|
||||
declare global {
|
||||
export interface ExportedInterface { foo: number }
|
||||
export type ExportedType = { bar: boolean }
|
||||
}
|
||||
export {}
|
||||
`,
|
||||
}
|
||||
|
||||
const globalTypeFiles = { globalTypeFiles: Object.keys(files) }
|
||||
|
||||
expect(
|
||||
resolve(`defineProps<ExportedInterface>()`, files, globalTypeFiles)
|
||||
.props,
|
||||
).toStrictEqual({
|
||||
foo: ['Number'],
|
||||
})
|
||||
|
||||
expect(
|
||||
resolve(`defineProps<ExportedType>()`, files, globalTypeFiles).props,
|
||||
).toStrictEqual({
|
||||
bar: ['Boolean'],
|
||||
})
|
||||
})
|
||||
|
||||
test('global types with ambient references', () => {
|
||||
const files = {
|
||||
// with references
|
||||
|
|
|
|||
|
|
@ -512,3 +512,22 @@ test('non-identifier expression in legacy filter syntax', () => {
|
|||
babelParse(compilationResult.code, { sourceType: 'module' })
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
test('prefixing props edge case in inline mode', () => {
|
||||
const src = `
|
||||
<script setup lang="ts">
|
||||
defineProps<{ Foo: { Bar: unknown } }>()
|
||||
</script>
|
||||
<template>
|
||||
<Foo.Bar/>
|
||||
</template>
|
||||
`
|
||||
const { descriptor } = parse(src)
|
||||
const { content } = compileScript(descriptor, {
|
||||
id: 'xxx',
|
||||
inlineTemplate: true,
|
||||
})
|
||||
|
||||
expect(content).toMatchSnapshot()
|
||||
expect(content).toMatch(`__props["Foo"]).Bar`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -109,8 +109,8 @@ describe('CSS vars injection', () => {
|
|||
{ isProd: true },
|
||||
)
|
||||
expect(content).toMatch(`_useCssVars(_ctx => ({
|
||||
"4003f1a6": (_ctx.color),
|
||||
"41b6490a": (_ctx.font.size)
|
||||
"v4003f1a6": (_ctx.color),
|
||||
"v41b6490a": (_ctx.font.size)
|
||||
}))}`)
|
||||
|
||||
const { code } = compileStyle({
|
||||
|
|
@ -124,8 +124,8 @@ describe('CSS vars injection', () => {
|
|||
})
|
||||
expect(code).toMatchInlineSnapshot(`
|
||||
".foo {
|
||||
color: var(--4003f1a6);
|
||||
font-size: var(--41b6490a);
|
||||
color: var(--v4003f1a6);
|
||||
font-size: var(--v41b6490a);
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -72,6 +72,14 @@ describe('compiler sfc: transform srcset', () => {
|
|||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('transform empty srcset w/ includeAbsolute: true', () => {
|
||||
expect(
|
||||
compileWithSrcset(`<img srcset=" " />`, {
|
||||
includeAbsolute: true,
|
||||
}).code,
|
||||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('transform srcset w/ stringify', () => {
|
||||
const code = compileWithSrcset(
|
||||
`<div>${src}</div>`,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.17",
|
||||
"version": "3.5.24",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
|
@ -58,10 +58,10 @@
|
|||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "~10.0.3",
|
||||
"minimatch": "~10.1.1",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-selector-parser": "^7.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.89.2"
|
||||
"sass": "^1.93.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import type {
|
|||
Declaration,
|
||||
ExportSpecifier,
|
||||
Identifier,
|
||||
LVal,
|
||||
Node,
|
||||
ObjectPattern,
|
||||
Statement,
|
||||
|
|
@ -55,7 +56,13 @@ import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
|
|||
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
||||
import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
||||
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
|
||||
import {
|
||||
getImportedName,
|
||||
isCallOf,
|
||||
isJS,
|
||||
isLiteralNode,
|
||||
isTS,
|
||||
} from './script/utils'
|
||||
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||
import { isImportUsed } from './script/importUsageCheck'
|
||||
import { processAwait } from './script/topLevelAwait'
|
||||
|
|
@ -167,33 +174,43 @@ export function compileScript(
|
|||
)
|
||||
}
|
||||
|
||||
const ctx = new ScriptCompileContext(sfc, options)
|
||||
const { script, scriptSetup, source, filename } = sfc
|
||||
const hoistStatic = options.hoistStatic !== false && !script
|
||||
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
|
||||
const scriptLang = script && script.lang
|
||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||
const isJSOrTS =
|
||||
isJS(scriptLang, scriptSetupLang) || isTS(scriptLang, scriptSetupLang)
|
||||
|
||||
if (!scriptSetup) {
|
||||
if (!script) {
|
||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||
}
|
||||
// normal <script> only
|
||||
return processNormalScript(ctx, scopeId)
|
||||
}
|
||||
|
||||
if (script && scriptLang !== scriptSetupLang) {
|
||||
if (script && scriptSetup && scriptLang !== scriptSetupLang) {
|
||||
throw new Error(
|
||||
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
|
||||
`language type.`,
|
||||
)
|
||||
}
|
||||
|
||||
if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
|
||||
if (!scriptSetup) {
|
||||
if (!script) {
|
||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||
}
|
||||
|
||||
// normal <script> only
|
||||
if (script.lang && !isJSOrTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return script
|
||||
}
|
||||
|
||||
const ctx = new ScriptCompileContext(sfc, options)
|
||||
return processNormalScript(ctx, scopeId)
|
||||
}
|
||||
|
||||
if (scriptSetupLang && !isJSOrTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return scriptSetup
|
||||
}
|
||||
|
||||
const ctx = new ScriptCompileContext(sfc, options)
|
||||
|
||||
// metadata that needs to be returned
|
||||
// const ctx.bindingMetadata: BindingMetadata = {}
|
||||
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
|
||||
|
|
@ -540,7 +557,7 @@ export function compileScript(
|
|||
}
|
||||
|
||||
// defineProps
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
||||
const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
|
||||
if (ctx.propsDestructureRestId) {
|
||||
setupBindings[ctx.propsDestructureRestId] =
|
||||
BindingTypes.SETUP_REACTIVE_CONST
|
||||
|
|
@ -548,10 +565,10 @@ export function compileScript(
|
|||
|
||||
// defineEmits
|
||||
const isDefineEmits =
|
||||
!isDefineProps && processDefineEmits(ctx, init, decl.id)
|
||||
!isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
|
||||
!isDefineEmits &&
|
||||
(processDefineSlots(ctx, init, decl.id) ||
|
||||
processDefineModel(ctx, init, decl.id))
|
||||
(processDefineSlots(ctx, init, decl.id as LVal) ||
|
||||
processDefineModel(ctx, init, decl.id as LVal))
|
||||
|
||||
if (
|
||||
isDefineProps &&
|
||||
|
|
@ -816,6 +833,8 @@ export function compileScript(
|
|||
let templateMap
|
||||
// 9. generate return statement
|
||||
let returned
|
||||
// ensure props bindings register before compile template in inline mode
|
||||
const propsDecl = genRuntimeProps(ctx)
|
||||
if (
|
||||
!options.inlineTemplate ||
|
||||
(!sfc.template && ctx.hasDefaultExportRender)
|
||||
|
|
@ -948,7 +967,6 @@ export function compileScript(
|
|||
runtimeOptions += `\n __ssrInlineRender: true,`
|
||||
}
|
||||
|
||||
const propsDecl = genRuntimeProps(ctx)
|
||||
if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`
|
||||
|
||||
const emitsDecl = genRuntimeEmits(ctx)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import type { BindingMetadata } from '../../../compiler-core/src'
|
|||
import MagicString from 'magic-string'
|
||||
import type { TypeScope } from './resolveType'
|
||||
import { warn } from '../warn'
|
||||
import { isJS, isTS } from './utils'
|
||||
|
||||
export class ScriptCompileContext {
|
||||
isJS: boolean
|
||||
|
|
@ -87,16 +88,8 @@ export class ScriptCompileContext {
|
|||
const scriptLang = script && script.lang
|
||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||
|
||||
this.isJS =
|
||||
scriptLang === 'js' ||
|
||||
scriptLang === 'jsx' ||
|
||||
scriptSetupLang === 'js' ||
|
||||
scriptSetupLang === 'jsx'
|
||||
this.isTS =
|
||||
scriptLang === 'ts' ||
|
||||
scriptLang === 'tsx' ||
|
||||
scriptSetupLang === 'ts' ||
|
||||
scriptSetupLang === 'tsx'
|
||||
this.isJS = isJS(scriptLang, scriptSetupLang)
|
||||
this.isTS = isTS(scriptLang, scriptSetupLang)
|
||||
|
||||
const customElement = options.customElement
|
||||
const filename = this.descriptor.filename
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ export function processDefineModel(
|
|||
return true
|
||||
}
|
||||
|
||||
export function genModelProps(ctx: ScriptCompileContext) {
|
||||
export function genModelProps(ctx: ScriptCompileContext): string | undefined {
|
||||
if (!ctx.hasDefineModelCall) return
|
||||
|
||||
const isProd = !!ctx.options.isProd
|
||||
|
|
|
|||
|
|
@ -12,10 +12,6 @@ export function processNormalScript(
|
|||
scopeId: string,
|
||||
): SFCScriptBlock {
|
||||
const script = ctx.descriptor.script!
|
||||
if (script.lang && !ctx.isJS && !ctx.isTS) {
|
||||
// do not process non js/ts script blocks
|
||||
return script
|
||||
}
|
||||
try {
|
||||
let content = script.content
|
||||
let map = script.map
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import {
|
|||
createGetCanonicalFileName,
|
||||
getId,
|
||||
getImportedName,
|
||||
getStringLiteralKey,
|
||||
joinPaths,
|
||||
normalizePath,
|
||||
} from './utils'
|
||||
|
|
@ -336,13 +337,9 @@ function typeElementsToMap(
|
|||
Object.assign(scope.types, typeParameters)
|
||||
}
|
||||
;(e as MaybeWithScope)._ownerScope = scope
|
||||
const name = getId(e.key)
|
||||
if (name && !e.computed) {
|
||||
const name = getStringLiteralKey(e)
|
||||
if (name !== null) {
|
||||
res.props[name] = e as ResolvedElements['props'][string]
|
||||
} else if (e.key.type === 'TemplateLiteral') {
|
||||
for (const key of resolveTemplateKeys(ctx, e.key, scope)) {
|
||||
res.props[key] = e as ResolvedElements['props'][string]
|
||||
}
|
||||
} else {
|
||||
ctx.error(
|
||||
`Unsupported computed key in type referenced by a macro`,
|
||||
|
|
@ -853,7 +850,7 @@ export function registerTS(_loadTS: () => typeof TS): void {
|
|||
) {
|
||||
throw new Error(
|
||||
'Failed to load TypeScript, which is required for resolving imported types. ' +
|
||||
'Please make sure "typescript" is installed as a project dependency.',
|
||||
'Please make sure "TypeScript" is installed as a project dependency.',
|
||||
)
|
||||
} else {
|
||||
throw new Error(
|
||||
|
|
@ -951,7 +948,7 @@ function importSourceToScope(
|
|||
if (!ts) {
|
||||
return ctx.error(
|
||||
`Failed to resolve import source ${JSON.stringify(source)}. ` +
|
||||
`typescript is required as a peer dep for vue in order ` +
|
||||
`TypeScript is required as a peer dep for vue in order ` +
|
||||
`to support resolving types from module imports.`,
|
||||
node,
|
||||
scope,
|
||||
|
|
@ -1029,6 +1026,14 @@ function resolveWithTS(
|
|||
if (configs.length === 1) {
|
||||
matchedConfig = configs[0]
|
||||
} else {
|
||||
const [major, minor] = ts.versionMajorMinor.split('.').map(Number)
|
||||
const getPattern = (base: string, p: string) => {
|
||||
// ts 5.5+ supports ${configDir} in paths
|
||||
const supportsConfigDir = major > 5 || (major === 5 && minor >= 5)
|
||||
return p.startsWith('${configDir}') && supportsConfigDir
|
||||
? normalizePath(p.replace('${configDir}', dirname(configPath!)))
|
||||
: joinPaths(base, p)
|
||||
}
|
||||
// resolve which config matches the current file
|
||||
for (const c of configs) {
|
||||
const base = normalizePath(
|
||||
|
|
@ -1039,11 +1044,11 @@ function resolveWithTS(
|
|||
const excluded: string[] | undefined = c.config.raw?.exclude
|
||||
if (
|
||||
(!included && (!base || containingFile.startsWith(base))) ||
|
||||
included?.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
included?.some(p => isMatch(containingFile, getPattern(base, p)))
|
||||
) {
|
||||
if (
|
||||
excluded &&
|
||||
excluded.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
excluded.some(p => isMatch(containingFile, getPattern(base, p)))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
|
|
@ -1296,7 +1301,12 @@ function recordTypes(
|
|||
}
|
||||
} else if (stmt.type === 'TSModuleDeclaration' && stmt.global) {
|
||||
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 {
|
||||
|
|
@ -1500,7 +1510,15 @@ export function inferRuntimeType(
|
|||
node: Node & MaybeWithScope,
|
||||
scope: TypeScope = node._ownerScope || ctxToScope(ctx),
|
||||
isKeyOf = false,
|
||||
typeParameters?: Record<string, Node>,
|
||||
): string[] {
|
||||
if (
|
||||
node.leadingComments &&
|
||||
node.leadingComments.some(c => c.value.includes('@vue-ignore'))
|
||||
) {
|
||||
return [UNKNOWN_TYPE]
|
||||
}
|
||||
|
||||
try {
|
||||
switch (node.type) {
|
||||
case 'TSStringKeyword':
|
||||
|
|
@ -1588,19 +1606,43 @@ export function inferRuntimeType(
|
|||
case 'TSTypeReference': {
|
||||
const resolved = resolveTypeReference(ctx, node, scope)
|
||||
if (resolved) {
|
||||
// #13240
|
||||
// Special case for function type aliases to ensure correct runtime behavior
|
||||
// other type aliases still fallback to unknown as before
|
||||
if (
|
||||
resolved.type === 'TSTypeAliasDeclaration' &&
|
||||
resolved.typeAnnotation.type === 'TSFunctionType'
|
||||
) {
|
||||
return ['Function']
|
||||
if (resolved.type === 'TSTypeAliasDeclaration') {
|
||||
// #13240
|
||||
// Special case for function type aliases to ensure correct runtime behavior
|
||||
// other type aliases still fallback to unknown as before
|
||||
if (resolved.typeAnnotation.type === 'TSFunctionType') {
|
||||
return ['Function']
|
||||
}
|
||||
|
||||
if (node.typeParameters) {
|
||||
const typeParams: Record<string, Node> = Object.create(null)
|
||||
if (resolved.typeParameters) {
|
||||
resolved.typeParameters.params.forEach((p, i) => {
|
||||
typeParams![p.name] = node.typeParameters!.params[i]
|
||||
})
|
||||
}
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
resolved.typeAnnotation,
|
||||
resolved._ownerScope,
|
||||
isKeyOf,
|
||||
typeParams,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||
}
|
||||
|
||||
if (node.typeName.type === 'Identifier') {
|
||||
if (typeParameters && typeParameters[node.typeName.name]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
typeParameters[node.typeName.name],
|
||||
scope,
|
||||
isKeyOf,
|
||||
typeParameters,
|
||||
)
|
||||
}
|
||||
if (isKeyOf) {
|
||||
switch (node.typeName.name) {
|
||||
case 'String':
|
||||
|
|
@ -1733,11 +1775,56 @@ export function inferRuntimeType(
|
|||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||
|
||||
case 'TSUnionType':
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf)
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf, typeParameters)
|
||||
case 'TSIntersectionType': {
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
|
||||
t => t !== UNKNOWN_TYPE,
|
||||
)
|
||||
return flattenTypes(
|
||||
ctx,
|
||||
node.types,
|
||||
scope,
|
||||
isKeyOf,
|
||||
typeParameters,
|
||||
).filter(t => t !== UNKNOWN_TYPE)
|
||||
}
|
||||
case 'TSMappedType': {
|
||||
// only support { [K in keyof T]: T[K] }
|
||||
const { typeAnnotation, typeParameter } = node
|
||||
if (
|
||||
typeAnnotation &&
|
||||
typeAnnotation.type === 'TSIndexedAccessType' &&
|
||||
typeParameter &&
|
||||
typeParameter.constraint &&
|
||||
typeParameters
|
||||
) {
|
||||
const constraint = typeParameter.constraint
|
||||
if (
|
||||
constraint.type === 'TSTypeOperator' &&
|
||||
constraint.operator === 'keyof' &&
|
||||
constraint.typeAnnotation &&
|
||||
constraint.typeAnnotation.type === 'TSTypeReference' &&
|
||||
constraint.typeAnnotation.typeName.type === 'Identifier'
|
||||
) {
|
||||
const typeName = constraint.typeAnnotation.typeName.name
|
||||
const index = typeAnnotation.indexType
|
||||
const obj = typeAnnotation.objectType
|
||||
if (
|
||||
obj &&
|
||||
obj.type === 'TSTypeReference' &&
|
||||
obj.typeName.type === 'Identifier' &&
|
||||
obj.typeName.name === typeName &&
|
||||
index &&
|
||||
index.type === 'TSTypeReference' &&
|
||||
index.typeName.type === 'Identifier' &&
|
||||
index.typeName.name === typeParameter.name
|
||||
) {
|
||||
const targetType = typeParameters[typeName]
|
||||
if (targetType) {
|
||||
return inferRuntimeType(ctx, targetType, scope)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [UNKNOWN_TYPE]
|
||||
}
|
||||
|
||||
case 'TSEnumDeclaration':
|
||||
|
|
@ -1808,14 +1895,17 @@ function flattenTypes(
|
|||
types: TSType[],
|
||||
scope: TypeScope,
|
||||
isKeyOf: boolean = false,
|
||||
typeParameters: Record<string, Node> | undefined = undefined,
|
||||
): string[] {
|
||||
if (types.length === 1) {
|
||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
|
||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf, typeParameters)
|
||||
}
|
||||
return [
|
||||
...new Set(
|
||||
([] as string[]).concat(
|
||||
...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
|
||||
...types.map(t =>
|
||||
inferRuntimeType(ctx, t, scope, isKeyOf, typeParameters),
|
||||
),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
@ -1936,8 +2026,7 @@ function findStaticPropertyType(node: TSTypeLiteral, key: string) {
|
|||
const prop = node.members.find(
|
||||
m =>
|
||||
m.type === 'TSPropertySignature' &&
|
||||
!m.computed &&
|
||||
getId(m.key) === key &&
|
||||
getStringLiteralKey(m) === key &&
|
||||
m.typeAnnotation,
|
||||
)
|
||||
return prop && prop.typeAnnotation!.typeAnnotation
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import type {
|
|||
ImportSpecifier,
|
||||
Node,
|
||||
StringLiteral,
|
||||
TSMethodSignature,
|
||||
TSPropertySignature,
|
||||
} from '@babel/types'
|
||||
import path from 'path'
|
||||
|
||||
|
|
@ -79,6 +81,22 @@ export function getId(node: Expression) {
|
|||
: null
|
||||
}
|
||||
|
||||
export function getStringLiteralKey(
|
||||
node: TSPropertySignature | TSMethodSignature,
|
||||
): string | null {
|
||||
return node.computed
|
||||
? node.key.type === 'TemplateLiteral' && !node.key.expressions.length
|
||||
? node.key.quasis.map(q => q.value.cooked).join('')
|
||||
: null
|
||||
: node.key.type === 'Identifier'
|
||||
? node.key.name
|
||||
: node.key.type === 'StringLiteral'
|
||||
? node.key.value
|
||||
: node.key.type === 'NumericLiteral'
|
||||
? String(node.key.value)
|
||||
: null
|
||||
}
|
||||
|
||||
const identity = (str: string) => str
|
||||
const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g
|
||||
const toLowerCase = (str: string) => str.toLowerCase()
|
||||
|
|
@ -121,3 +139,8 @@ export const propNameEscapeSymbolsRE: RegExp =
|
|||
export function getEscapedPropName(key: string): string {
|
||||
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
||||
}
|
||||
|
||||
export const isJS = (...langs: (string | null | undefined)[]): boolean =>
|
||||
langs.some(lang => lang === 'js' || lang === 'jsx')
|
||||
export const isTS = (...langs: (string | null | undefined)[]): boolean =>
|
||||
langs.some(lang => lang === 'ts' || lang === 'tsx')
|
||||
|
|
|
|||
|
|
@ -40,7 +40,8 @@ function genVarName(
|
|||
isSSR = false,
|
||||
): string {
|
||||
if (isProd) {
|
||||
return hash(id + raw)
|
||||
// hash must not start with a digit to comply with CSS custom property naming rules
|
||||
return hash(id + raw).replace(/^\d/, r => `v${r}`)
|
||||
} else {
|
||||
// escape ASCII Punctuation & Symbols
|
||||
// #7823 need to double-escape in SSR because the attributes are rendered
|
||||
|
|
|
|||
|
|
@ -8,8 +8,9 @@ import {
|
|||
import selectorParser from 'postcss-selector-parser'
|
||||
import { warn } from '../warn'
|
||||
|
||||
const animationNameRE = /^(-\w+-)?animation-name$/
|
||||
const animationRE = /^(-\w+-)?animation$/
|
||||
const animationNameRE = /^(?:-\w+-)?animation-name$/
|
||||
const animationRE = /^(?:-\w+-)?animation$/
|
||||
const keyframesRE = /^(?:-\w+-)?keyframes$/
|
||||
|
||||
const scopedPlugin: PluginCreator<string> = (id = '') => {
|
||||
const keyframes = Object.create(null)
|
||||
|
|
@ -21,10 +22,7 @@ const scopedPlugin: PluginCreator<string> = (id = '') => {
|
|||
processRule(id, rule)
|
||||
},
|
||||
AtRule(node) {
|
||||
if (
|
||||
/-?keyframes$/.test(node.name) &&
|
||||
!node.params.endsWith(`-${shortId}`)
|
||||
) {
|
||||
if (keyframesRE.test(node.name) && !node.params.endsWith(`-${shortId}`)) {
|
||||
// register keyframes
|
||||
keyframes[node.params] = node.params = node.params + '-' + shortId
|
||||
}
|
||||
|
|
@ -72,7 +70,7 @@ function processRule(id: string, rule: Rule) {
|
|||
processedRules.has(rule) ||
|
||||
(rule.parent &&
|
||||
rule.parent.type === 'atrule' &&
|
||||
/-?keyframes$/.test((rule.parent as AtRule).name))
|
||||
keyframesRE.test((rule.parent as AtRule).name))
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export function isRelativeUrl(url: string): boolean {
|
|||
return firstChar === '.' || firstChar === '~' || firstChar === '@'
|
||||
}
|
||||
|
||||
const externalRE = /^(https?:)?\/\//
|
||||
const externalRE = /^(?:https?:)?\/\//
|
||||
export function isExternalUrl(url: string): boolean {
|
||||
return externalRE.test(url)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ export const transformSrcset: NodeTransform = (
|
|||
|
||||
const shouldProcessUrl = (url: string) => {
|
||||
return (
|
||||
url &&
|
||||
!isExternalUrl(url) &&
|
||||
!isDataUrl(url) &&
|
||||
(options.includeAbsolute || isRelativeUrl(url))
|
||||
|
|
|
|||
|
|
@ -317,6 +317,35 @@ describe('ssr: components', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
// #13724
|
||||
test('slot content with v-memo', () => {
|
||||
const { code } = compile(`<foo><bar v-memo="[]" /></foo>`)
|
||||
expect(code).not.toMatch(`_cache`)
|
||||
expect(compile(`<foo><bar v-memo="[]" /></foo>`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, createVNode: _createVNode } = require("vue")
|
||||
const { ssrRenderComponent: _ssrRenderComponent } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
const _component_foo = _resolveComponent("foo")
|
||||
const _component_bar = _resolveComponent("bar")
|
||||
|
||||
_push(_ssrRenderComponent(_component_foo, _attrs, {
|
||||
default: _withCtx((_, _push, _parent, _scopeId) => {
|
||||
if (_push) {
|
||||
_push(_ssrRenderComponent(_component_bar, null, null, _parent, _scopeId))
|
||||
} else {
|
||||
return [
|
||||
_createVNode(_component_bar)
|
||||
]
|
||||
}
|
||||
}),
|
||||
_: 1 /* STABLE */
|
||||
}, _parent))
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
describe('built-in fallthroughs', () => {
|
||||
test('transition', () => {
|
||||
expect(compile(`<transition><div/></transition>`).code)
|
||||
|
|
|
|||
|
|
@ -101,6 +101,28 @@ describe('transition-group', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
test('with dynamic tag shorthand', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<transition-group :tag><div v-for="i in list"/></transition-group>`,
|
||||
).code,
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<\${
|
||||
_ctx.tag
|
||||
}\${
|
||||
_ssrRenderAttrs(_attrs)
|
||||
}>\`)
|
||||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`</\${_ctx.tag}>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with multi fragments children', () => {
|
||||
expect(
|
||||
compile(
|
||||
|
|
|
|||
|
|
@ -11,9 +11,9 @@ describe('ssr: v-show', () => {
|
|||
const { ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${_ssrRenderAttrs(_mergeProps({
|
||||
_push(\`<div\${_ssrRenderAttrs(_mergeProps(_attrs, {
|
||||
style: (_ctx.foo) ? null : { display: "none" }
|
||||
}, _attrs))}></div>\`)
|
||||
}))}></div>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
|
@ -92,6 +92,24 @@ describe('ssr: v-show', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
test('with style + display', () => {
|
||||
expect(compileWithWrapper(`<div v-show="foo" style="display:flex" />`).code)
|
||||
.toMatchInlineSnapshot(`
|
||||
"const { ssrRenderStyle: _ssrRenderStyle, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<div\${
|
||||
_ssrRenderAttrs(_attrs)
|
||||
}><div style="\${
|
||||
_ssrRenderStyle([
|
||||
{"display":"flex"},
|
||||
(_ctx.foo) ? null : { display: "none" }
|
||||
])
|
||||
}"></div></div>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with v-bind', () => {
|
||||
expect(
|
||||
compileWithWrapper(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.17",
|
||||
"version": "3.5.24",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ if (__TEST__) {
|
|||
if (SSRErrorCodes.X_SSR_UNSAFE_ATTR_NAME < DOMErrorCodes.__EXTEND_POINT__) {
|
||||
throw new Error(
|
||||
`SSRErrorCodes need to be updated to ${
|
||||
DOMErrorCodes.__EXTEND_POINT__ + 1
|
||||
DOMErrorCodes.__EXTEND_POINT__
|
||||
} to match extension point from core DOMErrorCodes.`,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
transformExpression,
|
||||
transformOn,
|
||||
transformStyle,
|
||||
transformVBindShorthand,
|
||||
} from '@vue/compiler-dom'
|
||||
import { ssrCodegenTransform } from './ssrCodegenTransform'
|
||||
import { ssrTransformElement } from './transforms/ssrTransformElement'
|
||||
|
|
@ -55,6 +56,7 @@ export function compile(
|
|||
...options,
|
||||
hoistStatic: false,
|
||||
nodeTransforms: [
|
||||
transformVBindShorthand,
|
||||
ssrTransformIf,
|
||||
ssrTransformFor,
|
||||
trackVForSlotScopes,
|
||||
|
|
|
|||
|
|
@ -88,6 +88,17 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||
const hasCustomDir = node.props.some(
|
||||
p => p.type === NodeTypes.DIRECTIVE && !isBuiltInDirective(p.name),
|
||||
)
|
||||
|
||||
// v-show has a higher priority in ssr
|
||||
const vShowPropIndex = node.props.findIndex(
|
||||
i => i.type === NodeTypes.DIRECTIVE && i.name === 'show',
|
||||
)
|
||||
if (vShowPropIndex !== -1) {
|
||||
const vShowProp = node.props[vShowPropIndex]
|
||||
node.props.splice(vShowPropIndex, 1)
|
||||
node.props.push(vShowProp)
|
||||
}
|
||||
|
||||
const needMergeProps = hasDynamicVBind || hasCustomDir
|
||||
if (needMergeProps) {
|
||||
const { props, directives } = buildProps(
|
||||
|
|
@ -111,8 +122,13 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||
| InterpolationNode
|
||||
| undefined
|
||||
// If interpolation, this is dynamic <textarea> content, potentially
|
||||
// injected by v-model and takes higher priority than v-bind value
|
||||
if (!existingText || existingText.type !== NodeTypes.INTERPOLATION) {
|
||||
// injected by v-model and takes higher priority than v-bind value.
|
||||
// Additionally, directives with content overrides (v-text/v-html)
|
||||
// have higher priority than the merged props.
|
||||
if (
|
||||
!hasContentOverrideDirective(node) &&
|
||||
(!existingText || existingText.type !== NodeTypes.INTERPOLATION)
|
||||
) {
|
||||
// <textarea> with dynamic v-bind. We don't know if the final props
|
||||
// will contain .value, so we will have to do something special:
|
||||
// assign the merged props to a temp variable, and check whether
|
||||
|
|
@ -165,9 +181,8 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||
]
|
||||
}
|
||||
} else if (directives.length && !node.children.length) {
|
||||
// v-text directive has higher priority than the merged props
|
||||
const vText = findDir(node, 'text')
|
||||
if (!vText) {
|
||||
// v-text/v-html have higher priority than the merged props
|
||||
if (!hasContentOverrideDirective(node)) {
|
||||
const tempId = `_temp${context.temps++}`
|
||||
propsExp.arguments = [
|
||||
createAssignmentExpression(
|
||||
|
|
@ -438,6 +453,10 @@ function findVModel(node: PlainElementNode): DirectiveNode | undefined {
|
|||
) as DirectiveNode | undefined
|
||||
}
|
||||
|
||||
function hasContentOverrideDirective(node: PlainElementNode): boolean {
|
||||
return !!findDir(node, 'text') || !!findDir(node, 'html')
|
||||
}
|
||||
|
||||
export function ssrProcessElement(
|
||||
node: PlainElementNode,
|
||||
context: SSRTransformContext,
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import {
|
|||
|
||||
// Plugin for the first transform pass, which simply constructs the AST node
|
||||
export const ssrTransformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
/^(?:if|else|else-if)$/,
|
||||
processIf,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -498,9 +498,10 @@ describe('reactivity/readonly', () => {
|
|||
const r = ref(false)
|
||||
const ror = readonly(r)
|
||||
const obj = reactive({ ror })
|
||||
expect(() => {
|
||||
obj.ror = true
|
||||
}).toThrow()
|
||||
obj.ror = true
|
||||
expect(
|
||||
`Set operation on key "ror" failed: target is readonly.`,
|
||||
).toHaveBeenWarned()
|
||||
expect(obj.ror).toBe(false)
|
||||
})
|
||||
|
||||
|
|
@ -521,6 +522,16 @@ describe('reactivity/readonly', () => {
|
|||
expect(obj.r).toBe(ro)
|
||||
expect(r.value).toBe(ro)
|
||||
})
|
||||
|
||||
test('should keep nested ref readonly', () => {
|
||||
const items = ref(['one', 'two', 'three'])
|
||||
const obj = {
|
||||
o: readonly({
|
||||
items,
|
||||
}),
|
||||
}
|
||||
expect(isReadonly(obj.o.items)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test('should be able to trigger with triggerRef', () => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.5.17",
|
||||
"version": "3.5.24",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ export const arrayInstrumentations: Record<string | symbol, Function> = <any>{
|
|||
return reactiveReadArray(this).join(separator)
|
||||
},
|
||||
|
||||
// keys() iterator only reads `length`, no optimisation required
|
||||
// keys() iterator only reads `length`, no optimization required
|
||||
|
||||
lastIndexOf(...args: unknown[]) {
|
||||
return searchProxy(this, 'lastIndexOf', args)
|
||||
|
|
@ -200,7 +200,7 @@ function iterator(
|
|||
wrapValue: (value: any) => unknown,
|
||||
) {
|
||||
// note that taking ARRAY_ITERATE dependency here is not strictly equivalent
|
||||
// to calling iterate on the proxified array.
|
||||
// to calling iterate on the proxied array.
|
||||
// creating the iterator does not access any array property:
|
||||
// it is only when .next() is called that length and indexes are accessed.
|
||||
// pushed to the extreme, an iterator could be created in one effect scope,
|
||||
|
|
@ -215,7 +215,7 @@ function iterator(
|
|||
iter._next = iter.next
|
||||
iter.next = () => {
|
||||
const result = iter._next()
|
||||
if (result.value) {
|
||||
if (!result.done) {
|
||||
result.value = wrapValue(result.value)
|
||||
}
|
||||
return result
|
||||
|
|
|
|||
|
|
@ -119,7 +119,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
|
||||
if (isRef(res)) {
|
||||
// ref unwrapping - skip unwrap for Array + integer key.
|
||||
return targetIsArray && isIntegerKey(key) ? res : res.value
|
||||
const value = targetIsArray && isIntegerKey(key) ? res : res.value
|
||||
return isReadonly && isObject(value) ? readonly(value) : value
|
||||
}
|
||||
|
||||
if (isObject(res)) {
|
||||
|
|
@ -153,7 +154,13 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
|||
}
|
||||
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
||||
if (isOldValueReadonly) {
|
||||
return false
|
||||
if (__DEV__) {
|
||||
warn(
|
||||
`Set operation on key "${String(key)}" failed: target is readonly.`,
|
||||
target[key],
|
||||
)
|
||||
}
|
||||
return true
|
||||
} else {
|
||||
oldValue.value = value
|
||||
return true
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ function createInstrumentations(
|
|||
get size() {
|
||||
const target = (this as unknown as IterableCollections)[ReactiveFlags.RAW]
|
||||
!readonly && track(toRaw(target), TrackOpTypes.ITERATE, ITERATE_KEY)
|
||||
return Reflect.get(target, 'size', target)
|
||||
return target.size
|
||||
},
|
||||
has(this: CollectionTypes, key: unknown): boolean {
|
||||
const target = this[ReactiveFlags.RAW]
|
||||
|
|
|
|||
|
|
@ -311,7 +311,7 @@ function prepareDeps(sub: Subscriber) {
|
|||
}
|
||||
|
||||
function cleanupDeps(sub: Subscriber) {
|
||||
// Cleanup unsued deps
|
||||
// Cleanup unused deps
|
||||
let head
|
||||
let tail = sub.depsTail
|
||||
let link = tail
|
||||
|
|
@ -470,11 +470,6 @@ function removeDep(link: Link) {
|
|||
}
|
||||
}
|
||||
|
||||
export interface ReactiveEffectRunner<T = any> {
|
||||
(): T
|
||||
effect: ReactiveEffect
|
||||
}
|
||||
|
||||
export function effect<T = any>(
|
||||
fn: () => T,
|
||||
options?: ReactiveEffectOptions,
|
||||
|
|
|
|||
|
|
@ -275,7 +275,7 @@ export function proxyRefs<T extends object>(
|
|||
objectWithRefs: T,
|
||||
): ShallowUnwrapRef<T> {
|
||||
return isReactive(objectWithRefs)
|
||||
? objectWithRefs
|
||||
? (objectWithRefs as ShallowUnwrapRef<T>)
|
||||
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -331,17 +331,17 @@ export function watch(
|
|||
export function traverse(
|
||||
value: unknown,
|
||||
depth: number = Infinity,
|
||||
seen?: Set<unknown>,
|
||||
seen?: Map<unknown, number>,
|
||||
): unknown {
|
||||
if (depth <= 0 || !isObject(value) || (value as any)[ReactiveFlags.SKIP]) {
|
||||
return value
|
||||
}
|
||||
|
||||
seen = seen || new Set()
|
||||
if (seen.has(value)) {
|
||||
seen = seen || new Map()
|
||||
if ((seen.get(value) || 0) >= depth) {
|
||||
return value
|
||||
}
|
||||
seen.add(value)
|
||||
seen.set(value, depth)
|
||||
depth--
|
||||
if (isRef(value)) {
|
||||
traverse(value.value, depth, seen)
|
||||
|
|
|
|||
|
|
@ -1689,6 +1689,57 @@ describe('api: watch', () => {
|
|||
expect(cb).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
test('watching the same object at different depths', async () => {
|
||||
const arr1: any[] = reactive([[[{ foo: {} }]]])
|
||||
const arr2 = arr1[0]
|
||||
const arr3 = arr2[0]
|
||||
const obj = arr3[0]
|
||||
arr1.push(arr3)
|
||||
|
||||
const cb1 = vi.fn()
|
||||
const cb2 = vi.fn()
|
||||
const cb3 = vi.fn()
|
||||
const cb4 = vi.fn()
|
||||
watch(arr1, cb1, { deep: 1 })
|
||||
watch(arr1, cb2, { deep: 2 })
|
||||
watch(arr1, cb3, { deep: 3 })
|
||||
watch(arr1, cb4, { deep: 4 })
|
||||
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(0)
|
||||
expect(cb3).toHaveBeenCalledTimes(0)
|
||||
expect(cb4).toHaveBeenCalledTimes(0)
|
||||
|
||||
obj.foo = {}
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(0)
|
||||
expect(cb3).toHaveBeenCalledTimes(1)
|
||||
expect(cb4).toHaveBeenCalledTimes(1)
|
||||
|
||||
obj.foo.bar = 1
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(0)
|
||||
expect(cb3).toHaveBeenCalledTimes(1)
|
||||
expect(cb4).toHaveBeenCalledTimes(2)
|
||||
|
||||
arr3.push(obj.foo)
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(1)
|
||||
expect(cb3).toHaveBeenCalledTimes(2)
|
||||
expect(cb4).toHaveBeenCalledTimes(3)
|
||||
|
||||
obj.foo.bar = 2
|
||||
await nextTick()
|
||||
expect(cb1).toHaveBeenCalledTimes(0)
|
||||
expect(cb2).toHaveBeenCalledTimes(1)
|
||||
expect(cb3).toHaveBeenCalledTimes(3)
|
||||
expect(cb4).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
test('pause / resume', async () => {
|
||||
const count = ref(0)
|
||||
const cb = vi.fn()
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
import {
|
||||
type ComponentPublicInstance,
|
||||
createApp,
|
||||
defineComponent,
|
||||
h,
|
||||
nextTick,
|
||||
|
|
@ -598,4 +599,45 @@ describe('component: emit', () => {
|
|||
render(h(ComponentC), el)
|
||||
expect(renderFn).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('merging emits for a component that is also used as a mixin', () => {
|
||||
const render = () => h('div')
|
||||
const CompA = {
|
||||
render,
|
||||
}
|
||||
const validateByMixin = vi.fn(() => true)
|
||||
const validateByGlobalMixin = vi.fn(() => true)
|
||||
|
||||
const mixin = {
|
||||
emits: {
|
||||
one: validateByMixin,
|
||||
},
|
||||
}
|
||||
|
||||
const CompB = defineComponent({
|
||||
mixins: [mixin, CompA],
|
||||
created(this) {
|
||||
this.$emit('one', 1)
|
||||
},
|
||||
render,
|
||||
})
|
||||
|
||||
const app = createApp({
|
||||
render() {
|
||||
return [h(CompA), h(CompB)]
|
||||
},
|
||||
})
|
||||
|
||||
app.mixin({
|
||||
emits: {
|
||||
one: validateByGlobalMixin,
|
||||
two: null,
|
||||
},
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
app.mount(root)
|
||||
expect(validateByMixin).toHaveBeenCalledTimes(1)
|
||||
expect(validateByGlobalMixin).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -167,13 +167,26 @@ describe('component: proxy', () => {
|
|||
data() {
|
||||
return {
|
||||
foo: 0,
|
||||
$foo: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cmp: () => {
|
||||
throw new Error('value of cmp should not be accessed')
|
||||
},
|
||||
$cmp: () => {
|
||||
throw new Error('value of $cmp should not be read')
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
bar: 1,
|
||||
}
|
||||
},
|
||||
__cssModules: {
|
||||
$style: {},
|
||||
cssStyles: {},
|
||||
},
|
||||
mounted() {
|
||||
instanceProxy = this
|
||||
},
|
||||
|
|
@ -181,6 +194,7 @@ describe('component: proxy', () => {
|
|||
|
||||
const app = createApp(Comp, { msg: 'hello' })
|
||||
app.config.globalProperties.global = 1
|
||||
app.config.globalProperties.$global = 1
|
||||
|
||||
app.mount(nodeOps.createElement('div'))
|
||||
|
||||
|
|
@ -188,12 +202,20 @@ describe('component: proxy', () => {
|
|||
expect('msg' in instanceProxy).toBe(true)
|
||||
// data
|
||||
expect('foo' in instanceProxy).toBe(true)
|
||||
// ctx
|
||||
expect('$foo' in instanceProxy).toBe(false)
|
||||
// setupState
|
||||
expect('bar' in instanceProxy).toBe(true)
|
||||
// ctx
|
||||
expect('cmp' in instanceProxy).toBe(true)
|
||||
expect('$cmp' in instanceProxy).toBe(true)
|
||||
// public properties
|
||||
expect('$el' in instanceProxy).toBe(true)
|
||||
// CSS modules
|
||||
expect('$style' in instanceProxy).toBe(true)
|
||||
expect('cssStyles' in instanceProxy).toBe(true)
|
||||
// global properties
|
||||
expect('global' in instanceProxy).toBe(true)
|
||||
expect('$global' in instanceProxy).toBe(true)
|
||||
|
||||
// non-existent
|
||||
expect('$foobar' in instanceProxy).toBe(false)
|
||||
|
|
@ -202,11 +224,15 @@ describe('component: proxy', () => {
|
|||
// #4962 triggering getter should not cause non-existent property to
|
||||
// pass the has check
|
||||
instanceProxy.baz
|
||||
instanceProxy.$baz
|
||||
expect('baz' in instanceProxy).toBe(false)
|
||||
expect('$baz' in instanceProxy).toBe(false)
|
||||
|
||||
// set non-existent (goes into proxyTarget sink)
|
||||
instanceProxy.baz = 1
|
||||
expect('baz' in instanceProxy).toBe(true)
|
||||
instanceProxy.$baz = 1
|
||||
expect('$baz' in instanceProxy).toBe(true)
|
||||
|
||||
// dev mode ownKeys check for console inspection
|
||||
// should only expose own keys
|
||||
|
|
@ -214,7 +240,10 @@ describe('component: proxy', () => {
|
|||
'msg',
|
||||
'bar',
|
||||
'foo',
|
||||
'cmp',
|
||||
'$cmp',
|
||||
'baz',
|
||||
'$baz',
|
||||
])
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
nodeOps,
|
||||
ref,
|
||||
render,
|
||||
serializeInner,
|
||||
useSlots,
|
||||
} from '@vue/runtime-test'
|
||||
import { createBlock, normalizeVNode } from '../src/vnode'
|
||||
|
|
@ -55,14 +56,10 @@ describe('component: slots', () => {
|
|||
expect(Object.getOwnPropertyDescriptor(slots, '_')!.enumerable).toBe(
|
||||
false,
|
||||
)
|
||||
expect(slots).toHaveProperty('__')
|
||||
expect(Object.getOwnPropertyDescriptor(slots, '__')!.enumerable).toBe(
|
||||
false,
|
||||
)
|
||||
return h('div')
|
||||
},
|
||||
}
|
||||
const slots = { foo: () => {}, _: 1, __: [1] }
|
||||
const slots = { foo: () => {}, _: 1 }
|
||||
render(createBlock(Comp, null, slots), nodeOps.createElement('div'))
|
||||
})
|
||||
|
||||
|
|
@ -74,6 +71,10 @@ describe('component: slots', () => {
|
|||
footer: ['f1', 'f2'],
|
||||
})
|
||||
|
||||
expect(
|
||||
'[Vue warn]: Non-function value encountered for slot "_inner". Prefer function slots for better performance.',
|
||||
).toHaveBeenWarned()
|
||||
|
||||
expect(
|
||||
'[Vue warn]: Non-function value encountered for slot "header". Prefer function slots for better performance.',
|
||||
).toHaveBeenWarned()
|
||||
|
|
@ -82,8 +83,8 @@ describe('component: slots', () => {
|
|||
'[Vue warn]: Non-function value encountered for slot "footer". Prefer function slots for better performance.',
|
||||
).toHaveBeenWarned()
|
||||
|
||||
expect(slots).not.toHaveProperty('_inner')
|
||||
expect(slots).not.toHaveProperty('foo')
|
||||
expect(slots._inner()).toMatchObject([normalizeVNode('_inner')])
|
||||
expect(slots.header()).toMatchObject([normalizeVNode('header')])
|
||||
expect(slots.footer()).toMatchObject([
|
||||
normalizeVNode('f1'),
|
||||
|
|
@ -442,4 +443,22 @@ describe('component: slots', () => {
|
|||
'Slot "default" invoked outside of the render function',
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('slot name starts with underscore', () => {
|
||||
const Comp = {
|
||||
setup(_: any, { slots }: any) {
|
||||
return () => slots._foo()
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () => h(Comp, null, { _foo: () => 'foo' })
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
createApp(App).mount(root)
|
||||
expect(serializeInner(root)).toBe('foo')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import {
|
|||
onUnmounted,
|
||||
ref,
|
||||
render,
|
||||
renderList,
|
||||
renderSlot,
|
||||
resolveDynamicComponent,
|
||||
serializeInner,
|
||||
shallowRef,
|
||||
|
|
@ -2161,6 +2163,80 @@ describe('Suspense', () => {
|
|||
await Promise.all(deps)
|
||||
})
|
||||
|
||||
// #13453
|
||||
test('add new async deps during patching', async () => {
|
||||
const getComponent = (type: string) => {
|
||||
if (type === 'A') {
|
||||
return defineAsyncComponent({
|
||||
setup() {
|
||||
return () => h('div', 'A')
|
||||
},
|
||||
})
|
||||
}
|
||||
return defineAsyncComponent({
|
||||
setup() {
|
||||
return () => h('div', 'B')
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const types = ref(['A'])
|
||||
const add = async () => {
|
||||
types.value.push('B')
|
||||
}
|
||||
|
||||
const update = async () => {
|
||||
// mount Suspense B
|
||||
// [Suspense A] -> [Suspense A(pending), Suspense B(pending)]
|
||||
await add()
|
||||
// patch Suspense B (still pending)
|
||||
// [Suspense A(pending), Suspense B(pending)] -> [Suspense B(pending)]
|
||||
types.value.shift()
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return h(Fragment, null, [
|
||||
renderList(types.value, type => {
|
||||
return h(
|
||||
Suspense,
|
||||
{ key: type },
|
||||
{
|
||||
default: () => [
|
||||
renderSlot(this.$slots, 'default', { type: type }),
|
||||
],
|
||||
},
|
||||
)
|
||||
}),
|
||||
])
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Comp, null, {
|
||||
default: (params: any) => [h(getComponent(params.type))],
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe(`<!---->`)
|
||||
|
||||
await Promise.all(deps)
|
||||
expect(serializeInner(root)).toBe(`<div>A</div>`)
|
||||
|
||||
update()
|
||||
await nextTick()
|
||||
// wait for both A and B to resolve
|
||||
await Promise.all(deps)
|
||||
// wait for new B to resolve
|
||||
await Promise.all(deps)
|
||||
expect(serializeInner(root)).toBe(`<div>B</div>`)
|
||||
})
|
||||
|
||||
describe('warnings', () => {
|
||||
// base function to check if a combination of slots warns or not
|
||||
function baseCheckWarn(
|
||||
|
|
@ -2230,5 +2306,57 @@ describe('Suspense', () => {
|
|||
fallback: [h('div'), h('div')],
|
||||
})
|
||||
})
|
||||
|
||||
// #13559
|
||||
test('renders multiple async components in Suspense with v-for and updates on items change', async () => {
|
||||
const CompAsyncSetup = defineAsyncComponent({
|
||||
props: ['item'],
|
||||
render(ctx: any) {
|
||||
return h('div', ctx.item.name)
|
||||
},
|
||||
})
|
||||
|
||||
const items = ref([
|
||||
{ id: 1, name: '111' },
|
||||
{ id: 2, name: '222' },
|
||||
{ id: 3, name: '333' },
|
||||
])
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: () =>
|
||||
h(
|
||||
Fragment,
|
||||
null,
|
||||
items.value.map(item =>
|
||||
h(CompAsyncSetup, { item, key: item.id }),
|
||||
),
|
||||
),
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
await nextTick()
|
||||
await Promise.all(deps)
|
||||
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>111</div><div>222</div><div>333</div>`,
|
||||
)
|
||||
|
||||
items.value = [
|
||||
{ id: 4, name: '444' },
|
||||
{ id: 5, name: '555' },
|
||||
{ id: 6, name: '666' },
|
||||
]
|
||||
await nextTick()
|
||||
await Promise.all(deps)
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div>444</div><div>555</div><div>666</div>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -106,6 +106,134 @@ describe('useTemplateRef', () => {
|
|||
expect(tRef!.value).toBe(null)
|
||||
})
|
||||
|
||||
test('should work when used with direct ref value with ref_key', () => {
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
return () => h('div', { ref: tRef, ref_key: key })
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toBe(root.children[0])
|
||||
})
|
||||
|
||||
test('should work when used with direct ref value with ref_key and ref_for', () => {
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
[1, 2, 3].map(x =>
|
||||
h('span', { ref: tRef, ref_key: key, ref_for: true }, x.toString()),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toHaveLength(3)
|
||||
})
|
||||
|
||||
test('should work when used with direct ref value with ref_key and dynamic value', async () => {
|
||||
const refMode = ref('h1-ref')
|
||||
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
switch (refMode.value) {
|
||||
case 'h1-ref':
|
||||
return h('h1', { ref: tRef, ref_key: key })
|
||||
case 'h2-ref':
|
||||
return h('h2', { ref: tRef, ref_key: key })
|
||||
case 'no-ref':
|
||||
return h('span')
|
||||
case 'nothing':
|
||||
return null
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect(tRef!.value.tag).toBe('h1')
|
||||
|
||||
refMode.value = 'h2-ref'
|
||||
await nextTick()
|
||||
expect(tRef!.value.tag).toBe('h2')
|
||||
|
||||
refMode.value = 'no-ref'
|
||||
await nextTick()
|
||||
expect(tRef!.value).toBeNull()
|
||||
|
||||
refMode.value = 'nothing'
|
||||
await nextTick()
|
||||
expect(tRef!.value).toBeNull()
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should work when used with dynamic direct refs and ref_keys', async () => {
|
||||
const refKey = ref('foo')
|
||||
|
||||
let tRefs: Record<string, ShallowRef>
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRefs = {
|
||||
foo: useTemplateRef('foo'),
|
||||
bar: useTemplateRef('bar'),
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return h('div', { ref: tRefs[refKey.value], ref_key: refKey.value })
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect(tRefs!['foo'].value).toBe(root.children[0])
|
||||
expect(tRefs!['bar'].value).toBeNull()
|
||||
|
||||
refKey.value = 'bar'
|
||||
await nextTick()
|
||||
expect(tRefs!['foo'].value).toBeNull()
|
||||
expect(tRefs!['bar'].value).toBe(root.children[0])
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should not work when used with direct ref value without ref_key (in dev mode)', () => {
|
||||
let tRef: ShallowRef
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef('refKey')
|
||||
return () => h('div', { ref: tRef })
|
||||
},
|
||||
}
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect(tRef!.value).toBeNull()
|
||||
})
|
||||
|
||||
test('should work when used as direct ref value (compiled in prod mode)', () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
|
|
@ -125,4 +253,65 @@ describe('useTemplateRef', () => {
|
|||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
test('should work when used as direct ref value with ref_key and ref_for (compiled in prod mode)', () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
let tRef: ShallowRef
|
||||
const key = 'refKey'
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef(key)
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
[1, 2, 3].map(x =>
|
||||
h(
|
||||
'span',
|
||||
{ ref: tRef, ref_key: key, ref_for: true },
|
||||
x.toString(),
|
||||
),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toHaveLength(3)
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
|
||||
test('should work when used as direct ref value with ref_for but without ref_key (compiled in prod mode)', () => {
|
||||
__DEV__ = false
|
||||
try {
|
||||
let tRef: ShallowRef
|
||||
const Comp = {
|
||||
setup() {
|
||||
tRef = useTemplateRef('refKey')
|
||||
},
|
||||
render() {
|
||||
return h(
|
||||
'div',
|
||||
[1, 2, 3].map(x =>
|
||||
h('span', { ref: tRef, ref_for: true }, x.toString()),
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
|
||||
expect('target is readonly').not.toHaveBeenWarned()
|
||||
expect(tRef!.value).toHaveLength(3)
|
||||
} finally {
|
||||
__DEV__ = true
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -894,4 +894,150 @@ describe('hot module replacement', () => {
|
|||
await timeout()
|
||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
||||
})
|
||||
|
||||
test('multi reload child wrapped in Suspense + KeepAlive', async () => {
|
||||
const id = 'test-child-reload-3'
|
||||
const Child: ComponentOptions = {
|
||||
__hmrId: id,
|
||||
setup() {
|
||||
const count = ref(0)
|
||||
return { count }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
}
|
||||
createRecord(id, Child)
|
||||
|
||||
const appId = 'test-app-id'
|
||||
const App: ComponentOptions = {
|
||||
__hmrId: appId,
|
||||
components: { Child },
|
||||
render: compileToFunction(`
|
||||
<KeepAlive>
|
||||
<Suspense>
|
||||
<Child />
|
||||
</Suspense>
|
||||
</KeepAlive>
|
||||
`),
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe('<div>0</div>')
|
||||
await timeout()
|
||||
reload(id, {
|
||||
__hmrId: id,
|
||||
setup() {
|
||||
const count = ref(1)
|
||||
return { count }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
})
|
||||
await timeout()
|
||||
expect(serializeInner(root)).toBe('<div>1</div>')
|
||||
|
||||
reload(id, {
|
||||
__hmrId: id,
|
||||
setup() {
|
||||
const count = ref(2)
|
||||
return { count }
|
||||
},
|
||||
render: compileToFunction(`<div>{{ count }}</div>`),
|
||||
})
|
||||
await timeout()
|
||||
expect(serializeInner(root)).toBe('<div>2</div>')
|
||||
})
|
||||
|
||||
test('rerender for nested component', () => {
|
||||
const id = 'child-nested-rerender'
|
||||
const Foo: ComponentOptions = {
|
||||
__hmrId: id,
|
||||
render() {
|
||||
return this.$slots.default()
|
||||
},
|
||||
}
|
||||
createRecord(id, Foo)
|
||||
|
||||
const parentId = 'parent-nested-rerender'
|
||||
const Parent: ComponentOptions = {
|
||||
__hmrId: parentId,
|
||||
render() {
|
||||
return h(Foo, null, {
|
||||
default: () => this.$slots.default(),
|
||||
_: 3 /* FORWARDED */,
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
const appId = 'app-nested-rerender'
|
||||
const App: ComponentOptions = {
|
||||
__hmrId: appId,
|
||||
render: () =>
|
||||
h(Parent, null, {
|
||||
default: () => [
|
||||
h(Foo, null, {
|
||||
default: () => ['foo'],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
}
|
||||
createRecord(parentId, App)
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe('foo')
|
||||
|
||||
rerender(id, () => 'bar')
|
||||
expect(serializeInner(root)).toBe('bar')
|
||||
})
|
||||
|
||||
// https://github.com/vitejs/vite-plugin-vue/issues/599
|
||||
// Both Outer and Inner are reloaded when './server.js' changes
|
||||
test('reload nested components from single update', async () => {
|
||||
const innerId = 'nested-reload-inner'
|
||||
const outerId = 'nested-reload-outer'
|
||||
|
||||
let Inner = {
|
||||
__hmrId: innerId,
|
||||
render() {
|
||||
return h('div', 'foo')
|
||||
},
|
||||
}
|
||||
let Outer = {
|
||||
__hmrId: outerId,
|
||||
render() {
|
||||
return h(Inner)
|
||||
},
|
||||
}
|
||||
|
||||
createRecord(innerId, Inner)
|
||||
createRecord(outerId, Outer)
|
||||
|
||||
const App = {
|
||||
render: () => h(Outer),
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe('<div>foo</div>')
|
||||
|
||||
Inner = {
|
||||
__hmrId: innerId,
|
||||
render() {
|
||||
return h('div', 'bar')
|
||||
},
|
||||
}
|
||||
Outer = {
|
||||
__hmrId: outerId,
|
||||
render() {
|
||||
return h(Inner)
|
||||
},
|
||||
}
|
||||
|
||||
// trigger reload for both Outer and Inner
|
||||
reload(outerId, Outer)
|
||||
reload(innerId, Inner)
|
||||
await nextTick()
|
||||
|
||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -82,6 +82,11 @@ describe('SSR hydration', () => {
|
|||
expect(`Hydration children mismatch in <div>`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('text w/ newlines', async () => {
|
||||
mountWithHydration('<div>1\n2\n3</div>', () => h('div', '1\r\n2\r3'))
|
||||
expect(`Hydration text mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('comment', () => {
|
||||
const { vnode, container } = mountWithHydration('<!---->', () => null)
|
||||
expect(vnode.el).toBe(container.firstChild)
|
||||
|
|
@ -1160,6 +1165,69 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
})
|
||||
|
||||
// #13510
|
||||
test('update async component after parent mount before async component resolve', async () => {
|
||||
const Comp = {
|
||||
props: ['toggle'],
|
||||
render(this: any) {
|
||||
return h('h1', [
|
||||
this.toggle ? 'Async component' : 'Updated async component',
|
||||
])
|
||||
},
|
||||
}
|
||||
let serverResolve: any
|
||||
let AsyncComp = defineAsyncComponent(
|
||||
() =>
|
||||
new Promise(r => {
|
||||
serverResolve = r
|
||||
}),
|
||||
)
|
||||
|
||||
const toggle = ref(true)
|
||||
const App = {
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
// change state, after mount and before async component resolve
|
||||
nextTick(() => (toggle.value = false))
|
||||
})
|
||||
|
||||
return () => {
|
||||
return h(AsyncComp, { toggle: toggle.value })
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
// server render
|
||||
const htmlPromise = renderToString(h(App))
|
||||
serverResolve(Comp)
|
||||
const html = await htmlPromise
|
||||
expect(html).toMatchInlineSnapshot(`"<h1>Async component</h1>"`)
|
||||
|
||||
// hydration
|
||||
let clientResolve: any
|
||||
AsyncComp = defineAsyncComponent(
|
||||
() =>
|
||||
new Promise(r => {
|
||||
clientResolve = r
|
||||
}),
|
||||
)
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = html
|
||||
createSSRApp(App).mount(container)
|
||||
|
||||
// resolve
|
||||
clientResolve(Comp)
|
||||
await new Promise(r => setTimeout(r))
|
||||
|
||||
// prevent lazy hydration since the component has been patched
|
||||
expect('Skipping lazy hydration for component').toHaveBeenWarned()
|
||||
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<h1>Updated async component</h1>"`,
|
||||
)
|
||||
})
|
||||
|
||||
test('hydrate safely when property used by async setup changed before render', async () => {
|
||||
const toggle = ref(true)
|
||||
|
||||
|
|
@ -1677,6 +1745,35 @@ describe('SSR hydration', () => {
|
|||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
// #13394
|
||||
test('transition appear work with empty content', async () => {
|
||||
const show = ref(true)
|
||||
const { vnode, container } = mountWithHydration(
|
||||
`<template><!----></template>`,
|
||||
function (this: any) {
|
||||
return h(
|
||||
Transition,
|
||||
{ appear: true },
|
||||
{
|
||||
default: () =>
|
||||
show.value
|
||||
? renderSlot(this.$slots, 'default')
|
||||
: createTextVNode('foo'),
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
// empty slot render as a comment node
|
||||
expect(container.firstChild!.nodeType).toBe(Node.COMMENT_NODE)
|
||||
expect(vnode.el).toBe(container.firstChild)
|
||||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe('foo')
|
||||
})
|
||||
|
||||
test('transition appear with v-if', () => {
|
||||
const show = false
|
||||
const { vnode, container } = mountWithHydration(
|
||||
|
|
@ -2265,6 +2362,30 @@ describe('SSR hydration', () => {
|
|||
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('with disabled teleport + undefined target', async () => {
|
||||
const container = document.createElement('div')
|
||||
const isOpen = ref(false)
|
||||
const App = {
|
||||
setup() {
|
||||
return { isOpen }
|
||||
},
|
||||
template: `
|
||||
<Teleport :to="undefined" :disabled="true">
|
||||
<div v-if="isOpen">
|
||||
Menu is open...
|
||||
</div>
|
||||
</Teleport>`,
|
||||
}
|
||||
container.innerHTML = await renderToString(h(App))
|
||||
const app = createSSRApp(App)
|
||||
app.mount(container)
|
||||
isOpen.value = true
|
||||
await nextTick()
|
||||
expect(container.innerHTML).toBe(
|
||||
`<!--teleport start--><div> Menu is open... </div><!--teleport end-->`,
|
||||
)
|
||||
})
|
||||
|
||||
test('escape css var name', () => {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<div style="padding: 4px;--foo\\.bar:red;"></div>`
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
render,
|
||||
serializeInner,
|
||||
shallowRef,
|
||||
watch,
|
||||
} from '@vue/runtime-test'
|
||||
|
||||
describe('api: template refs', () => {
|
||||
|
|
@ -179,6 +180,89 @@ describe('api: template refs', () => {
|
|||
expect(el.value).toBe(null)
|
||||
})
|
||||
|
||||
// #12639
|
||||
it('update and unmount child in the same tick', async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const el = ref(null)
|
||||
const toggle = ref(true)
|
||||
const show = ref(true)
|
||||
|
||||
const Comp = defineComponent({
|
||||
emits: ['change'],
|
||||
props: ['show'],
|
||||
setup(props, { emit }) {
|
||||
watch(
|
||||
() => props.show,
|
||||
() => {
|
||||
emit('change')
|
||||
},
|
||||
)
|
||||
return () => h('div', 'hi')
|
||||
},
|
||||
})
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return {
|
||||
refKey: el,
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return toggle.value
|
||||
? h(Comp, {
|
||||
ref: 'refKey',
|
||||
show: show.value,
|
||||
onChange: () => (toggle.value = false),
|
||||
})
|
||||
: null
|
||||
},
|
||||
}
|
||||
render(h(App), root)
|
||||
expect(el.value).not.toBe(null)
|
||||
|
||||
show.value = false
|
||||
await nextTick()
|
||||
expect(el.value).toBe(null)
|
||||
})
|
||||
|
||||
it('set and change ref in the same tick', async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const show = ref(false)
|
||||
const refName = ref('a')
|
||||
|
||||
const Child = defineComponent({
|
||||
setup() {
|
||||
refName.value = 'b'
|
||||
return () => {}
|
||||
},
|
||||
})
|
||||
|
||||
const Comp = {
|
||||
render() {
|
||||
return h(Child, {
|
||||
ref: refName.value,
|
||||
})
|
||||
},
|
||||
updated(this: any) {
|
||||
expect(this.$refs.a).toBe(null)
|
||||
expect(this.$refs.b).not.toBe(null)
|
||||
},
|
||||
}
|
||||
|
||||
const App = {
|
||||
render() {
|
||||
return show.value ? h(Comp) : null
|
||||
},
|
||||
}
|
||||
|
||||
render(h(App), root)
|
||||
expect(refName.value).toBe('a')
|
||||
|
||||
show.value = true
|
||||
await nextTick()
|
||||
expect(refName.value).toBe('b')
|
||||
})
|
||||
|
||||
it('unset old ref when new ref is absent', async () => {
|
||||
const root1 = nodeOps.createElement('div')
|
||||
const root2 = nodeOps.createElement('div')
|
||||
|
|
|
|||
|
|
@ -553,18 +553,6 @@ describe('vnode', () => {
|
|||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
||||
})
|
||||
|
||||
test('with suspense', () => {
|
||||
const hoist = createVNode('div')
|
||||
let vnode1
|
||||
const vnode =
|
||||
(openBlock(),
|
||||
createBlock('div', null, [
|
||||
hoist,
|
||||
(vnode1 = createVNode(() => {}, null, 'text')),
|
||||
]))
|
||||
expect(vnode.dynamicChildren).toStrictEqual([vnode1])
|
||||
})
|
||||
|
||||
// #1039
|
||||
// <component :is="foo">{{ bar }}</component>
|
||||
// - content is compiled as slot
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.5.17",
|
||||
"version": "3.5.24",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ export interface AsyncComponentOptions<T = any> {
|
|||
export const isAsyncWrapper = (i: ComponentInternalInstance | VNode): boolean =>
|
||||
!!(i.type as ComponentOptions).__asyncLoader
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
export function defineAsyncComponent<
|
||||
T extends Component = { new (): ComponentPublicInstance },
|
||||
>(source: AsyncComponentLoader<T> | AsyncComponentOptions<T>): T {
|
||||
|
|
@ -123,28 +123,30 @@ export function defineAsyncComponent<
|
|||
|
||||
__asyncHydrate(el, instance, hydrate) {
|
||||
let patched = false
|
||||
;(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 performHydrate = () => {
|
||||
// skip hydration if the component has been patched
|
||||
if (__DEV__ && patched) {
|
||||
warn(
|
||||
`Skipping lazy hydration for component '${getComponentName(resolvedComp!)}': ` +
|
||||
`it was updated before lazy hydration performed.`,
|
||||
)
|
||||
return
|
||||
}
|
||||
hydrate()
|
||||
}
|
||||
const teardown = hydrateStrategy(performHydrate, cb =>
|
||||
forEachElement(el, cb),
|
||||
)
|
||||
if (teardown) {
|
||||
;(instance.bum || (instance.bum = [])).push(teardown)
|
||||
}
|
||||
;(instance.u || (instance.u = [])).push(() => (patched = true))
|
||||
}
|
||||
: hydrate
|
||||
: performHydrate
|
||||
if (resolvedComp) {
|
||||
doHydrate()
|
||||
} else {
|
||||
|
|
@ -239,7 +241,10 @@ export function defineAsyncComponent<
|
|||
error: error.value,
|
||||
})
|
||||
} else if (loadingComponent && !delayed.value) {
|
||||
return createVNode(loadingComponent)
|
||||
return createInnerComp(
|
||||
loadingComponent as ConcreteComponent,
|
||||
instance,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export interface App<HostElement = any> {
|
|||
HostElement = any,
|
||||
Value = any,
|
||||
Modifiers extends string = string,
|
||||
Arg extends string = string,
|
||||
Arg = any,
|
||||
>(
|
||||
name: string,
|
||||
): Directive<HostElement, Value, Modifiers, Arg> | undefined
|
||||
|
|
@ -58,7 +58,7 @@ export interface App<HostElement = any> {
|
|||
HostElement = any,
|
||||
Value = any,
|
||||
Modifiers extends string = string,
|
||||
Arg extends string = string,
|
||||
Arg = any,
|
||||
>(
|
||||
name: string,
|
||||
directive: Directive<HostElement, Value, Modifiers, Arg>,
|
||||
|
|
|
|||
|
|
@ -301,7 +301,7 @@ export function defineComponent<
|
|||
>
|
||||
|
||||
// implementation, close to no-op
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
/*@__NO_SIDE_EFFECTS__*/
|
||||
export function defineComponent(
|
||||
options: unknown,
|
||||
extraOptions?: ComponentOptions,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { isFunction } from '@vue/shared'
|
||||
import { currentInstance } from './component'
|
||||
import { currentRenderingInstance } from './componentRenderContext'
|
||||
import { currentInstance, getCurrentInstance } from './component'
|
||||
import { currentApp } from './apiCreateApp'
|
||||
import { warn } from './warning'
|
||||
|
||||
|
|
@ -51,7 +50,7 @@ export function inject(
|
|||
) {
|
||||
// fallback to `currentRenderingInstance` so that this can be called in
|
||||
// a functional component
|
||||
const instance = currentInstance || currentRenderingInstance
|
||||
const instance = getCurrentInstance()
|
||||
|
||||
// also support looking up from app-level provides w/ `app.runWithContext()`
|
||||
if (instance || currentApp) {
|
||||
|
|
@ -90,5 +89,5 @@ export function inject(
|
|||
* user. One example is `useRoute()` in `vue-router`.
|
||||
*/
|
||||
export function hasInjectionContext(): boolean {
|
||||
return !!(currentInstance || currentRenderingInstance || currentApp)
|
||||
return !!(getCurrentInstance() || currentApp)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -319,7 +319,14 @@ type InferDefaults<T> = {
|
|||
[K in keyof T]?: InferDefault<T, T[K]>
|
||||
}
|
||||
|
||||
type NativeType = null | number | string | boolean | symbol | Function
|
||||
type NativeType =
|
||||
| null
|
||||
| undefined
|
||||
| number
|
||||
| string
|
||||
| boolean
|
||||
| symbol
|
||||
| Function
|
||||
|
||||
type InferDefault<P, T> =
|
||||
| ((props: P) => T & {})
|
||||
|
|
@ -382,17 +389,17 @@ export function withDefaults<
|
|||
}
|
||||
|
||||
export function useSlots(): SetupContext['slots'] {
|
||||
return getContext().slots
|
||||
return getContext('useSlots').slots
|
||||
}
|
||||
|
||||
export function useAttrs(): SetupContext['attrs'] {
|
||||
return getContext().attrs
|
||||
return getContext('useAttrs').attrs
|
||||
}
|
||||
|
||||
function getContext(): SetupContext {
|
||||
function getContext(calledFunctionName: string): SetupContext {
|
||||
const i = getCurrentInstance()!
|
||||
if (__DEV__ && !i) {
|
||||
warn(`useContext() called without active instance.`)
|
||||
warn(`${calledFunctionName}() called without active instance.`)
|
||||
}
|
||||
return i.setupContext || (i.setupContext = createSetupContext(i))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { queuePostRenderEffect } from './renderer'
|
|||
import { warn } from './warning'
|
||||
import type { ObjectWatchOptionItem } from './componentOptions'
|
||||
import { useSSRContext } from './helpers/useSsrContext'
|
||||
import type { ComponentPublicInstance } from './componentPublicInstance'
|
||||
|
||||
export type {
|
||||
WatchHandle,
|
||||
|
|
@ -66,7 +67,9 @@ export function watchPostEffect(
|
|||
return doWatch(
|
||||
effect,
|
||||
null,
|
||||
__DEV__ ? extend({}, options as any, { flush: 'post' }) : { flush: 'post' },
|
||||
__DEV__
|
||||
? extend({}, options as WatchEffectOptions, { flush: 'post' })
|
||||
: { flush: 'post' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -77,7 +80,9 @@ export function watchSyncEffect(
|
|||
return doWatch(
|
||||
effect,
|
||||
null,
|
||||
__DEV__ ? extend({}, options as any, { flush: 'sync' }) : { flush: 'sync' },
|
||||
__DEV__
|
||||
? extend({}, options as WatchEffectOptions, { flush: 'sync' })
|
||||
: { flush: 'sync' },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -243,11 +248,11 @@ export function instanceWatch(
|
|||
value: WatchCallback | ObjectWatchOptionItem,
|
||||
options?: WatchOptions,
|
||||
): WatchHandle {
|
||||
const publicThis = this.proxy as any
|
||||
const publicThis = this.proxy
|
||||
const getter = isString(source)
|
||||
? source.includes('.')
|
||||
? createPathGetter(publicThis, source)
|
||||
: () => publicThis[source]
|
||||
? createPathGetter(publicThis!, source)
|
||||
: () => publicThis![source as keyof typeof publicThis]
|
||||
: source.bind(publicThis, publicThis)
|
||||
let cb
|
||||
if (isFunction(value)) {
|
||||
|
|
@ -262,12 +267,15 @@ export function instanceWatch(
|
|||
return res
|
||||
}
|
||||
|
||||
export function createPathGetter(ctx: any, path: string) {
|
||||
export function createPathGetter(
|
||||
ctx: ComponentPublicInstance,
|
||||
path: string,
|
||||
): () => WatchSource | WatchSource[] | WatchEffect | object {
|
||||
const segments = path.split('.')
|
||||
return (): any => {
|
||||
return (): WatchSource | WatchSource[] | WatchEffect | object => {
|
||||
let cur = ctx
|
||||
for (let i = 0; i < segments.length && cur; i++) {
|
||||
cur = cur[segments[i]]
|
||||
cur = cur[segments[i] as keyof typeof cur]
|
||||
}
|
||||
return cur
|
||||
}
|
||||
|
|
|
|||
|
|
@ -536,7 +536,7 @@ function installCompatMount(
|
|||
if (__DEV__) {
|
||||
for (let i = 0; i < container.attributes.length; i++) {
|
||||
const attr = container.attributes[i]
|
||||
if (attr.name !== 'v-cloak' && /^(v-|:|@)/.test(attr.name)) {
|
||||
if (attr.name !== 'v-cloak' && /^(?:v-|:|@)/.test(attr.name)) {
|
||||
warnDeprecation(DeprecationTypes.GLOBAL_MOUNT_CONTAINER, null)
|
||||
break
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue