mirror of https://github.com/vuejs/core.git
Merge branch 'main' into compat-array-watch
# Conflicts: # packages/reactivity/src/watch.ts
This commit is contained in:
commit
5d04c062d6
|
@ -1,18 +1,17 @@
|
||||||
{
|
{
|
||||||
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||||
extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'],
|
extends: ['config:recommended', 'schedule:weekly', 'group:allNonMajor'],
|
||||||
labels: ['dependencies'],
|
labels: ['dependencies'],
|
||||||
ignorePaths: ['**/__tests__/**'],
|
ignorePaths: ['**/__tests__/**'],
|
||||||
rangeStrategy: 'bump',
|
rangeStrategy: 'bump',
|
||||||
packageRules: [
|
packageRules: [
|
||||||
{
|
{
|
||||||
depTypeList: ['peerDependencies'],
|
matchDepTypes: ['peerDependencies'],
|
||||||
enabled: false,
|
enabled: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'test',
|
groupName: 'test',
|
||||||
matchPackageNames: ['vitest', 'jsdom', 'puppeteer'],
|
matchPackageNames: ['vitest', 'jsdom', 'puppeteer', '@vitest{/,}**'],
|
||||||
matchPackagePrefixes: ['@vitest'],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'playground',
|
groupName: 'playground',
|
||||||
|
@ -23,18 +22,28 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'compiler',
|
groupName: 'compiler',
|
||||||
matchPackageNames: ['magic-string'],
|
matchPackageNames: ['magic-string', '@babel{/,}**', 'postcss{/,}**'],
|
||||||
matchPackagePrefixes: ['@babel', 'postcss'],
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'build',
|
groupName: 'build',
|
||||||
matchPackageNames: ['vite', '@swc/core'],
|
matchPackageNames: [
|
||||||
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
|
'vite',
|
||||||
|
'@swc/core',
|
||||||
|
'rollup{/,}**',
|
||||||
|
'esbuild{/,}**',
|
||||||
|
'@rollup{/,}**',
|
||||||
|
'@vitejs{/,}**',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
groupName: 'lint',
|
groupName: 'lint',
|
||||||
matchPackageNames: ['simple-git-hooks', 'lint-staged'],
|
matchPackageNames: [
|
||||||
matchPackagePrefixes: ['typescript-eslint', 'eslint', 'prettier'],
|
'simple-git-hooks',
|
||||||
|
'lint-staged',
|
||||||
|
'typescript-eslint{/,}**',
|
||||||
|
'eslint{/,}**',
|
||||||
|
'prettier{/,}**',
|
||||||
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
ignoreDeps: [
|
ignoreDeps: [
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
@ -31,4 +31,4 @@ jobs:
|
||||||
- name: Run prettier
|
- name: Run prettier
|
||||||
run: pnpm run format
|
run: pnpm run format
|
||||||
|
|
||||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef
|
||||||
|
|
|
@ -17,7 +17,7 @@ jobs:
|
||||||
ref: minor
|
ref: minor
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|
|
@ -15,7 +15,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
|
|
@ -25,7 +25,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
@ -37,7 +37,7 @@ jobs:
|
||||||
run: pnpm install
|
run: pnpm install
|
||||||
|
|
||||||
- name: Download Size Data
|
- name: Download Size Data
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
name: size-data
|
name: size-data
|
||||||
run_id: ${{ github.event.workflow_run.id }}
|
run_id: ${{ github.event.workflow_run.id }}
|
||||||
|
@ -56,7 +56,7 @@ jobs:
|
||||||
path: temp/size/base.txt
|
path: temp/size/base.txt
|
||||||
|
|
||||||
- name: Download Previous Size Data
|
- name: Download Previous Size Data
|
||||||
uses: dawidd6/action-download-artifact@v6
|
uses: dawidd6/action-download-artifact@v9
|
||||||
with:
|
with:
|
||||||
branch: ${{ steps.pr-base.outputs.content }}
|
branch: ${{ steps.pr-base.outputs.content }}
|
||||||
workflow: size-data.yml
|
workflow: size-data.yml
|
||||||
|
|
|
@ -14,7 +14,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
@ -35,7 +35,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
@ -63,7 +63,7 @@ jobs:
|
||||||
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
key: chromium-${{ hashFiles('pnpm-lock.yaml') }}
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
@ -88,7 +88,7 @@ jobs:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Install pnpm
|
- name: Install pnpm
|
||||||
uses: pnpm/action-setup@v4.0.0
|
uses: pnpm/action-setup@v4.1.0
|
||||||
|
|
||||||
- name: Install Node.js
|
- name: Install Node.js
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
|
@ -104,5 +104,8 @@ jobs:
|
||||||
- name: Run prettier
|
- name: Run prettier
|
||||||
run: pnpm run format-check
|
run: pnpm run format-check
|
||||||
|
|
||||||
|
- name: Run tsc
|
||||||
|
run: pnpm run check
|
||||||
|
|
||||||
- name: Run type declaration tests
|
- name: Run type declaration tests
|
||||||
run: pnpm run test-dts
|
run: pnpm run test-dts
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
20
|
22.14.0
|
||||||
|
|
|
@ -13,5 +13,6 @@
|
||||||
},
|
},
|
||||||
"[json]": {
|
"[json]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
},
|
||||||
|
"editor.formatOnSave": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
https://vuejs.org/funding.json
|
71
CHANGELOG.md
71
CHANGELOG.md
|
@ -1,3 +1,74 @@
|
||||||
|
## [3.5.14](https://github.com/vuejs/core/compare/v3.5.13...v3.5.14) (2025-05-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compat:** correct deprecation message for v-bind.sync usage ([#13137](https://github.com/vuejs/core/issues/13137)) ([466b30f](https://github.com/vuejs/core/commit/466b30f4049ec89fb282624ec17d1a93472ab93f)), closes [#13133](https://github.com/vuejs/core/issues/13133)
|
||||||
|
* **compiler-core:** remove slot cache from parent renderCache during unmounting ([#13215](https://github.com/vuejs/core/issues/13215)) ([5d166f3](https://github.com/vuejs/core/commit/5d166f3796a03a497435fc079c6a83a4e9c6cf52))
|
||||||
|
* **compiler-sfc:** fix scope handling for props destructure in function parameters and catch clauses ([8e34357](https://github.com/vuejs/core/commit/8e3435779a667de485cf9efd78667d0ca14c5f84)), closes [#12790](https://github.com/vuejs/core/issues/12790)
|
||||||
|
* **compiler-sfc:** treat the return value of `useTemplateRef` as a definite ref ([#13197](https://github.com/vuejs/core/issues/13197)) ([8ae1122](https://github.com/vuejs/core/commit/8ae11226e8ee938615e17c7b81dc38ae3f7cefb9))
|
||||||
|
* **compiler:** fix spelling error in domTagConfig ([#13043](https://github.com/vuejs/core/issues/13043)) ([388295b](https://github.com/vuejs/core/commit/388295b27f3cc69eba25d325bbe60a36a3df831a))
|
||||||
|
* **customFormatter:** properly accessing ref value during debugger ([#12948](https://github.com/vuejs/core/issues/12948)) ([fdbd026](https://github.com/vuejs/core/commit/fdbd02658301dd794fe0c84f0018d080a07fca9f))
|
||||||
|
* **hmr/teleport:** adjust static children traversal for HMR in dev mode ([#12819](https://github.com/vuejs/core/issues/12819)) ([5e37dd0](https://github.com/vuejs/core/commit/5e37dd009562bcd8080a200c32abde2d6e4f0305)), closes [#12816](https://github.com/vuejs/core/issues/12816)
|
||||||
|
* **hmr:** avoid hydration for hmr root reload ([#12450](https://github.com/vuejs/core/issues/12450)) ([1f98a9c](https://github.com/vuejs/core/commit/1f98a9c493d01c21befa90107f0593bc92a58932)), closes [vitejs/vite-plugin-vue#146](https://github.com/vitejs/vite-plugin-vue/issues/146) [vitejs/vite-plugin-vue#477](https://github.com/vitejs/vite-plugin-vue/issues/477)
|
||||||
|
* **hmr:** avoid hydration for hmr updating ([#12262](https://github.com/vuejs/core/issues/12262)) ([9c4dbbc](https://github.com/vuejs/core/commit/9c4dbbc5185125835ad3e49baba303bd54676111)), closes [#7706](https://github.com/vuejs/core/issues/7706) [#8170](https://github.com/vuejs/core/issues/8170)
|
||||||
|
* **reactivity:** ensure markRaw objects are not reactive ([#12824](https://github.com/vuejs/core/issues/12824)) ([295b5ec](https://github.com/vuejs/core/commit/295b5ec19b6a52c4a56652cc4d6e93a4ea7c14ed)), closes [#12807](https://github.com/vuejs/core/issues/12807)
|
||||||
|
* **reactivity:** ensure multiple effectScope on() and off() calls maintains correct active scope ([22dcbf3](https://github.com/vuejs/core/commit/22dcbf3e20eb84f69c8952f6f70d9990136a4a68)), closes [#12631](https://github.com/vuejs/core/issues/12631) [#12632](https://github.com/vuejs/core/issues/12632) [#12641](https://github.com/vuejs/core/issues/12641)
|
||||||
|
* **reactivity:** should not recompute if computed does not track reactive data ([#12341](https://github.com/vuejs/core/issues/12341)) ([0b23fd2](https://github.com/vuejs/core/commit/0b23fd23833cf085e7e112bf4435cfc9b360d072)), closes [#12337](https://github.com/vuejs/core/issues/12337)
|
||||||
|
* **runtime-core:** stop tracking deps in setRef during unmount ([#13210](https://github.com/vuejs/core/issues/13210)) ([016c472](https://github.com/vuejs/core/commit/016c472bd2e7604b21c69dee1da8545ce26e4d2f))
|
||||||
|
* **runtime-core:** update __vnode of static nodes when patching along the optimized path ([#13223](https://github.com/vuejs/core/issues/13223)) ([b3ecee3](https://github.com/vuejs/core/commit/b3ecee3da8ed5c55dea89ce6b4b376b2b722b018))
|
||||||
|
* **runtime-core:** inherit comment nodes during block patch in production build ([#10748](https://github.com/vuejs/core/issues/10748)) ([6264505](https://github.com/vuejs/core/commit/626450590d81f79117b34d2a73073b1dc8f551bd)), closes [#10747](https://github.com/vuejs/core/issues/10747) [#12650](https://github.com/vuejs/core/issues/12650)
|
||||||
|
* **runtime-core:** prevent unmounted vnode from being inserted during transition leave ([#12862](https://github.com/vuejs/core/issues/12862)) ([d6a6ec1](https://github.com/vuejs/core/commit/d6a6ec13ce521683bfb2a22932778ef7b51f8600)), closes [#12860](https://github.com/vuejs/core/issues/12860)
|
||||||
|
* **runtime-core:** respect immutability for readonly reactive arrays in `v-for` ([#13091](https://github.com/vuejs/core/issues/13091)) ([3f27c58](https://github.com/vuejs/core/commit/3f27c58ffbd4309df369bc89493fdc284dc540bb)), closes [#13087](https://github.com/vuejs/core/issues/13087)
|
||||||
|
* **runtime-dom:** always treat autocorrect as attribute ([#13001](https://github.com/vuejs/core/issues/13001)) ([1499135](https://github.com/vuejs/core/commit/1499135c227236e037bb746beeb777941b0b58ff)), closes [#5705](https://github.com/vuejs/core/issues/5705)
|
||||||
|
* **slots:** properly warn if slot invoked in setup ([#12195](https://github.com/vuejs/core/issues/12195)) ([9196222](https://github.com/vuejs/core/commit/9196222ae1d63b52b35ac5fbf5e71494587ccf05)), closes [#12194](https://github.com/vuejs/core/issues/12194)
|
||||||
|
* **ssr:** properly init slots during ssr rendering ([#12441](https://github.com/vuejs/core/issues/12441)) ([2206cd2](https://github.com/vuejs/core/commit/2206cd235a1627c540e795e378b7564a55b47313)), closes [#12438](https://github.com/vuejs/core/issues/12438)
|
||||||
|
* **transition:** fix KeepAlive with transition out-in mode behavior in production ([#12468](https://github.com/vuejs/core/issues/12468)) ([343c891](https://github.com/vuejs/core/commit/343c89122448719bd6ed6bd9de986dfb2721d6bf)), closes [#12465](https://github.com/vuejs/core/issues/12465)
|
||||||
|
* **TransitionGroup:** reset prevChildren to prevent memory leak ([#13183](https://github.com/vuejs/core/issues/13183)) ([8b848cb](https://github.com/vuejs/core/commit/8b848cbbd2af337d23e19e202f9ab433f8580855)), closes [#13181](https://github.com/vuejs/core/issues/13181)
|
||||||
|
* **types:** allow return any for Options API lifecycle hooks ([#5914](https://github.com/vuejs/core/issues/5914)) ([06310e8](https://github.com/vuejs/core/commit/06310e82f5bed62d1b9733dcb18cd8d6edc988de))
|
||||||
|
* **types:** the directive's modifiers should be optional ([#12605](https://github.com/vuejs/core/issues/12605)) ([10e54dc](https://github.com/vuejs/core/commit/10e54dcc86a7967f3196d96200bcbd1d3d42082f))
|
||||||
|
* **typos:** fix comments referencing transformElement.ts ([#12551](https://github.com/vuejs/core/issues/12551))[ci-skip] ([11c053a](https://github.com/vuejs/core/commit/11c053a5429ad0d27a0e2c78b6b026ea00ace116))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **types:** add type TemplateRef ([#12645](https://github.com/vuejs/core/issues/12645)) ([636a861](https://github.com/vuejs/core/commit/636a8619f06c71dfd79f7f6412fd130c4f84226f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## [3.5.13](https://github.com/vuejs/core/compare/v3.5.12...v3.5.13) (2024-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compiler-core:** handle v-memo + v-for with functional key ([#12014](https://github.com/vuejs/core/issues/12014)) ([99009ee](https://github.com/vuejs/core/commit/99009eee0efc238392daba93792d478525b21afa)), closes [#12013](https://github.com/vuejs/core/issues/12013)
|
||||||
|
* **compiler-dom:** properly stringify template string style ([#12392](https://github.com/vuejs/core/issues/12392)) ([2d78539](https://github.com/vuejs/core/commit/2d78539da35322aea5f821b3cf9b02d006abac72)), closes [#12391](https://github.com/vuejs/core/issues/12391)
|
||||||
|
* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
|
||||||
|
* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
|
||||||
|
* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
|
||||||
|
* **reactivity:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
|
||||||
|
* **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370)
|
||||||
|
* **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533)
|
||||||
|
* **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6))
|
||||||
|
* **ssr:** avoid updating subtree of async component if it is resolved ([#12363](https://github.com/vuejs/core/issues/12363)) ([da7ad5e](https://github.com/vuejs/core/commit/da7ad5e3d24f3e108401188d909d27a4910da095)), closes [#12362](https://github.com/vuejs/core/issues/12362)
|
||||||
|
* **ssr:** ensure v-text updates correctly with custom directives in SSR output ([#12311](https://github.com/vuejs/core/issues/12311)) ([1f75d4e](https://github.com/vuejs/core/commit/1f75d4e6dfe18121ebe443cd3e8105d54f727893)), closes [#12309](https://github.com/vuejs/core/issues/12309)
|
||||||
|
* **ssr:** handle initial selected state for select with v-model + v-for option ([#12399](https://github.com/vuejs/core/issues/12399)) ([4f8d807](https://github.com/vuejs/core/commit/4f8d8078221ee52deed266677a227ad2a6d8dd22)), closes [#12395](https://github.com/vuejs/core/issues/12395)
|
||||||
|
* **teleport:** handle deferred teleport update before mounted ([#12168](https://github.com/vuejs/core/issues/12168)) ([8bff142](https://github.com/vuejs/core/commit/8bff142f99b646e9dd15897ec75368fbf34f1534)), closes [#12161](https://github.com/vuejs/core/issues/12161)
|
||||||
|
* **templateRef:** set ref on cached async component which wrapped in KeepAlive ([#12290](https://github.com/vuejs/core/issues/12290)) ([983eb50](https://github.com/vuejs/core/commit/983eb50a17eac76f1bba4394ad0316c62b72191d)), closes [#4999](https://github.com/vuejs/core/issues/4999) [#5004](https://github.com/vuejs/core/issues/5004)
|
||||||
|
* **test:** update snapshot ([#12169](https://github.com/vuejs/core/issues/12169)) ([828d4a4](https://github.com/vuejs/core/commit/828d4a443919fa2aa4e2e92fbd03a5f04b258eea))
|
||||||
|
* **Transition:** fix transition memory leak edge case ([#12182](https://github.com/vuejs/core/issues/12182)) ([660132d](https://github.com/vuejs/core/commit/660132df6c6a8c14bf75e593dc47d2fdada30322)), closes [#12181](https://github.com/vuejs/core/issues/12181)
|
||||||
|
* **transition:** reflow before leave-active class after leave-from ([#12288](https://github.com/vuejs/core/issues/12288)) ([4b479db](https://github.com/vuejs/core/commit/4b479db61d233b054561402ae94ef08550073ea1)), closes [#2593](https://github.com/vuejs/core/issues/2593)
|
||||||
|
* **types:** defineEmits w/ interface declaration ([#12343](https://github.com/vuejs/core/issues/12343)) ([1022eab](https://github.com/vuejs/core/commit/1022eabaa1aaf8436876f5ec5573cb1e4b3959a6)), closes [#8457](https://github.com/vuejs/core/issues/8457)
|
||||||
|
* **v-once:** setting hasOnce to current block only when in v-once ([#12374](https://github.com/vuejs/core/issues/12374)) ([37300fc](https://github.com/vuejs/core/commit/37300fc26190a7299efddbf98800ffd96d5cad96)), closes [#12371](https://github.com/vuejs/core/issues/12371)
|
||||||
|
|
||||||
|
|
||||||
|
### Performance Improvements
|
||||||
|
|
||||||
|
* **reactivity:** do not track inner key `__v_skip`` ([#11690](https://github.com/vuejs/core/issues/11690)) ([d637bd6](https://github.com/vuejs/core/commit/d637bd6c0164c2883e6eabd3c2f1f8c258dedfb1))
|
||||||
|
* **runtime-core:** use feature flag for call to resolveMergedOptions ([#12163](https://github.com/vuejs/core/issues/12163)) ([1755ac0](https://github.com/vuejs/core/commit/1755ac0a108ba3486bd8397e56d3bdcd69196594))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.5.12](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12) (2024-10-11)
|
## [3.5.12](https://github.com/vuejs/core/compare/v3.5.11...v3.5.12) (2024-10-11)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@ Please make sure to respect issue requirements and use [the new issue helper](ht
|
||||||
|
|
||||||
## Stay In Touch
|
## Stay In Touch
|
||||||
|
|
||||||
- [Twitter](https://twitter.com/vuejs)
|
- [X](https://x.com/vuejs)
|
||||||
|
- [Bluesky](https://bsky.app/profile/vuejs.org)
|
||||||
- [Blog](https://blog.vuejs.org/)
|
- [Blog](https://blog.vuejs.org/)
|
||||||
- [Job Board](https://vuejobs.com/?ref=vuejs)
|
- [Job Board](https://vuejobs.com/?ref=vuejs)
|
||||||
|
|
||||||
|
@ -44,7 +45,9 @@ Please make sure to read the [Contributing Guide](https://github.com/vuejs/core/
|
||||||
|
|
||||||
Thank you to all the people who already contributed to Vue!
|
Thank you to all the people who already contributed to Vue!
|
||||||
|
|
||||||
<a href="https://github.com/vuejs/core/graphs/contributors"><img src="https://opencollective.com/vuejs/contributors.svg?width=890" /></a>
|
<a href="https://github.com/vuejs/core/graphs/contributors"><img src="https://opencollective.com/vuejs/contributors.svg?width=890&limit=500" /></a>
|
||||||
|
|
||||||
|
<sub>_Note: Showing the first 500 contributors only due to GitHub image size limitations_</sub>
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
@ -741,17 +741,6 @@ Note that this is a type-only breaking change in a minor release, which adheres
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.13](https://github.com/vuejs/core/compare/v3.3.12...v3.3.13) (2023-12-19)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-core:** fix v-on with modifiers on inline expression of undefined ([#9866](https://github.com/vuejs/core/issues/9866)) ([bae79dd](https://github.com/vuejs/core/commit/bae79ddf8564a2da4a5365cfeb8d811990f42335)), closes [#9865](https://github.com/vuejs/core/issues/9865)
|
|
||||||
* **runtime-dom:** cache event handlers by key/modifiers ([#9851](https://github.com/vuejs/core/issues/9851)) ([04d2c05](https://github.com/vuejs/core/commit/04d2c05054c26b02fbc1d84839b0ed5cd36455b6)), closes [#9849](https://github.com/vuejs/core/issues/9849)
|
|
||||||
* **types:** extract properties from extended collections ([#9854](https://github.com/vuejs/core/issues/9854)) ([24b1c1d](https://github.com/vuejs/core/commit/24b1c1dd57fd55d998aa231a147500e010b10219)), closes [#9852](https://github.com/vuejs/core/issues/9852)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-beta.3](https://github.com/vuejs/core/compare/v3.3.12...v3.4.0-beta.3) (2023-12-16)
|
# [3.4.0-beta.3](https://github.com/vuejs/core/compare/v3.3.12...v3.4.0-beta.3) (2023-12-16)
|
||||||
|
|
||||||
|
|
||||||
|
@ -764,19 +753,6 @@ Note that this is a type-only breaking change in a minor release, which adheres
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.12](https://github.com/vuejs/core/compare/v3.3.11...v3.3.12) (2023-12-16)
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **hydration:** handle appear transition before patch props ([#9837](https://github.com/vuejs/core/issues/9837)) ([e70f4c4](https://github.com/vuejs/core/commit/e70f4c47c553b6e16d8fad70743271ca23802fe7)), closes [#9832](https://github.com/vuejs/core/issues/9832)
|
|
||||||
* **sfc/cssVars:** fix loss of CSS v-bind variables when setting inline style with string value ([#9824](https://github.com/vuejs/core/issues/9824)) ([0a387df](https://github.com/vuejs/core/commit/0a387dfb1d04afb6eae4296b6da76dfdaca77af4)), closes [#9821](https://github.com/vuejs/core/issues/9821)
|
|
||||||
* **ssr:** fix suspense hydration of fallback content ([#7188](https://github.com/vuejs/core/issues/7188)) ([60415b5](https://github.com/vuejs/core/commit/60415b5d67df55f1fd6b176615299c08640fa142))
|
|
||||||
* **types:** add `xmlns:xlink` to `SVGAttributes` ([#9300](https://github.com/vuejs/core/issues/9300)) ([0d61b42](https://github.com/vuejs/core/commit/0d61b429ecf63591d31e09702058fa4c7132e1a7)), closes [#9299](https://github.com/vuejs/core/issues/9299)
|
|
||||||
* **types:** fix `shallowRef` type error ([#9839](https://github.com/vuejs/core/issues/9839)) ([9a57158](https://github.com/vuejs/core/commit/9a571582b53220270e498d8712ea59312c0bef3a))
|
|
||||||
* **types:** support for generic keyof slots ([#8374](https://github.com/vuejs/core/issues/8374)) ([213eba4](https://github.com/vuejs/core/commit/213eba479ce080efc1053fe636f6be4a4c889b44))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-beta.2](https://github.com/vuejs/core/compare/v3.4.0-beta.1...v3.4.0-beta.2) (2023-12-14)
|
# [3.4.0-beta.2](https://github.com/vuejs/core/compare/v3.4.0-beta.1...v3.4.0-beta.2) (2023-12-14)
|
||||||
|
|
||||||
|
|
||||||
|
@ -836,22 +812,6 @@ default.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.11](https://github.com/vuejs/core/compare/v3.3.10...v3.3.11) (2023-12-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **custom-element:** correctly handle number type props in prod ([#8989](https://github.com/vuejs/core/issues/8989)) ([d74d364](https://github.com/vuejs/core/commit/d74d364d62db8e48881af6b5a75ce4fb5f36cc35))
|
|
||||||
* **reactivity:** fix mutation on user proxy of reactive Array ([6ecbd5c](https://github.com/vuejs/core/commit/6ecbd5ce2a7f59314a8326a1d193874b87f4d8c8)), closes [#9742](https://github.com/vuejs/core/issues/9742) [#9751](https://github.com/vuejs/core/issues/9751) [#9750](https://github.com/vuejs/core/issues/9750)
|
|
||||||
* **runtime-dom:** fix width and height prop check condition ([5b00286](https://github.com/vuejs/core/commit/5b002869c533220706f9788b496b8ca8d8e98609)), closes [#9762](https://github.com/vuejs/core/issues/9762)
|
|
||||||
* **shared:** handle Map with symbol keys in toDisplayString ([#9731](https://github.com/vuejs/core/issues/9731)) ([364821d](https://github.com/vuejs/core/commit/364821d6bdb1775e2f55a69bcfb9f40f7acf1506)), closes [#9727](https://github.com/vuejs/core/issues/9727)
|
|
||||||
* **shared:** handle more Symbol cases in toDisplayString ([983d45d](https://github.com/vuejs/core/commit/983d45d4f8eb766b5a16b7ea93b86d3c51618fa6))
|
|
||||||
* **Suspense:** properly get anchor when mount fallback vnode ([#9770](https://github.com/vuejs/core/issues/9770)) ([b700328](https://github.com/vuejs/core/commit/b700328342e17dc16b19316c2e134a26107139d2)), closes [#9769](https://github.com/vuejs/core/issues/9769)
|
|
||||||
* **types:** ref() return type should not be any when initial value is any ([#9768](https://github.com/vuejs/core/issues/9768)) ([cdac121](https://github.com/vuejs/core/commit/cdac12161ec27b45ded48854c3d749664b6d4a6d))
|
|
||||||
* **watch:** should not fire pre watcher on child component unmount ([#7181](https://github.com/vuejs/core/issues/7181)) ([6784f0b](https://github.com/vuejs/core/commit/6784f0b1f8501746ea70d87d18ed63a62cf6b76d)), closes [#7030](https://github.com/vuejs/core/issues/7030)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.10...v3.4.0-alpha.4) (2023-12-04)
|
# [3.4.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.10...v3.4.0-alpha.4) (2023-12-04)
|
||||||
|
|
||||||
|
|
||||||
|
@ -873,37 +833,6 @@ default.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.10](https://github.com/vuejs/core/compare/v3.3.9...v3.3.10) (2023-12-04)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **app:** prevent template from being cached between apps with different options ([#9724](https://github.com/vuejs/core/issues/9724)) ([ec71585](https://github.com/vuejs/core/commit/ec715854ca12520b2afc9e9b3981cbae05ae5206)), closes [#9618](https://github.com/vuejs/core/issues/9618)
|
|
||||||
* **compiler-sfc:** avoid passing forEach index to genMap ([f12db7f](https://github.com/vuejs/core/commit/f12db7fb564a534cef2e5805cc9f54afe5d72fbf))
|
|
||||||
* **compiler-sfc:** deindent pug/jade templates ([6345197](https://github.com/vuejs/core/commit/634519720a21fb5a6871454e1cadad7053a568b8)), closes [#3231](https://github.com/vuejs/core/issues/3231) [#3842](https://github.com/vuejs/core/issues/3842) [#7723](https://github.com/vuejs/core/issues/7723)
|
|
||||||
* **compiler-sfc:** fix :where and :is selector in scoped mode with multiple selectors ([#9735](https://github.com/vuejs/core/issues/9735)) ([c3e2c55](https://github.com/vuejs/core/commit/c3e2c556b532656b50b8ab5cd2d9eabc26622d63)), closes [#9707](https://github.com/vuejs/core/issues/9707)
|
|
||||||
* **compiler-sfc:** generate more treeshaking friendly code ([#9507](https://github.com/vuejs/core/issues/9507)) ([8d74ca0](https://github.com/vuejs/core/commit/8d74ca0e6fa2738ca6854b7e879ff59419f948c7)), closes [#9500](https://github.com/vuejs/core/issues/9500)
|
|
||||||
* **compiler-sfc:** support inferring generic types ([#8511](https://github.com/vuejs/core/issues/8511)) ([eb5e307](https://github.com/vuejs/core/commit/eb5e307c0be62002e62c4c800d0dfacb39b0d4ca)), closes [#8482](https://github.com/vuejs/core/issues/8482)
|
|
||||||
* **compiler-sfc:** support resolving components from props ([#8785](https://github.com/vuejs/core/issues/8785)) ([7cbcee3](https://github.com/vuejs/core/commit/7cbcee3d831241a8bd3588ae92d3f27e3641e25f))
|
|
||||||
* **compiler-sfc:** throw error when failing to load TS during type resolution ([#8883](https://github.com/vuejs/core/issues/8883)) ([4936d2e](https://github.com/vuejs/core/commit/4936d2e11a8d0ca3704bfe408548cb26bb3fd5e9))
|
|
||||||
* **cssVars:** cssVar names should be double-escaped when generating code for ssr ([#8824](https://github.com/vuejs/core/issues/8824)) ([5199a12](https://github.com/vuejs/core/commit/5199a12f8855cd06f24bf355708b5a2134f63176)), closes [#7823](https://github.com/vuejs/core/issues/7823)
|
|
||||||
* **deps:** update compiler to ^7.23.4 ([#9681](https://github.com/vuejs/core/issues/9681)) ([31f6ebc](https://github.com/vuejs/core/commit/31f6ebc4df84490ed29fb75e7bf4259200eb51f0))
|
|
||||||
* **runtime-core:** Suspense get anchor properly in Transition ([#9309](https://github.com/vuejs/core/issues/9309)) ([65f3fe2](https://github.com/vuejs/core/commit/65f3fe273127a8b68e1222fbb306d28d85f01757)), closes [#8105](https://github.com/vuejs/core/issues/8105)
|
|
||||||
* **runtime-dom:** set width/height with units as attribute ([#8781](https://github.com/vuejs/core/issues/8781)) ([bfc1838](https://github.com/vuejs/core/commit/bfc1838f31199de3f189198a3c234fa7bae91386))
|
|
||||||
* **ssr:** avoid computed being accidentally cached before server render ([#9688](https://github.com/vuejs/core/issues/9688)) ([30d5d93](https://github.com/vuejs/core/commit/30d5d93a92b2154406ec04f8aca6b217fa01177c)), closes [#5300](https://github.com/vuejs/core/issues/5300)
|
|
||||||
* **types:** expose emits as props in functional components ([#9234](https://github.com/vuejs/core/issues/9234)) ([887e54c](https://github.com/vuejs/core/commit/887e54c347ea9eac4c721b5e2288f054873d1d30))
|
|
||||||
* **types:** fix reactive collection types ([#8960](https://github.com/vuejs/core/issues/8960)) ([ad27473](https://github.com/vuejs/core/commit/ad274737015c36906d76f3189203093fa3a2e4e7)), closes [#8904](https://github.com/vuejs/core/issues/8904)
|
|
||||||
* **types:** improve return type withKeys and withModifiers ([#9734](https://github.com/vuejs/core/issues/9734)) ([43c3cfd](https://github.com/vuejs/core/commit/43c3cfdec5ae5d70fa2a21e857abc2d73f1a0d07))
|
|
||||||
|
|
||||||
|
|
||||||
### Performance Improvements
|
|
||||||
|
|
||||||
* optimize on* prop check ([38aaa8c](https://github.com/vuejs/core/commit/38aaa8c88648c54fe2616ad9c0961288092fcb44))
|
|
||||||
* **runtime-dom:** cache modifier wrapper functions ([da4a4fb](https://github.com/vuejs/core/commit/da4a4fb5e8eee3c6d31f24ebd79a9d0feca56cb2)), closes [#8882](https://github.com/vuejs/core/issues/8882)
|
|
||||||
* **v-on:** constant handlers with modifiers should not be treated as dynamic ([4d94ebf](https://github.com/vuejs/core/commit/4d94ebfe75174b340d2b794e699cad1add3600a9))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
||||||
|
|
||||||
|
|
||||||
|
@ -960,55 +889,6 @@ default.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.9](https://github.com/vuejs/core/compare/v3.3.8...v3.3.9) (2023-11-25)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compiler-core:** avoid rewriting scope variables in inline for loops ([#7245](https://github.com/vuejs/core/issues/7245)) ([a2d810e](https://github.com/vuejs/core/commit/a2d810eb40cef631f61991ca68b426ee9546aba0)), closes [#7238](https://github.com/vuejs/core/issues/7238)
|
|
||||||
* **compiler-core:** fix `resolveParserPlugins` decorators check ([#9566](https://github.com/vuejs/core/issues/9566)) ([9d0eba9](https://github.com/vuejs/core/commit/9d0eba916f3bf6fb5c03222400edae1a2db7444f)), closes [#9560](https://github.com/vuejs/core/issues/9560)
|
|
||||||
* **compiler-sfc:** consistently escape type-only prop names ([#8654](https://github.com/vuejs/core/issues/8654)) ([3e08d24](https://github.com/vuejs/core/commit/3e08d246dfd8523c54fb8e7a4a6fd5506ffb1bcc)), closes [#8635](https://github.com/vuejs/core/issues/8635) [#8910](https://github.com/vuejs/core/issues/8910) [vitejs/vite-plugin-vue#184](https://github.com/vitejs/vite-plugin-vue/issues/184)
|
|
||||||
* **compiler-sfc:** malformed filename on windows using path.posix.join() ([#9478](https://github.com/vuejs/core/issues/9478)) ([f18a174](https://github.com/vuejs/core/commit/f18a174979626b3429db93c5d5b7ae5448917c70)), closes [#8671](https://github.com/vuejs/core/issues/8671) [#9583](https://github.com/vuejs/core/issues/9583) [#9446](https://github.com/vuejs/core/issues/9446) [#9473](https://github.com/vuejs/core/issues/9473)
|
|
||||||
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([3227e50](https://github.com/vuejs/core/commit/3227e50b32105f8893f7dff2f29278c5b3a9f621))
|
|
||||||
* **compiler-sfc:** support resolve extends interface for defineEmits ([#8470](https://github.com/vuejs/core/issues/8470)) ([9e1b74b](https://github.com/vuejs/core/commit/9e1b74bcd5fa4151f5d1bc02c69fbbfa4762f577)), closes [#8465](https://github.com/vuejs/core/issues/8465)
|
|
||||||
* **hmr/transition:** fix kept-alive component inside transition disappearing after hmr ([#7126](https://github.com/vuejs/core/issues/7126)) ([d11e978](https://github.com/vuejs/core/commit/d11e978fc98dcc83526c167e603b8308f317f786)), closes [#7121](https://github.com/vuejs/core/issues/7121)
|
|
||||||
* **hydration:** force hydration for v-bind with .prop modifier ([364f319](https://github.com/vuejs/core/commit/364f319d214226770d97c98d8fcada80c9e8dde3)), closes [#7490](https://github.com/vuejs/core/issues/7490)
|
|
||||||
* **hydration:** properly hydrate indeterminate prop ([34b5a5d](https://github.com/vuejs/core/commit/34b5a5da4ae9c9faccac237acd7acc8e7e017571)), closes [#7476](https://github.com/vuejs/core/issues/7476)
|
|
||||||
* **reactivity:** clear method on readonly collections should return undefined ([#7316](https://github.com/vuejs/core/issues/7316)) ([657476d](https://github.com/vuejs/core/commit/657476dcdb964be4fbb1277c215c073f3275728e))
|
|
||||||
* **reactivity:** onCleanup also needs to be cleaned ([#8655](https://github.com/vuejs/core/issues/8655)) ([73fd810](https://github.com/vuejs/core/commit/73fd810eebdd383a2b4629f67736c4db1f428abd)), closes [#5151](https://github.com/vuejs/core/issues/5151) [#7695](https://github.com/vuejs/core/issues/7695)
|
|
||||||
* **ssr:** hydration `__vnode` missing for devtools ([#9328](https://github.com/vuejs/core/issues/9328)) ([5156ac5](https://github.com/vuejs/core/commit/5156ac5b38cfa80d3db26f2c9bf40cb22a7521cb))
|
|
||||||
* **types:** allow falsy value types in `StyleValue` ([#7954](https://github.com/vuejs/core/issues/7954)) ([17aa92b](https://github.com/vuejs/core/commit/17aa92b79b31d8bb8b5873ddc599420cb9806db8)), closes [#7955](https://github.com/vuejs/core/issues/7955)
|
|
||||||
* **types:** defineCustomElement using defineComponent return type with emits ([#7937](https://github.com/vuejs/core/issues/7937)) ([5d932a8](https://github.com/vuejs/core/commit/5d932a8e6d14343c9d7fc7c2ecb58ac618b2f938)), closes [#7782](https://github.com/vuejs/core/issues/7782)
|
|
||||||
* **types:** fix `unref` and `toValue` when input union type contains ComputedRef ([#8748](https://github.com/vuejs/core/issues/8748)) ([176d476](https://github.com/vuejs/core/commit/176d47671271b1abc21b1508e9a493c7efca6451)), closes [#8747](https://github.com/vuejs/core/issues/8747) [#8857](https://github.com/vuejs/core/issues/8857)
|
|
||||||
* **types:** fix instance type when props type is incompatible with setup returned type ([#7338](https://github.com/vuejs/core/issues/7338)) ([0e1e8f9](https://github.com/vuejs/core/commit/0e1e8f919e5a74cdaadf9c80ee135088b25e7fa3)), closes [#5885](https://github.com/vuejs/core/issues/5885)
|
|
||||||
* **types:** fix shallowRef return type with union value type ([#7853](https://github.com/vuejs/core/issues/7853)) ([7c44800](https://github.com/vuejs/core/commit/7c448000b0def910c2cfabfdf7ff20a3d6bc844f)), closes [#7852](https://github.com/vuejs/core/issues/7852)
|
|
||||||
* **types:** more precise types for class bindings ([#8012](https://github.com/vuejs/core/issues/8012)) ([46e3374](https://github.com/vuejs/core/commit/46e33744c890bd49482c5e5c5cdea44e00ec84d5))
|
|
||||||
* **types:** remove optional properties from defineProps return type ([#6421](https://github.com/vuejs/core/issues/6421)) ([94c049d](https://github.com/vuejs/core/commit/94c049d930d922069e38ea8700d7ff0970f71e61)), closes [#6420](https://github.com/vuejs/core/issues/6420)
|
|
||||||
* **types:** return type of withDefaults should be readonly ([#8601](https://github.com/vuejs/core/issues/8601)) ([f15debc](https://github.com/vuejs/core/commit/f15debc01acb22d23f5acee97e6f02db88cef11a))
|
|
||||||
* **types:** revert class type restrictions ([5d077c8](https://github.com/vuejs/core/commit/5d077c8754cc14f85d2d6d386df70cf8c0d93842)), closes [#8012](https://github.com/vuejs/core/issues/8012)
|
|
||||||
* **types:** update jsx type definitions ([#8607](https://github.com/vuejs/core/issues/8607)) ([58e2a94](https://github.com/vuejs/core/commit/58e2a94871ae06a909c5f8bad07fb401193e6a38))
|
|
||||||
* **types:** widen ClassValue type ([2424013](https://github.com/vuejs/core/commit/242401305944422d0c361b16101a4d18908927af))
|
|
||||||
* **v-model:** avoid overwriting number input with same value ([#7004](https://github.com/vuejs/core/issues/7004)) ([40f4b77](https://github.com/vuejs/core/commit/40f4b77bb570868cb6e47791078767797e465989)), closes [#7003](https://github.com/vuejs/core/issues/7003)
|
|
||||||
* **v-model:** unnecessary value binding error should apply to dynamic instead of static binding ([2859b65](https://github.com/vuejs/core/commit/2859b653c9a22460e60233cac10fe139e359b046)), closes [#3596](https://github.com/vuejs/core/issues/3596)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.3.8](https://github.com/vuejs/core/compare/v3.3.7...v3.3.8) (2023-11-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **compile-sfc:** support `Error` type in `defineProps` ([#5955](https://github.com/vuejs/core/issues/5955)) ([a989345](https://github.com/vuejs/core/commit/a9893458ec519aae442e1b99e64e6d74685cd22c))
|
|
||||||
* **compiler-core:** known global should be shadowed by local variables in expression rewrite ([#9492](https://github.com/vuejs/core/issues/9492)) ([a75d1c5](https://github.com/vuejs/core/commit/a75d1c5c6242e91a73cc5ba01e6da620dea0b3d9)), closes [#9482](https://github.com/vuejs/core/issues/9482)
|
|
||||||
* **compiler-sfc:** fix dynamic directive arguments usage check for slots ([#9495](https://github.com/vuejs/core/issues/9495)) ([b39fa1f](https://github.com/vuejs/core/commit/b39fa1f8157647859331ce439c42ae016a49b415)), closes [#9493](https://github.com/vuejs/core/issues/9493)
|
|
||||||
* **deps:** update dependency @vue/repl to ^2.6.2 ([#9536](https://github.com/vuejs/core/issues/9536)) ([5cef325](https://github.com/vuejs/core/commit/5cef325f41e3b38657c72fa1a38dedeee1c7a60a))
|
|
||||||
* **deps:** update dependency @vue/repl to ^2.6.3 ([#9540](https://github.com/vuejs/core/issues/9540)) ([176d590](https://github.com/vuejs/core/commit/176d59058c9aecffe9da4d4311e98496684f06d4))
|
|
||||||
* **hydration:** fix tagName access error on comment/text node hydration mismatch ([dd8a0cf](https://github.com/vuejs/core/commit/dd8a0cf5dcde13d2cbd899262a0e07f16e14e489)), closes [#9531](https://github.com/vuejs/core/issues/9531)
|
|
||||||
* **types:** avoid exposing lru-cache types in generated dts ([462aeb3](https://github.com/vuejs/core/commit/462aeb3b600765e219ded2ee9a0ed1e74df61de0)), closes [#9521](https://github.com/vuejs/core/issues/9521)
|
|
||||||
* **warn:** avoid warning on empty children with Suspense ([#3962](https://github.com/vuejs/core/issues/3962)) ([405f345](https://github.com/vuejs/core/commit/405f34587a63a5f1e3d147b9848219ea98acc22d))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
[build.environment]
|
[build.environment]
|
||||||
NODE_VERSION = "18"
|
NODE_VERSION = "22"
|
||||||
NPM_FLAGS = "--version" # prevent Netlify npm install
|
NPM_FLAGS = "--version" # prevent Netlify npm install
|
||||||
|
|
70
package.json
70
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.12",
|
"version": "3.5.14",
|
||||||
"packageManager": "pnpm@9.12.2",
|
"packageManager": "pnpm@10.11.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -22,7 +22,10 @@
|
||||||
"test-dts": "run-s build-dts test-dts-only",
|
"test-dts": "run-s build-dts test-dts-only",
|
||||||
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
||||||
"test-coverage": "vitest run --project unit --coverage",
|
"test-coverage": "vitest run --project unit --coverage",
|
||||||
"test-bench": "vitest bench",
|
"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",
|
||||||
|
"bench-compare": "vitest bench --project=unit --compare=temp/bench.json",
|
||||||
"release": "node scripts/release.js",
|
"release": "node scripts/release.js",
|
||||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||||
|
@ -62,62 +65,51 @@
|
||||||
"@babel/parser": "catalog:",
|
"@babel/parser": "catalog:",
|
||||||
"@babel/types": "catalog:",
|
"@babel/types": "catalog:",
|
||||||
"@rollup/plugin-alias": "^5.1.1",
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
"@rollup/plugin-commonjs": "^28.0.1",
|
"@rollup/plugin-commonjs": "^28.0.3",
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@swc/core": "^1.7.36",
|
"@swc/core": "^1.11.24",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^20.16.13",
|
"@types/node": "^22.15.21",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.7.0",
|
||||||
"@types/serve-handler": "^6.1.4",
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitest/coverage-v8": "^2.1.1",
|
"@vitest/coverage-v8": "^3.1.4",
|
||||||
|
"@vitest/eslint-plugin": "^1.2.0",
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^5.0.0",
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.24.0",
|
"esbuild": "^0.25.4",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.13.0",
|
"eslint": "^9.27.0",
|
||||||
"eslint-plugin-import-x": "^4.3.1",
|
"eslint-plugin-import-x": "^4.12.2",
|
||||||
"@vitest/eslint-plugin": "^1.0.1",
|
|
||||||
"estree-walker": "catalog:",
|
"estree-walker": "catalog:",
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^26.1.0",
|
||||||
"lint-staged": "^15.2.10",
|
"lint-staged": "^15.5.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"magic-string": "^0.30.12",
|
"magic-string": "^0.30.17",
|
||||||
"markdown-table": "^3.0.3",
|
"markdown-table": "^3.0.4",
|
||||||
"marked": "13.0.3",
|
"marked": "13.0.3",
|
||||||
"npm-run-all2": "^6.2.6",
|
"npm-run-all2": "^7.0.2",
|
||||||
"picocolors": "^1.1.1",
|
"picocolors": "^1.1.1",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.5.3",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~23.3.0",
|
"puppeteer": "~24.9.0",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.24.0",
|
"rollup": "^4.41.0",
|
||||||
"rollup-plugin-dts": "^6.1.1",
|
"rollup-plugin-dts": "^6.2.1",
|
||||||
"rollup-plugin-esbuild": "^6.1.1",
|
"rollup-plugin-esbuild": "^6.2.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
"semver": "^7.6.3",
|
"semver": "^7.7.2",
|
||||||
"serve": "^14.2.4",
|
"serve": "^14.2.4",
|
||||||
"serve-handler": "^6.1.6",
|
"serve-handler": "^6.1.6",
|
||||||
"simple-git-hooks": "^2.11.1",
|
"simple-git-hooks": "^2.13.0",
|
||||||
"todomvc-app-css": "^2.4.3",
|
"todomvc-app-css": "^2.4.3",
|
||||||
"tslib": "^2.8.0",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.10.0",
|
"typescript-eslint": "^8.32.1",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"vitest": "^2.1.1"
|
"vitest": "^3.1.4"
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"peerDependencyRules": {
|
|
||||||
"allowedVersions": {
|
|
||||||
"typescript-eslint>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/eslint-plugin>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/parser>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/type-utils>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/utils>eslint": "^9.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ app.directive<HTMLElement, string, 'prevent' | 'stop', 'arg1' | 'arg2'>(
|
||||||
mounted(el, binding) {
|
mounted(el, binding) {
|
||||||
expectType<HTMLElement>(el)
|
expectType<HTMLElement>(el)
|
||||||
expectType<string>(binding.value)
|
expectType<string>(binding.value)
|
||||||
expectType<{ prevent: boolean; stop: boolean }>(binding.modifiers)
|
expectType<{ prevent?: boolean; stop?: boolean }>(binding.modifiers)
|
||||||
expectType<'arg1' | 'arg2'>(binding.arg!)
|
expectType<'arg1' | 'arg2'>(binding.arg!)
|
||||||
|
|
||||||
// @ts-expect-error not any
|
// @ts-expect-error not any
|
||||||
|
|
|
@ -12,8 +12,11 @@ app.use(PluginWithoutType, 2)
|
||||||
app.use(PluginWithoutType, { anything: 'goes' }, true)
|
app.use(PluginWithoutType, { anything: 'goes' }, true)
|
||||||
|
|
||||||
type PluginOptions = {
|
type PluginOptions = {
|
||||||
|
/** option1 */
|
||||||
option1?: string
|
option1?: string
|
||||||
|
/** option2 */
|
||||||
option2: number
|
option2: number
|
||||||
|
/** option3 */
|
||||||
option3: boolean
|
option3: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +28,20 @@ const PluginWithObjectOptions = {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const objectPluginOptional = {
|
||||||
|
install(app: App, options?: PluginOptions) {},
|
||||||
|
}
|
||||||
|
app.use(objectPluginOptional)
|
||||||
|
app.use(
|
||||||
|
objectPluginOptional,
|
||||||
|
// Test JSDoc and `go to definition` for options
|
||||||
|
{
|
||||||
|
option1: 'foo',
|
||||||
|
option2: 1,
|
||||||
|
option3: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
for (const Plugin of [
|
for (const Plugin of [
|
||||||
PluginWithObjectOptions,
|
PluginWithObjectOptions,
|
||||||
PluginWithObjectOptions.install,
|
PluginWithObjectOptions.install,
|
||||||
|
@ -92,7 +109,27 @@ const PluginTyped: Plugin<PluginOptions> = (app, options) => {}
|
||||||
|
|
||||||
// @ts-expect-error: needs options
|
// @ts-expect-error: needs options
|
||||||
app.use(PluginTyped)
|
app.use(PluginTyped)
|
||||||
app.use(PluginTyped, { option2: 2, option3: true })
|
app.use(
|
||||||
|
PluginTyped,
|
||||||
|
// Test autocomplete for options
|
||||||
|
{
|
||||||
|
option1: '',
|
||||||
|
option2: 2,
|
||||||
|
option3: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
const functionPluginOptional = (app: App, options?: PluginOptions) => {}
|
||||||
|
app.use(functionPluginOptional)
|
||||||
|
app.use(functionPluginOptional, { option2: 2, option3: true })
|
||||||
|
|
||||||
|
// type optional params
|
||||||
|
const functionPluginOptional2: Plugin<[options?: PluginOptions]> = (
|
||||||
|
app,
|
||||||
|
options,
|
||||||
|
) => {}
|
||||||
|
app.use(functionPluginOptional2)
|
||||||
|
app.use(functionPluginOptional2, { option2: 2, option3: true })
|
||||||
|
|
||||||
// vuetify usage
|
// vuetify usage
|
||||||
const key: string = ''
|
const key: string = ''
|
||||||
|
|
|
@ -137,3 +137,18 @@ describe('Generic component', () => {
|
||||||
expectType<string | number>(comp.msg)
|
expectType<string | number>(comp.msg)
|
||||||
expectType<Array<string | number>>(comp.list)
|
expectType<Array<string | number>>(comp.list)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12751
|
||||||
|
{
|
||||||
|
const Comp = defineComponent({
|
||||||
|
__typeEmits: {} as {
|
||||||
|
'update:visible': [value?: boolean]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const comp: ComponentInstance<typeof Comp> = {} as any
|
||||||
|
|
||||||
|
expectType<((value?: boolean) => any) | undefined>(comp['onUpdate:visible'])
|
||||||
|
expectType<{ 'onUpdate:visible'?: (value?: boolean) => any }>(comp['$props'])
|
||||||
|
// @ts-expect-error
|
||||||
|
comp['$props']['$props']
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ import { type IsAny, type IsUnion, describe, expectType } from './utils'
|
||||||
describe('with object props', () => {
|
describe('with object props', () => {
|
||||||
interface ExpectedProps {
|
interface ExpectedProps {
|
||||||
a?: number | undefined
|
a?: number | undefined
|
||||||
|
aa: number
|
||||||
|
aaa: number | null
|
||||||
|
aaaa: number | undefined
|
||||||
b: string
|
b: string
|
||||||
e?: Function
|
e?: Function
|
||||||
h: boolean
|
h: boolean
|
||||||
|
@ -53,6 +56,19 @@ describe('with object props', () => {
|
||||||
|
|
||||||
const props = {
|
const props = {
|
||||||
a: Number,
|
a: Number,
|
||||||
|
aa: {
|
||||||
|
type: Number as PropType<number | undefined>,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
aaa: {
|
||||||
|
type: Number as PropType<number | null>,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
aaaa: {
|
||||||
|
type: Number as PropType<number | undefined>,
|
||||||
|
// `as const` prevents widening to `boolean` (keeps literal `true` type)
|
||||||
|
required: true as const,
|
||||||
|
},
|
||||||
// required should make property non-void
|
// required should make property non-void
|
||||||
b: {
|
b: {
|
||||||
type: String,
|
type: String,
|
||||||
|
@ -146,6 +162,13 @@ describe('with object props', () => {
|
||||||
setup(props) {
|
setup(props) {
|
||||||
// type assertion. See https://github.com/SamVerschueren/tsd
|
// type assertion. See https://github.com/SamVerschueren/tsd
|
||||||
expectType<ExpectedProps['a']>(props.a)
|
expectType<ExpectedProps['a']>(props.a)
|
||||||
|
expectType<ExpectedProps['aa']>(props.aa)
|
||||||
|
expectType<ExpectedProps['aaa']>(props.aaa)
|
||||||
|
|
||||||
|
// @ts-expect-error should included `undefined`
|
||||||
|
expectType<number>(props.aaaa)
|
||||||
|
expectType<ExpectedProps['aaaa']>(props.aaaa)
|
||||||
|
|
||||||
expectType<ExpectedProps['b']>(props.b)
|
expectType<ExpectedProps['b']>(props.b)
|
||||||
expectType<ExpectedProps['e']>(props.e)
|
expectType<ExpectedProps['e']>(props.e)
|
||||||
expectType<ExpectedProps['h']>(props.h)
|
expectType<ExpectedProps['h']>(props.h)
|
||||||
|
@ -198,6 +221,8 @@ describe('with object props', () => {
|
||||||
render() {
|
render() {
|
||||||
const props = this.$props
|
const props = this.$props
|
||||||
expectType<ExpectedProps['a']>(props.a)
|
expectType<ExpectedProps['a']>(props.a)
|
||||||
|
expectType<ExpectedProps['aa']>(props.aa)
|
||||||
|
expectType<ExpectedProps['aaa']>(props.aaa)
|
||||||
expectType<ExpectedProps['b']>(props.b)
|
expectType<ExpectedProps['b']>(props.b)
|
||||||
expectType<ExpectedProps['e']>(props.e)
|
expectType<ExpectedProps['e']>(props.e)
|
||||||
expectType<ExpectedProps['h']>(props.h)
|
expectType<ExpectedProps['h']>(props.h)
|
||||||
|
@ -225,6 +250,8 @@ describe('with object props', () => {
|
||||||
|
|
||||||
// should also expose declared props on `this`
|
// should also expose declared props on `this`
|
||||||
expectType<ExpectedProps['a']>(this.a)
|
expectType<ExpectedProps['a']>(this.a)
|
||||||
|
expectType<ExpectedProps['aa']>(this.aa)
|
||||||
|
expectType<ExpectedProps['aaa']>(this.aaa)
|
||||||
expectType<ExpectedProps['b']>(this.b)
|
expectType<ExpectedProps['b']>(this.b)
|
||||||
expectType<ExpectedProps['e']>(this.e)
|
expectType<ExpectedProps['e']>(this.e)
|
||||||
expectType<ExpectedProps['h']>(this.h)
|
expectType<ExpectedProps['h']>(this.h)
|
||||||
|
@ -269,6 +296,7 @@ describe('with object props', () => {
|
||||||
expectType<JSX.Element>(
|
expectType<JSX.Element>(
|
||||||
<MyComponent
|
<MyComponent
|
||||||
a={1}
|
a={1}
|
||||||
|
aaaa={1}
|
||||||
b="b"
|
b="b"
|
||||||
bb="bb"
|
bb="bb"
|
||||||
e={() => {}}
|
e={() => {}}
|
||||||
|
@ -295,6 +323,7 @@ describe('with object props', () => {
|
||||||
|
|
||||||
expectType<Component>(
|
expectType<Component>(
|
||||||
<MyComponent
|
<MyComponent
|
||||||
|
aaaa={1}
|
||||||
b="b"
|
b="b"
|
||||||
dd={{ n: 1 }}
|
dd={{ n: 1 }}
|
||||||
ddd={['ddd']}
|
ddd={['ddd']}
|
||||||
|
|
|
@ -29,7 +29,7 @@ describe('custom', () => {
|
||||||
value: number
|
value: number
|
||||||
oldValue: number | null
|
oldValue: number | null
|
||||||
arg?: 'Arg'
|
arg?: 'Arg'
|
||||||
modifiers: Record<'a' | 'b', boolean>
|
modifiers: Partial<Record<'a' | 'b', boolean>>
|
||||||
}>(testDirective<number, 'a' | 'b', 'Arg'>())
|
}>(testDirective<number, 'a' | 'b', 'Arg'>())
|
||||||
|
|
||||||
expectType<{
|
expectType<{
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {
|
||||||
type MaybeRefOrGetter,
|
type MaybeRefOrGetter,
|
||||||
type Ref,
|
type Ref,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
|
type TemplateRef,
|
||||||
type ToRefs,
|
type ToRefs,
|
||||||
type WritableComputedRef,
|
type WritableComputedRef,
|
||||||
computed,
|
computed,
|
||||||
|
@ -535,7 +536,7 @@ expectType<string>(toValue(unref2))
|
||||||
|
|
||||||
// useTemplateRef
|
// useTemplateRef
|
||||||
const tRef = useTemplateRef('foo')
|
const tRef = useTemplateRef('foo')
|
||||||
expectType<Readonly<ShallowRef<unknown>>>(tRef)
|
expectType<TemplateRef>(tRef)
|
||||||
|
|
||||||
const tRef2 = useTemplateRef<HTMLElement>('bar')
|
const tRef2 = useTemplateRef<HTMLElement>('bar')
|
||||||
expectType<Readonly<ShallowRef<HTMLElement | null>>>(tRef2)
|
expectType<TemplateRef<HTMLElement>>(tRef2)
|
||||||
|
|
|
@ -306,6 +306,14 @@ describe('defineEmits w/ type declaration', () => {
|
||||||
emit2('baz')
|
emit2('baz')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('defineEmits w/ interface declaration', () => {
|
||||||
|
interface Emits {
|
||||||
|
foo: [value: string]
|
||||||
|
}
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
emit('foo', 'hi')
|
||||||
|
})
|
||||||
|
|
||||||
describe('defineEmits w/ alt type declaration', () => {
|
describe('defineEmits w/ alt type declaration', () => {
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
foo: [id: string]
|
foo: [id: string]
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
"vite": "catalog:"
|
"vite": "catalog:"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vue/repl": "^4.4.2",
|
"@vue/repl": "^4.5.1",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"jszip": "^3.10.1",
|
"jszip": "^3.10.1",
|
||||||
"vue": "workspace:*"
|
"vue": "workspace:*"
|
||||||
|
|
|
@ -165,8 +165,9 @@ onMounted(() => {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
font-family:
|
||||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
--base: #444;
|
--base: #444;
|
||||||
--nav-height: 50px;
|
--nav-height: 50px;
|
||||||
|
|
|
@ -46,6 +46,7 @@ function resetVueVersion() {
|
||||||
|
|
||||||
async function copyLink(e: MouseEvent) {
|
async function copyLink(e: MouseEvent) {
|
||||||
if (e.metaKey) {
|
if (e.metaKey) {
|
||||||
|
resetVueVersion()
|
||||||
// hidden logic for going to local debug from play.vuejs.org
|
// hidden logic for going to local debug from play.vuejs.org
|
||||||
window.location.href = 'http://localhost:5173/' + window.location.hash
|
window.location.href = 'http://localhost:5173/' + window.location.hash
|
||||||
return
|
return
|
||||||
|
|
|
@ -17,7 +17,10 @@ export async function downloadProject(store: ReplStore) {
|
||||||
|
|
||||||
// basic structure
|
// basic structure
|
||||||
zip.file('index.html', index)
|
zip.file('index.html', index)
|
||||||
zip.file('package.json', pkg)
|
zip.file(
|
||||||
|
'package.json',
|
||||||
|
pkg.replace(`"vue": "latest"`, `"vue": "${store.vueVersion || 'latest'}"`),
|
||||||
|
)
|
||||||
zip.file('vite.config.js', config)
|
zip.file('vite.config.js', config)
|
||||||
zip.file('README.md', readme)
|
zip.file('README.md', readme)
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
"serve": "vite preview"
|
"serve": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.4.0"
|
"vue": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.1.4",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"vite": "^5.4.9"
|
"vite": "^6.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"enableNonBrowserBranches": true
|
"enableNonBrowserBranches": true
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"monaco-editor": "^0.52.0",
|
"monaco-editor": "^0.52.2",
|
||||||
"source-map-js": "^1.2.1"
|
"source-map-js": "^1.2.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
font-family:
|
||||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu,
|
||||||
|
Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
--bg: #1d1f21;
|
--bg: #1d1f21;
|
||||||
--border: #333;
|
--border: #333;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,23 @@
|
||||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`compiler: v-memo transform > element v-for key expression prefixing + v-memo 1`] = `
|
||||||
|
"import { renderList as _renderList, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock, isMemoSame as _isMemoSame, withMemo as _withMemo } from "vue"
|
||||||
|
|
||||||
|
export function render(_ctx, _cache) {
|
||||||
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
|
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(_ctx.tableData, (data, __, ___, _cached) => {
|
||||||
|
const _memo = (_ctx.getLetter(data))
|
||||||
|
if (_cached && _cached.key === _ctx.getId(data) && _isMemoSame(_cached, _memo)) return _cached
|
||||||
|
const _item = (_openBlock(), _createElementBlock("span", {
|
||||||
|
key: _ctx.getId(data)
|
||||||
|
}))
|
||||||
|
_item.memo = _memo
|
||||||
|
return _item
|
||||||
|
}, _cache, 0), 128 /* KEYED_FRAGMENT */))
|
||||||
|
]))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: v-memo transform > on component 1`] = `
|
exports[`compiler: v-memo transform > on component 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
"import { resolveComponent as _resolveComponent, createVNode as _createVNode, withMemo as _withMemo, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ return function render(_ctx, _cache) {
|
||||||
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
const { setBlockTracking: _setBlockTracking, createElementVNode: _createElementVNode } = _Vue
|
||||||
|
|
||||||
return _cache[0] || (
|
return _cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -28,7 +28,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -47,7 +47,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -66,7 +66,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
|
||||||
|
|
||||||
return (_openBlock(), _createElementBlock("div", null, [
|
return (_openBlock(), _createElementBlock("div", null, [
|
||||||
_cache[0] || (
|
_cache[0] || (
|
||||||
_setBlockTracking(-1),
|
_setBlockTracking(-1, true),
|
||||||
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
||||||
_setBlockTracking(1),
|
_setBlockTracking(1),
|
||||||
_cache[0]
|
_cache[0]
|
||||||
|
|
|
@ -170,6 +170,11 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_PROPERTY,
|
||||||
|
key: { content: '__' },
|
||||||
|
value: { content: '[0]' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -197,6 +202,11 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_PROPERTY,
|
||||||
|
key: { content: '__' },
|
||||||
|
value: { content: '[0]' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -53,4 +53,12 @@ describe('compiler: v-memo transform', () => {
|
||||||
),
|
),
|
||||||
).toMatchSnapshot()
|
).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('element v-for key expression prefixing + v-memo', () => {
|
||||||
|
expect(
|
||||||
|
compile(
|
||||||
|
`<span v-for="data of tableData" :key="getId(data)" v-memo="getLetter(data)"></span>`,
|
||||||
|
),
|
||||||
|
).toMatchSnapshot()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.5.12",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
|
|
@ -418,6 +418,7 @@ export interface CacheExpression extends Node {
|
||||||
index: number
|
index: number
|
||||||
value: JSChildNode
|
value: JSChildNode
|
||||||
needPauseTracking: boolean
|
needPauseTracking: boolean
|
||||||
|
inVOnce: boolean
|
||||||
needArraySpread: boolean
|
needArraySpread: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -774,12 +775,14 @@ export function createCacheExpression(
|
||||||
index: number,
|
index: number,
|
||||||
value: JSChildNode,
|
value: JSChildNode,
|
||||||
needPauseTracking: boolean = false,
|
needPauseTracking: boolean = false,
|
||||||
|
inVOnce: boolean = false,
|
||||||
): CacheExpression {
|
): CacheExpression {
|
||||||
return {
|
return {
|
||||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||||
index,
|
index,
|
||||||
value,
|
value,
|
||||||
needPauseTracking: needPauseTracking,
|
needPauseTracking: needPauseTracking,
|
||||||
|
inVOnce,
|
||||||
needArraySpread: false,
|
needArraySpread: false,
|
||||||
loc: locStub,
|
loc: locStub,
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,7 +188,9 @@ function createCodegenContext(
|
||||||
name = content
|
name = content
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addMapping(node.loc.start, name)
|
if (node.loc.source) {
|
||||||
|
addMapping(node.loc.start, name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (newlineIndex === NewlineType.Unknown) {
|
if (newlineIndex === NewlineType.Unknown) {
|
||||||
// multiple newlines, full iteration
|
// multiple newlines, full iteration
|
||||||
|
@ -225,7 +227,7 @@ function createCodegenContext(
|
||||||
context.column = code.length - newlineIndex
|
context.column = code.length - newlineIndex
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (node && node.loc !== locStub) {
|
if (node && node.loc !== locStub && node.loc.source) {
|
||||||
addMapping(node.loc.end)
|
addMapping(node.loc.end)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1017,7 +1019,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||||
push(`_cache[${node.index}] || (`)
|
push(`_cache[${node.index}] || (`)
|
||||||
if (needPauseTracking) {
|
if (needPauseTracking) {
|
||||||
indent()
|
indent()
|
||||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
push(`${helper(SET_BLOCK_TRACKING)}(-1`)
|
||||||
|
if (node.inVOnce) push(`, true`)
|
||||||
|
push(`),`)
|
||||||
newline()
|
newline()
|
||||||
push(`(`)
|
push(`(`)
|
||||||
}
|
}
|
||||||
|
|
|
@ -388,7 +388,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
||||||
currentOptions,
|
currentOptions,
|
||||||
currentProp.loc,
|
currentProp.loc,
|
||||||
currentProp.rawName,
|
currentProp.arg!.loc.source,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
currentProp.name = 'model'
|
currentProp.name = 'model'
|
||||||
|
|
|
@ -116,7 +116,7 @@ export interface TransformContext
|
||||||
addIdentifiers(exp: ExpressionNode | string): void
|
addIdentifiers(exp: ExpressionNode | string): void
|
||||||
removeIdentifiers(exp: ExpressionNode | string): void
|
removeIdentifiers(exp: ExpressionNode | string): void
|
||||||
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
||||||
cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
|
cache(exp: JSChildNode, isVNode?: boolean, inVOnce?: boolean): CacheExpression
|
||||||
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
||||||
|
|
||||||
// 2.x Compat only
|
// 2.x Compat only
|
||||||
|
@ -297,11 +297,12 @@ export function createTransformContext(
|
||||||
identifier.hoisted = exp
|
identifier.hoisted = exp
|
||||||
return identifier
|
return identifier
|
||||||
},
|
},
|
||||||
cache(exp, isVNode = false) {
|
cache(exp, isVNode = false, inVOnce = false) {
|
||||||
const cacheExp = createCacheExpression(
|
const cacheExp = createCacheExpression(
|
||||||
context.cached.length,
|
context.cached.length,
|
||||||
exp,
|
exp,
|
||||||
isVNode,
|
isVNode,
|
||||||
|
inVOnce,
|
||||||
)
|
)
|
||||||
context.cached.push(cacheExp)
|
context.cached.push(cacheExp)
|
||||||
return cacheExp
|
return cacheExp
|
||||||
|
|
|
@ -12,11 +12,14 @@ import {
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
type SlotFunctionExpression,
|
type SlotFunctionExpression,
|
||||||
|
type SlotsObjectProperty,
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
type TemplateNode,
|
type TemplateNode,
|
||||||
type TextCallNode,
|
type TextCallNode,
|
||||||
type VNodeCall,
|
type VNodeCall,
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
|
createObjectProperty,
|
||||||
|
createSimpleExpression,
|
||||||
getVNodeBlockHelper,
|
getVNodeBlockHelper,
|
||||||
getVNodeHelper,
|
getVNodeHelper,
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
|
@ -140,6 +143,7 @@ function walk(
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedAsArray = false
|
let cachedAsArray = false
|
||||||
|
const slotCacheKeys = []
|
||||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||||
if (
|
if (
|
||||||
node.tagType === ElementTypes.ELEMENT &&
|
node.tagType === ElementTypes.ELEMENT &&
|
||||||
|
@ -163,6 +167,7 @@ function walk(
|
||||||
// default slot
|
// default slot
|
||||||
const slot = getSlotNode(node.codegenNode, 'default')
|
const slot = getSlotNode(node.codegenNode, 'default')
|
||||||
if (slot) {
|
if (slot) {
|
||||||
|
slotCacheKeys.push(context.cached.length)
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -186,6 +191,7 @@ function walk(
|
||||||
slotName.arg &&
|
slotName.arg &&
|
||||||
getSlotNode(parent.codegenNode, slotName.arg)
|
getSlotNode(parent.codegenNode, slotName.arg)
|
||||||
if (slot) {
|
if (slot) {
|
||||||
|
slotCacheKeys.push(context.cached.length)
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -196,10 +202,31 @@ function walk(
|
||||||
|
|
||||||
if (!cachedAsArray) {
|
if (!cachedAsArray) {
|
||||||
for (const child of toCache) {
|
for (const child of toCache) {
|
||||||
|
slotCacheKeys.push(context.cached.length)
|
||||||
child.codegenNode = context.cache(child.codegenNode!)
|
child.codegenNode = context.cache(child.codegenNode!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// put the slot cached keys on the slot object, so that the cache
|
||||||
|
// can be removed when component unmounting to prevent memory leaks
|
||||||
|
if (
|
||||||
|
slotCacheKeys.length &&
|
||||||
|
node.type === NodeTypes.ELEMENT &&
|
||||||
|
node.tagType === ElementTypes.COMPONENT &&
|
||||||
|
node.codegenNode &&
|
||||||
|
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||||
|
node.codegenNode.children &&
|
||||||
|
!isArray(node.codegenNode.children) &&
|
||||||
|
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||||
|
) {
|
||||||
|
node.codegenNode.children.properties.push(
|
||||||
|
createObjectProperty(
|
||||||
|
`__`,
|
||||||
|
createSimpleExpression(JSON.stringify(slotCacheKeys), false),
|
||||||
|
) as SlotsObjectProperty,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function getCacheExpression(value: JSChildNode): CacheExpression {
|
function getCacheExpression(value: JSChildNode): CacheExpression {
|
||||||
const exp = context.cache(value)
|
const exp = context.cache(value)
|
||||||
// #6978, #7138, #7114
|
// #6978, #7138, #7114
|
||||||
|
|
|
@ -594,11 +594,9 @@ export function buildProps(
|
||||||
hasDynamicKeys = true
|
hasDynamicKeys = true
|
||||||
if (exp) {
|
if (exp) {
|
||||||
if (isVBind) {
|
if (isVBind) {
|
||||||
// #10696 in case a v-bind object contains ref
|
|
||||||
pushRefVForMarker()
|
|
||||||
// have to merge early for compat build check
|
|
||||||
pushMergeArg()
|
|
||||||
if (__COMPAT__) {
|
if (__COMPAT__) {
|
||||||
|
// have to merge early for compat build check
|
||||||
|
pushMergeArg()
|
||||||
// 2.x v-bind object order compat
|
// 2.x v-bind object order compat
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
const hasOverridableKeys = mergeArgs.some(arg => {
|
const hasOverridableKeys = mergeArgs.some(arg => {
|
||||||
|
@ -641,6 +639,9 @@ export function buildProps(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// #10696 in case a v-bind object contains ref
|
||||||
|
pushRefVForMarker()
|
||||||
|
pushMergeArg()
|
||||||
mergeArgs.push(exp)
|
mergeArgs.push(exp)
|
||||||
} else {
|
} else {
|
||||||
// v-on="obj" -> toHandlers(obj)
|
// v-on="obj" -> toHandlers(obj)
|
||||||
|
|
|
@ -24,7 +24,7 @@ import {
|
||||||
isStaticPropertyKey,
|
isStaticPropertyKey,
|
||||||
walkIdentifiers,
|
walkIdentifiers,
|
||||||
} from '../babelUtils'
|
} from '../babelUtils'
|
||||||
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
|
import { advancePositionWithClone, findDir, isSimpleIdentifier } from '../utils'
|
||||||
import {
|
import {
|
||||||
genPropsAccessExp,
|
genPropsAccessExp,
|
||||||
hasOwn,
|
hasOwn,
|
||||||
|
@ -54,6 +54,7 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||||
)
|
)
|
||||||
} else if (node.type === NodeTypes.ELEMENT) {
|
} else if (node.type === NodeTypes.ELEMENT) {
|
||||||
// handle directives on element
|
// handle directives on element
|
||||||
|
const memo = findDir(node, 'memo')
|
||||||
for (let i = 0; i < node.props.length; i++) {
|
for (let i = 0; i < node.props.length; i++) {
|
||||||
const dir = node.props[i]
|
const dir = node.props[i]
|
||||||
// do not process for v-on & v-for since they are special handled
|
// do not process for v-on & v-for since they are special handled
|
||||||
|
@ -65,7 +66,14 @@ export const transformExpression: NodeTransform = (node, context) => {
|
||||||
if (
|
if (
|
||||||
exp &&
|
exp &&
|
||||||
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
exp.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
!(dir.name === 'on' && arg)
|
!(dir.name === 'on' && arg) &&
|
||||||
|
// key has been processed in transformFor(vMemo + vFor)
|
||||||
|
!(
|
||||||
|
memo &&
|
||||||
|
arg &&
|
||||||
|
arg.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||||
|
arg.content === 'key'
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
dir.exp = processExpression(
|
dir.exp = processExpression(
|
||||||
exp,
|
exp,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { camelize } from '@vue/shared'
|
||||||
import { CAMELIZE } from '../runtimeHelpers'
|
import { CAMELIZE } from '../runtimeHelpers'
|
||||||
import { processExpression } from './transformExpression'
|
import { processExpression } from './transformExpression'
|
||||||
|
|
||||||
// v-bind without arg is handled directly in ./transformElements.ts due to it affecting
|
// 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
|
// codegen for the entire props object. This transform here is only for v-bind
|
||||||
// *with* args.
|
// *with* args.
|
||||||
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
||||||
|
|
|
@ -63,17 +63,27 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
|
||||||
const isTemplate = isTemplateNode(node)
|
const isTemplate = isTemplateNode(node)
|
||||||
const memo = findDir(node, 'memo')
|
const memo = findDir(node, 'memo')
|
||||||
const keyProp = findProp(node, `key`, false, true)
|
const keyProp = findProp(node, `key`, false, true)
|
||||||
if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
|
const isDirKey = keyProp && keyProp.type === NodeTypes.DIRECTIVE
|
||||||
|
if (isDirKey && !keyProp.exp) {
|
||||||
// resolve :key shorthand #10882
|
// resolve :key shorthand #10882
|
||||||
transformBindShorthand(keyProp, context)
|
transformBindShorthand(keyProp, context)
|
||||||
}
|
}
|
||||||
const keyExp =
|
let keyExp =
|
||||||
keyProp &&
|
keyProp &&
|
||||||
(keyProp.type === NodeTypes.ATTRIBUTE
|
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||||
? keyProp.value
|
? keyProp.value
|
||||||
? createSimpleExpression(keyProp.value.content, true)
|
? createSimpleExpression(keyProp.value.content, true)
|
||||||
: undefined
|
: undefined
|
||||||
: keyProp.exp)
|
: keyProp.exp)
|
||||||
|
|
||||||
|
if (memo && keyExp && isDirKey) {
|
||||||
|
if (!__BROWSER__) {
|
||||||
|
keyProp.exp = keyExp = processExpression(
|
||||||
|
keyExp as SimpleExpressionNode,
|
||||||
|
context,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
const keyProperty =
|
const keyProperty =
|
||||||
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
||||||
|
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { hasScopeRef, isFnExpression, isMemberExpression } from '../utils'
|
||||||
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
import { TO_HANDLER_KEY } from '../runtimeHelpers'
|
||||||
|
|
||||||
export interface VOnDirectiveNode extends DirectiveNode {
|
export interface VOnDirectiveNode extends DirectiveNode {
|
||||||
// v-on without arg is handled directly in ./transformElements.ts due to it affecting
|
// v-on 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-on
|
// codegen for the entire props object. This transform here is only for v-on
|
||||||
// *with* args.
|
// *with* args.
|
||||||
arg: ExpressionNode
|
arg: ExpressionNode
|
||||||
|
|
|
@ -17,7 +17,11 @@ export const transformOnce: NodeTransform = (node, context) => {
|
||||||
context.inVOnce = false
|
context.inVOnce = false
|
||||||
const cur = context.currentNode as ElementNode | IfNode | ForNode
|
const cur = context.currentNode as ElementNode | IfNode | ForNode
|
||||||
if (cur.codegenNode) {
|
if (cur.codegenNode) {
|
||||||
cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */)
|
cur.codegenNode = context.cache(
|
||||||
|
cur.codegenNode,
|
||||||
|
true /* isVNode */,
|
||||||
|
true /* inVOnce */,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -342,7 +342,6 @@ export function buildSlots(
|
||||||
: hasForwardedSlots(node.children)
|
: hasForwardedSlots(node.children)
|
||||||
? SlotFlags.FORWARDED
|
? SlotFlags.FORWARDED
|
||||||
: SlotFlags.STABLE
|
: SlotFlags.STABLE
|
||||||
|
|
||||||
let slots = createObjectExpression(
|
let slots = createObjectExpression(
|
||||||
slotsProperties.concat(
|
slotsProperties.concat(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
|
|
|
@ -32,6 +32,16 @@ return function render(_ctx, _cache) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
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] = [
|
||||||
|
_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)
|
||||||
|
])))
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`stringify static html > should bail for <option> elements with null values 1`] = `
|
exports[`stringify static html > should bail for <option> elements with null values 1`] = `
|
||||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||||
|
|
||||||
|
|
|
@ -162,6 +162,27 @@ describe('stringify static html', () => {
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12391
|
||||||
|
test('serializing template string style', () => {
|
||||||
|
const { ast, code } = compileWithStringify(
|
||||||
|
`<div><div :style="\`color:red;\`">${repeat(
|
||||||
|
`<span :class="[{ foo: true }, { bar: true }]">{{ 1 }} + {{ false }}</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</div></div>`,
|
||||||
|
)
|
||||||
|
// should be optimized now
|
||||||
|
expect(ast.cached).toMatchObject([
|
||||||
|
cachedArrayStaticNodeMatcher(
|
||||||
|
`<div style="color:red;">${repeat(
|
||||||
|
`<span class="foo bar">1 + false</span>`,
|
||||||
|
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||||
|
)}</div>`,
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('escape', () => {
|
test('escape', () => {
|
||||||
const { ast, code } = compileWithStringify(
|
const { ast, code } = compileWithStringify(
|
||||||
`<div><div>${repeat(
|
`<div><div>${repeat(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { type CompilerError, compile } from '../../src'
|
import { type CompilerError, compile } from '../../src'
|
||||||
|
import { isValidHTMLNesting } from '../../src/htmlNesting'
|
||||||
|
|
||||||
describe('validate html nesting', () => {
|
describe('validate html nesting', () => {
|
||||||
it('should warn with p > div', () => {
|
it('should warn with p > div', () => {
|
||||||
|
@ -17,4 +18,185 @@ describe('validate html nesting', () => {
|
||||||
})
|
})
|
||||||
expect(err).toBeUndefined()
|
expect(err).toBeUndefined()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13318
|
||||||
|
it('should not warn when parent tag is template', () => {
|
||||||
|
let err: CompilerError | undefined
|
||||||
|
compile(`<template><tr/></template>`, {
|
||||||
|
onWarn: e => (err = e),
|
||||||
|
})
|
||||||
|
expect(err).toBeUndefined()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copied from https://github.com/MananTank/validate-html-nesting
|
||||||
|
* with ISC license
|
||||||
|
*/
|
||||||
|
describe('isValidHTMLNesting', () => {
|
||||||
|
test('form', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('form', 'form')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('form', 'div')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('form', 'input')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('form', 'select')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('form', 'button')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('form', 'label')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('form', 'h1')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('p', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('p', 'p')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('p', 'div')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('p', 'hr')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('p', 'blockquote')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('p', 'pre')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('p', 'a')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('p', 'span')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('p', 'abbr')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('p', 'button')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('p', 'b')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('p', 'i')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('p', 'input')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('p', 'label')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('a', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('a', 'a')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('a', 'div')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('a', 'span')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('button', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('button', 'button')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('button', 'div')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('button', 'span')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('table', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('table', 'tr')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('table', 'table')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('table', 'td')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('table', 'thead')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('table', 'tbody')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('table', 'tfoot')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('table', 'caption')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('table', 'colgroup')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('td', () => {
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('td', 'span')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('tr', 'td')).toBe(true)
|
||||||
|
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('td', 'td')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('div', 'td')).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('tbody', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('tbody', 'td')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('tr', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('tr', 'tr')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('table', 'tr')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('tbody', 'tr')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('thead', 'tr')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('tfoot', 'tr')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('tr', 'td')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('tr', 'th')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('li', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('li', 'li')).toBe(false)
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('li', 'div')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('li', 'ul')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('headings', () => {
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('h1', 'h1')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('h2', 'h1')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('h3', 'h1')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('h1', 'h6')).toBe(false)
|
||||||
|
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('h1', 'div')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('SVG', () => {
|
||||||
|
test('svg', () => {
|
||||||
|
// invalid non-svg tags as children
|
||||||
|
expect(isValidHTMLNesting('svg', 'div')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('svg', 'img')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('svg', 'p')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('svg', 'h2')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('svg', 'span')).toBe(false)
|
||||||
|
|
||||||
|
// valid non-svg tags as children
|
||||||
|
expect(isValidHTMLNesting('svg', 'a')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('svg', 'textarea')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('svg', 'input')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('svg', 'select')).toBe(true)
|
||||||
|
|
||||||
|
// valid svg tags as children
|
||||||
|
expect(isValidHTMLNesting('svg', 'g')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('svg', 'ellipse')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('svg', 'feOffset')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('foreignObject', () => {
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('foreignObject', 'g')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('foreignObject', 'div')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('foreignObject', 'a')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('foreignObject', 'textarea')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('g', () => {
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('g', 'div')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('g', 'p')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('g', 'a')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('g', 'textarea')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('g', 'g')).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('dl', () => {
|
||||||
|
// valid
|
||||||
|
expect(isValidHTMLNesting('dl', 'dt')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('dl', 'dd')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('dl', 'div')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('div', 'dt')).toBe(true)
|
||||||
|
expect(isValidHTMLNesting('div', 'dd')).toBe(true)
|
||||||
|
|
||||||
|
// invalid
|
||||||
|
expect(isValidHTMLNesting('span', 'dt')).toBe(false)
|
||||||
|
expect(isValidHTMLNesting('span', 'dd')).toBe(false)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.5.12",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
@ -11,6 +11,11 @@
|
||||||
* returns true if given parent-child nesting is valid HTML
|
* returns true if given parent-child nesting is valid HTML
|
||||||
*/
|
*/
|
||||||
export function isValidHTMLNesting(parent: string, child: string): boolean {
|
export function isValidHTMLNesting(parent: string, child: string): boolean {
|
||||||
|
// if the parent is a template, it can have any child
|
||||||
|
if (parent === 'template') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// if we know the list of children that are the only valid children for the given parent
|
// if we know the list of children that are the only valid children for the given parent
|
||||||
if (parent in onlyValidChildren) {
|
if (parent in onlyValidChildren) {
|
||||||
return onlyValidChildren[parent].has(child)
|
return onlyValidChildren[parent].has(child)
|
||||||
|
|
|
@ -1,5 +1,11 @@
|
||||||
import { BindingTypes } from '@vue/compiler-core'
|
import { BindingTypes } from '@vue/compiler-core'
|
||||||
import { assertCode, compileSFCScript as compile, mockId } from './utils'
|
import {
|
||||||
|
assertCode,
|
||||||
|
compileSFCScript as compile,
|
||||||
|
getPositionInCode,
|
||||||
|
mockId,
|
||||||
|
} from './utils'
|
||||||
|
import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
|
||||||
|
|
||||||
describe('SFC compile <script setup>', () => {
|
describe('SFC compile <script setup>', () => {
|
||||||
test('should compile JS syntax', () => {
|
test('should compile JS syntax', () => {
|
||||||
|
@ -690,6 +696,27 @@ describe('SFC compile <script setup>', () => {
|
||||||
expect(content).toMatch(`new (_unref(Foo)).Bar()`)
|
expect(content).toMatch(`new (_unref(Foo)).Bar()`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12682
|
||||||
|
test('source map', () => {
|
||||||
|
const source = `
|
||||||
|
<script setup>
|
||||||
|
const count = ref(0)
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<button @click="throw new Error(\`msg\`);"></button>
|
||||||
|
</template>
|
||||||
|
`
|
||||||
|
const { content, map } = compile(source, { inlineTemplate: true })
|
||||||
|
expect(map).not.toBeUndefined()
|
||||||
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
expect(
|
||||||
|
consumer.originalPositionFor(getPositionInCode(content, 'count')),
|
||||||
|
).toMatchObject(getPositionInCode(source, `count`))
|
||||||
|
expect(
|
||||||
|
consumer.originalPositionFor(getPositionInCode(content, 'Error')),
|
||||||
|
).toMatchObject(getPositionInCode(source, `Error`))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with TypeScript', () => {
|
describe('with TypeScript', () => {
|
||||||
|
@ -980,7 +1007,7 @@ describe('SFC compile <script setup>', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
let bar = 1
|
let bar = 1
|
||||||
defineModel({
|
const model = defineModel({
|
||||||
default: () => bar
|
default: () => bar
|
||||||
})
|
})
|
||||||
</script>`),
|
</script>`),
|
||||||
|
@ -990,7 +1017,7 @@ describe('SFC compile <script setup>', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
const bar = 1
|
const bar = 1
|
||||||
defineModel({
|
const model = defineModel({
|
||||||
default: () => bar
|
default: () => bar
|
||||||
})
|
})
|
||||||
</script>`),
|
</script>`),
|
||||||
|
@ -1000,7 +1027,7 @@ describe('SFC compile <script setup>', () => {
|
||||||
expect(() =>
|
expect(() =>
|
||||||
compile(`<script setup>
|
compile(`<script setup>
|
||||||
let bar = 1
|
let bar = 1
|
||||||
defineModel({
|
const model = defineModel({
|
||||||
get: () => bar,
|
get: () => bar,
|
||||||
set: () => bar
|
set: () => bar
|
||||||
})
|
})
|
||||||
|
|
|
@ -148,6 +148,27 @@ export default /*@__PURE__*/_defineComponent({
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
return { }
|
||||||
|
}
|
||||||
|
|
||||||
|
})"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`defineProps > w/ TSTypeAliasDeclaration 1`] = `
|
||||||
|
"import { defineComponent as _defineComponent } from 'vue'
|
||||||
|
type FunFoo<O> = (item: O) => boolean;
|
||||||
|
type FunBar = FunFoo<number>;
|
||||||
|
|
||||||
|
export default /*@__PURE__*/_defineComponent({
|
||||||
|
props: {
|
||||||
|
foo: { type: Function, required: false, default: () => true },
|
||||||
|
bar: { type: Function, required: false, default: () => true }
|
||||||
|
},
|
||||||
|
setup(__props: any, { expose: __expose }) {
|
||||||
|
__expose();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return { }
|
return { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,6 +192,25 @@ return () => {}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`sfc reactive props destructure > handle function parameters with same name as destructured props 1`] = `
|
||||||
|
"
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
|
||||||
|
function test(value) {
|
||||||
|
try {
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(__props.value)
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
||||||
"
|
"
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -269,4 +269,16 @@ describe('defineModel()', () => {
|
||||||
modelValue: BindingTypes.SETUP_REF,
|
modelValue: BindingTypes.SETUP_REF,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('error when defineModel is not assigned to a variable', () => {
|
||||||
|
expect(() =>
|
||||||
|
compile(`
|
||||||
|
<script setup>
|
||||||
|
defineModel()
|
||||||
|
</script>
|
||||||
|
`),
|
||||||
|
).toThrow(
|
||||||
|
'defineModel() must be assigned to a variable. For example: const model = defineModel()',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -808,4 +808,30 @@ const props = defineProps({ foo: String })
|
||||||
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
|
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
|
||||||
assertCode(content)
|
assertCode(content)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('w/ TSTypeAliasDeclaration', () => {
|
||||||
|
const { content } = compile(`
|
||||||
|
<script setup lang="ts">
|
||||||
|
type FunFoo<O> = (item: O) => boolean;
|
||||||
|
type FunBar = FunFoo<number>;
|
||||||
|
withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
foo?: FunFoo<number>;
|
||||||
|
bar?: FunBar;
|
||||||
|
}>(),
|
||||||
|
{
|
||||||
|
foo: () => true,
|
||||||
|
bar: () => true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`foo: { type: Function, required: false, default: () => true }`,
|
||||||
|
)
|
||||||
|
expect(content).toMatch(
|
||||||
|
`bar: { type: Function, required: false, default: () => true }`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -358,6 +358,22 @@ describe('sfc reactive props destructure', () => {
|
||||||
expect(content).toMatch(`props: ['item'],`)
|
expect(content).toMatch(`props: ['item'],`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('handle function parameters with same name as destructured props', () => {
|
||||||
|
const { content } = compile(`
|
||||||
|
<script setup>
|
||||||
|
const { value } = defineProps()
|
||||||
|
function test(value) {
|
||||||
|
try {
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(value)
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`console.log(__props.value)`)
|
||||||
|
})
|
||||||
|
|
||||||
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
|
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
|
@ -1434,6 +1434,29 @@ describe('resolveType', () => {
|
||||||
colsLg: ['Number'],
|
colsLg: ['Number'],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('allowArbitraryExtensions', () => {
|
||||||
|
const files = {
|
||||||
|
'/foo.d.vue.ts': 'export type Foo = number;',
|
||||||
|
'/foo.vue': '<template><div /></template>',
|
||||||
|
'/bar.d.css.ts': 'export type Bar = string;',
|
||||||
|
'/bar.css': ':root { --color: red; }',
|
||||||
|
}
|
||||||
|
|
||||||
|
const { props } = resolve(
|
||||||
|
`
|
||||||
|
import { Foo } from './foo.vue'
|
||||||
|
import { Bar } from './bar.css'
|
||||||
|
defineProps<{ foo: Foo; bar: Bar }>()
|
||||||
|
`,
|
||||||
|
files,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(props).toStrictEqual({
|
||||||
|
foo: ['Number'],
|
||||||
|
bar: ['String'],
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -211,38 +211,42 @@ color: red
|
||||||
expect(
|
expect(
|
||||||
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
|
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:where(:hover) { color: blue;
|
.div[data-v-test]:where(:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
|
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:is(:hover) { color: blue;
|
.div[data-v-test]:is(:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileScoped(
|
compileScoped(
|
||||||
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
|
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:where(.foo:hover) { color: blue;
|
.div[data-v-test]:where(.foo:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileScoped(
|
compileScoped(
|
||||||
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
|
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:is(.foo:hover) { color: blue;
|
.div[data-v-test]:is(.foo:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('media query', () => {
|
test('media query', () => {
|
||||||
|
@ -489,7 +493,31 @@ describe('SFC style preprocessors', () => {
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
expect(compileScoped(`.foo * { color: red; }`)).toMatchInlineSnapshot(`
|
expect(compileScoped(`.foo * { color: red; }`)).toMatchInlineSnapshot(`
|
||||||
".foo[data-v-test] * { color: red;
|
".foo[data-v-test] [data-v-test] { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compileScoped(`.foo :active { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".foo[data-v-test] :active { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compileScoped(`.foo *:active { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
".foo[data-v-test] [data-v-test]:active { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compileScoped(`.foo * .bar { color: red; }`)).toMatchInlineSnapshot(`
|
||||||
|
".foo * .bar[data-v-test] { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compileScoped(`:last-child * { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"[data-v-test]:last-child [data-v-test] { color: red;
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
expect(compileScoped(`:last-child *:active { color: red; }`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"[data-v-test]:last-child [data-v-test]:active { color: red;
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ import {
|
||||||
} from '../src/compileTemplate'
|
} from '../src/compileTemplate'
|
||||||
import { type SFCTemplateBlock, parse } from '../src/parse'
|
import { type SFCTemplateBlock, parse } from '../src/parse'
|
||||||
import { compileScript } from '../src'
|
import { compileScript } from '../src'
|
||||||
|
import { getPositionInCode } from './utils'
|
||||||
|
|
||||||
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
|
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
|
||||||
return compileTemplate({
|
return compileTemplate({
|
||||||
|
@ -157,6 +158,35 @@ test('source map', () => {
|
||||||
).toMatchObject(getPositionInCode(template.content, `foobar`))
|
).toMatchObject(getPositionInCode(template.content, `foobar`))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('source map: v-if generated comment should not have original position', () => {
|
||||||
|
const template = parse(
|
||||||
|
`
|
||||||
|
<template>
|
||||||
|
<div v-if="true"></div>
|
||||||
|
</template>
|
||||||
|
`,
|
||||||
|
{ filename: 'example.vue', sourceMap: true },
|
||||||
|
).descriptor.template!
|
||||||
|
|
||||||
|
const { code, map } = compile({
|
||||||
|
filename: 'example.vue',
|
||||||
|
source: template.content,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(map!.sources).toEqual([`example.vue`])
|
||||||
|
expect(map!.sourcesContent).toEqual([template.content])
|
||||||
|
|
||||||
|
const consumer = new SourceMapConsumer(map as RawSourceMap)
|
||||||
|
const commentNode = code.match(/_createCommentVNode\("v-if", true\)/)
|
||||||
|
expect(commentNode).not.toBeNull()
|
||||||
|
const commentPosition = getPositionInCode(code, commentNode![0])
|
||||||
|
const originalPosition = consumer.originalPositionFor(commentPosition)
|
||||||
|
// the comment node should not be mapped to the original source
|
||||||
|
expect(originalPosition.column).toBeNull()
|
||||||
|
expect(originalPosition.line).toBeNull()
|
||||||
|
expect(originalPosition.source).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
test('should work w/ AST from descriptor', () => {
|
test('should work w/ AST from descriptor', () => {
|
||||||
const source = `
|
const source = `
|
||||||
<template>
|
<template>
|
||||||
|
@ -482,36 +512,3 @@ test('non-identifier expression in legacy filter syntax', () => {
|
||||||
babelParse(compilationResult.code, { sourceType: 'module' })
|
babelParse(compilationResult.code, { sourceType: 'module' })
|
||||||
}).not.toThrow()
|
}).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
interface Pos {
|
|
||||||
line: number
|
|
||||||
column: number
|
|
||||||
name?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
function getPositionInCode(
|
|
||||||
code: string,
|
|
||||||
token: string,
|
|
||||||
expectName: string | boolean = false,
|
|
||||||
): Pos {
|
|
||||||
const generatedOffset = code.indexOf(token)
|
|
||||||
let line = 1
|
|
||||||
let lastNewLinePos = -1
|
|
||||||
for (let i = 0; i < generatedOffset; i++) {
|
|
||||||
if (code.charCodeAt(i) === 10 /* newline char code */) {
|
|
||||||
line++
|
|
||||||
lastNewLinePos = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const res: Pos = {
|
|
||||||
line,
|
|
||||||
column:
|
|
||||||
lastNewLinePos === -1
|
|
||||||
? generatedOffset
|
|
||||||
: generatedOffset - lastNewLinePos - 1,
|
|
||||||
}
|
|
||||||
if (expectName) {
|
|
||||||
res.name = typeof expectName === 'string' ? expectName : token
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ font-weight: bold;
|
||||||
|
|
||||||
const consumer = new SourceMapConsumer(script!.map!)
|
const consumer = new SourceMapConsumer(script!.map!)
|
||||||
consumer.eachMapping(mapping => {
|
consumer.eachMapping(mapping => {
|
||||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
expect(mapping.originalLine! - mapping.generatedLine).toBe(padding)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -100,8 +100,8 @@ font-weight: bold;
|
||||||
|
|
||||||
const consumer = new SourceMapConsumer(template.map!)
|
const consumer = new SourceMapConsumer(template.map!)
|
||||||
consumer.eachMapping(mapping => {
|
consumer.eachMapping(mapping => {
|
||||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
expect(mapping.originalLine! - mapping.generatedLine).toBe(padding)
|
||||||
expect(mapping.originalColumn - mapping.generatedColumn).toBe(2)
|
expect(mapping.originalColumn! - mapping.generatedColumn).toBe(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ font-weight: bold;
|
||||||
|
|
||||||
const consumer = new SourceMapConsumer(custom!.map!)
|
const consumer = new SourceMapConsumer(custom!.map!)
|
||||||
consumer.eachMapping(mapping => {
|
consumer.eachMapping(mapping => {
|
||||||
expect(mapping.originalLine - mapping.generatedLine).toBe(padding)
|
expect(mapping.originalLine! - mapping.generatedLine).toBe(padding)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -40,3 +40,36 @@ export function assertCode(code: string): void {
|
||||||
}
|
}
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Pos {
|
||||||
|
line: number
|
||||||
|
column: number
|
||||||
|
name?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPositionInCode(
|
||||||
|
code: string,
|
||||||
|
token: string,
|
||||||
|
expectName: string | boolean = false,
|
||||||
|
): Pos {
|
||||||
|
const generatedOffset = code.indexOf(token)
|
||||||
|
let line = 1
|
||||||
|
let lastNewLinePos = -1
|
||||||
|
for (let i = 0; i < generatedOffset; i++) {
|
||||||
|
if (code.charCodeAt(i) === 10 /* newline char code */) {
|
||||||
|
line++
|
||||||
|
lastNewLinePos = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const res: Pos = {
|
||||||
|
line,
|
||||||
|
column:
|
||||||
|
lastNewLinePos === -1
|
||||||
|
? generatedOffset
|
||||||
|
: generatedOffset - lastNewLinePos - 1,
|
||||||
|
}
|
||||||
|
if (expectName) {
|
||||||
|
res.name = typeof expectName === 'string' ? expectName : token
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.5.12",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
"module": "dist/compiler-sfc.esm-browser.js",
|
"module": "dist/compiler-sfc.esm-browser.js",
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
"@vue/shared": "workspace:*",
|
"@vue/shared": "workspace:*",
|
||||||
"estree-walker": "catalog:",
|
"estree-walker": "catalog:",
|
||||||
"magic-string": "catalog:",
|
"magic-string": "catalog:",
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.5.3",
|
||||||
"source-map-js": "catalog:"
|
"source-map-js": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
@ -58,10 +58,10 @@
|
||||||
"hash-sum": "^2.0.0",
|
"hash-sum": "^2.0.0",
|
||||||
"lru-cache": "10.1.0",
|
"lru-cache": "10.1.0",
|
||||||
"merge-source-map": "^1.1.0",
|
"merge-source-map": "^1.1.0",
|
||||||
"minimatch": "~9.0.5",
|
"minimatch": "~10.0.1",
|
||||||
"postcss-modules": "^6.0.0",
|
"postcss-modules": "^6.0.1",
|
||||||
"postcss-selector-parser": "^6.1.2",
|
"postcss-selector-parser": "^7.1.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.80.3"
|
"sass": "^1.89.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,11 @@ import type {
|
||||||
Statement,
|
Statement,
|
||||||
} from '@babel/types'
|
} from '@babel/types'
|
||||||
import { walk } from 'estree-walker'
|
import { walk } from 'estree-walker'
|
||||||
import type { RawSourceMap } from 'source-map-js'
|
import {
|
||||||
|
type RawSourceMap,
|
||||||
|
SourceMapConsumer,
|
||||||
|
SourceMapGenerator,
|
||||||
|
} from 'source-map-js'
|
||||||
import {
|
import {
|
||||||
normalScriptDefaultVar,
|
normalScriptDefaultVar,
|
||||||
processNormalScript,
|
processNormalScript,
|
||||||
|
@ -170,8 +174,6 @@ export function compileScript(
|
||||||
const scriptLang = script && script.lang
|
const scriptLang = script && script.lang
|
||||||
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||||
|
|
||||||
let refBindings: string[] | undefined
|
|
||||||
|
|
||||||
if (!scriptSetup) {
|
if (!scriptSetup) {
|
||||||
if (!script) {
|
if (!script) {
|
||||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||||
|
@ -740,12 +742,6 @@ export function compileScript(
|
||||||
for (const key in setupBindings) {
|
for (const key in setupBindings) {
|
||||||
ctx.bindingMetadata[key] = setupBindings[key]
|
ctx.bindingMetadata[key] = setupBindings[key]
|
||||||
}
|
}
|
||||||
// known ref bindings
|
|
||||||
if (refBindings) {
|
|
||||||
for (const key of refBindings) {
|
|
||||||
ctx.bindingMetadata[key] = BindingTypes.SETUP_REF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 7. inject `useCssVars` calls
|
// 7. inject `useCssVars` calls
|
||||||
if (
|
if (
|
||||||
|
@ -817,6 +813,7 @@ export function compileScript(
|
||||||
args += `, { ${destructureElements.join(', ')} }`
|
args += `, { ${destructureElements.join(', ')} }`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let templateMap
|
||||||
// 9. generate return statement
|
// 9. generate return statement
|
||||||
let returned
|
let returned
|
||||||
if (
|
if (
|
||||||
|
@ -866,7 +863,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
// inline render function mode - we are going to compile the template and
|
// inline render function mode - we are going to compile the template and
|
||||||
// inline it right here
|
// inline it right here
|
||||||
const { code, ast, preamble, tips, errors } = compileTemplate({
|
const { code, ast, preamble, tips, errors, map } = compileTemplate({
|
||||||
filename,
|
filename,
|
||||||
ast: sfc.template.ast,
|
ast: sfc.template.ast,
|
||||||
source: sfc.template.content,
|
source: sfc.template.content,
|
||||||
|
@ -884,6 +881,7 @@ export function compileScript(
|
||||||
bindingMetadata: ctx.bindingMetadata,
|
bindingMetadata: ctx.bindingMetadata,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
templateMap = map
|
||||||
if (tips.length) {
|
if (tips.length) {
|
||||||
tips.forEach(warnOnce)
|
tips.forEach(warnOnce)
|
||||||
}
|
}
|
||||||
|
@ -1022,19 +1020,28 @@ export function compileScript(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const content = ctx.s.toString()
|
||||||
|
let map =
|
||||||
|
options.sourceMap !== false
|
||||||
|
? (ctx.s.generateMap({
|
||||||
|
source: filename,
|
||||||
|
hires: true,
|
||||||
|
includeContent: true,
|
||||||
|
}) as unknown as RawSourceMap)
|
||||||
|
: undefined
|
||||||
|
// merge source maps of the script setup and template in inline mode
|
||||||
|
if (templateMap && map) {
|
||||||
|
const offset = content.indexOf(returned)
|
||||||
|
const templateLineOffset =
|
||||||
|
content.slice(0, offset).split(/\r?\n/).length - 1
|
||||||
|
map = mergeSourceMaps(map, templateMap, templateLineOffset)
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
...scriptSetup,
|
...scriptSetup,
|
||||||
bindings: ctx.bindingMetadata,
|
bindings: ctx.bindingMetadata,
|
||||||
imports: ctx.userImports,
|
imports: ctx.userImports,
|
||||||
content: ctx.s.toString(),
|
content,
|
||||||
map:
|
map,
|
||||||
options.sourceMap !== false
|
|
||||||
? (ctx.s.generateMap({
|
|
||||||
source: filename,
|
|
||||||
hires: true,
|
|
||||||
includeContent: true,
|
|
||||||
}) as unknown as RawSourceMap)
|
|
||||||
: undefined,
|
|
||||||
scriptAst: scriptAst?.body,
|
scriptAst: scriptAst?.body,
|
||||||
scriptSetupAst: scriptSetupAst?.body,
|
scriptSetupAst: scriptSetupAst?.body,
|
||||||
deps: ctx.deps ? [...ctx.deps] : undefined,
|
deps: ctx.deps ? [...ctx.deps] : undefined,
|
||||||
|
@ -1112,6 +1119,7 @@ function walkDeclaration(
|
||||||
m === userImportAliases['shallowRef'] ||
|
m === userImportAliases['shallowRef'] ||
|
||||||
m === userImportAliases['customRef'] ||
|
m === userImportAliases['customRef'] ||
|
||||||
m === userImportAliases['toRef'] ||
|
m === userImportAliases['toRef'] ||
|
||||||
|
m === userImportAliases['useTemplateRef'] ||
|
||||||
m === DEFINE_MODEL,
|
m === DEFINE_MODEL,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
@ -1291,3 +1299,42 @@ function isStaticNode(node: Node): boolean {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mergeSourceMaps(
|
||||||
|
scriptMap: RawSourceMap,
|
||||||
|
templateMap: RawSourceMap,
|
||||||
|
templateLineOffset: number,
|
||||||
|
): RawSourceMap {
|
||||||
|
const generator = new SourceMapGenerator()
|
||||||
|
const addMapping = (map: RawSourceMap, lineOffset = 0) => {
|
||||||
|
const consumer = new SourceMapConsumer(map)
|
||||||
|
;(consumer as any).sources.forEach((sourceFile: string) => {
|
||||||
|
;(generator as any)._sources.add(sourceFile)
|
||||||
|
const sourceContent = consumer.sourceContentFor(sourceFile)
|
||||||
|
if (sourceContent != null) {
|
||||||
|
generator.setSourceContent(sourceFile, sourceContent)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
consumer.eachMapping(m => {
|
||||||
|
if (m.originalLine == null) return
|
||||||
|
generator.addMapping({
|
||||||
|
generated: {
|
||||||
|
line: m.generatedLine + lineOffset,
|
||||||
|
column: m.generatedColumn,
|
||||||
|
},
|
||||||
|
original: {
|
||||||
|
line: m.originalLine,
|
||||||
|
column: m.originalColumn!,
|
||||||
|
},
|
||||||
|
source: m.source,
|
||||||
|
name: m.name,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
addMapping(scriptMap)
|
||||||
|
addMapping(templateMap, templateLineOffset)
|
||||||
|
;(generator as any)._sourceRoot = scriptMap.sourceRoot
|
||||||
|
;(generator as any)._file = scriptMap.file
|
||||||
|
return (generator as any).toJSON()
|
||||||
|
}
|
||||||
|
|
|
@ -289,7 +289,7 @@ function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
||||||
|
|
||||||
const origPosInOldMap = oldMapConsumer.originalPositionFor({
|
const origPosInOldMap = oldMapConsumer.originalPositionFor({
|
||||||
line: m.originalLine,
|
line: m.originalLine,
|
||||||
column: m.originalColumn,
|
column: m.originalColumn!,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (origPosInOldMap.source == null) {
|
if (origPosInOldMap.source == null) {
|
||||||
|
@ -305,7 +305,7 @@ function mapLines(oldMap: RawSourceMap, newMap: RawSourceMap): RawSourceMap {
|
||||||
line: origPosInOldMap.line, // map line
|
line: origPosInOldMap.line, // map line
|
||||||
// use current column, since the oldMap produced by @vue/compiler-sfc
|
// use current column, since the oldMap produced by @vue/compiler-sfc
|
||||||
// does not
|
// does not
|
||||||
column: m.originalColumn,
|
column: m.originalColumn!,
|
||||||
},
|
},
|
||||||
source: origPosInOldMap.source,
|
source: origPosInOldMap.source,
|
||||||
name: origPosInOldMap.name,
|
name: origPosInOldMap.name,
|
||||||
|
|
|
@ -39,7 +39,7 @@ export function rewriteDefaultAST(
|
||||||
ast.forEach(node => {
|
ast.forEach(node => {
|
||||||
if (node.type === 'ExportDefaultDeclaration') {
|
if (node.type === 'ExportDefaultDeclaration') {
|
||||||
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
if (node.declaration.type === 'ClassDeclaration' && node.declaration.id) {
|
||||||
let start: number =
|
const start: number =
|
||||||
node.declaration.decorators && node.declaration.decorators.length > 0
|
node.declaration.decorators && node.declaration.decorators.length > 0
|
||||||
? node.declaration.decorators[
|
? node.declaration.decorators[
|
||||||
node.declaration.decorators.length - 1
|
node.declaration.decorators.length - 1
|
||||||
|
|
|
@ -22,6 +22,13 @@ export function processDefineModel(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!declId) {
|
||||||
|
ctx.error(
|
||||||
|
'defineModel() must be assigned to a variable. For example: const model = defineModel()',
|
||||||
|
node,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
ctx.hasDefineModelCall = true
|
ctx.hasDefineModelCall = true
|
||||||
|
|
||||||
const type =
|
const type =
|
||||||
|
|
|
@ -291,7 +291,8 @@ export function transformDestructuredProps(
|
||||||
parent && parentStack.pop()
|
parent && parentStack.pop()
|
||||||
if (
|
if (
|
||||||
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
||||||
isFunctionType(node)
|
isFunctionType(node) ||
|
||||||
|
node.type === 'CatchClause'
|
||||||
) {
|
) {
|
||||||
popScope()
|
popScope()
|
||||||
}
|
}
|
||||||
|
|
|
@ -860,13 +860,13 @@ function resolveFS(ctx: TypeResolveContext): FS | undefined {
|
||||||
}
|
}
|
||||||
return (ctx.fs = {
|
return (ctx.fs = {
|
||||||
fileExists(file) {
|
fileExists(file) {
|
||||||
if (file.endsWith('.vue.ts')) {
|
if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) {
|
||||||
file = file.replace(/\.ts$/, '')
|
file = file.replace(/\.ts$/, '')
|
||||||
}
|
}
|
||||||
return fs.fileExists(file)
|
return fs.fileExists(file)
|
||||||
},
|
},
|
||||||
readFile(file) {
|
readFile(file) {
|
||||||
if (file.endsWith('.vue.ts')) {
|
if (file.endsWith('.vue.ts') && !file.endsWith('.d.vue.ts')) {
|
||||||
file = file.replace(/\.ts$/, '')
|
file = file.replace(/\.ts$/, '')
|
||||||
}
|
}
|
||||||
return fs.readFile(file)
|
return fs.readFile(file)
|
||||||
|
@ -1059,7 +1059,7 @@ function resolveWithTS(
|
||||||
|
|
||||||
if (res.resolvedModule) {
|
if (res.resolvedModule) {
|
||||||
let filename = res.resolvedModule.resolvedFileName
|
let filename = res.resolvedModule.resolvedFileName
|
||||||
if (filename.endsWith('.vue.ts')) {
|
if (filename.endsWith('.vue.ts') && !filename.endsWith('.d.vue.ts')) {
|
||||||
filename = filename.replace(/\.ts$/, '')
|
filename = filename.replace(/\.ts$/, '')
|
||||||
}
|
}
|
||||||
return fs.realpath ? fs.realpath(filename) : filename
|
return fs.realpath ? fs.realpath(filename) : filename
|
||||||
|
@ -1129,7 +1129,7 @@ export function fileToScope(
|
||||||
// fs should be guaranteed to exist here
|
// fs should be guaranteed to exist here
|
||||||
const fs = resolveFS(ctx)!
|
const fs = resolveFS(ctx)!
|
||||||
const source = fs.readFile(filename) || ''
|
const source = fs.readFile(filename) || ''
|
||||||
const body = parseFile(filename, source, ctx.options.babelParserPlugins)
|
const body = parseFile(filename, source, fs, ctx.options.babelParserPlugins)
|
||||||
const scope = new TypeScope(filename, source, 0, recordImports(body))
|
const scope = new TypeScope(filename, source, 0, recordImports(body))
|
||||||
recordTypes(ctx, body, scope, asGlobal)
|
recordTypes(ctx, body, scope, asGlobal)
|
||||||
fileToScopeCache.set(filename, scope)
|
fileToScopeCache.set(filename, scope)
|
||||||
|
@ -1139,6 +1139,7 @@ export function fileToScope(
|
||||||
function parseFile(
|
function parseFile(
|
||||||
filename: string,
|
filename: string,
|
||||||
content: string,
|
content: string,
|
||||||
|
fs: FS,
|
||||||
parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
|
parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
|
||||||
): Statement[] {
|
): Statement[] {
|
||||||
const ext = extname(filename)
|
const ext = extname(filename)
|
||||||
|
@ -1151,7 +1152,21 @@ function parseFile(
|
||||||
),
|
),
|
||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
}).program.body
|
}).program.body
|
||||||
} else if (ext === '.vue') {
|
}
|
||||||
|
|
||||||
|
// simulate `allowArbitraryExtensions` on TypeScript >= 5.0
|
||||||
|
const isUnknownTypeSource = !/\.[cm]?[tj]sx?$/.test(filename)
|
||||||
|
const arbitraryTypeSource = `${filename.slice(0, -ext.length)}.d${ext}.ts`
|
||||||
|
const hasArbitraryTypeDeclaration =
|
||||||
|
isUnknownTypeSource && fs.fileExists(arbitraryTypeSource)
|
||||||
|
if (hasArbitraryTypeDeclaration) {
|
||||||
|
return babelParse(fs.readFile(arbitraryTypeSource)!, {
|
||||||
|
plugins: resolveParserPlugins('ts', parserPlugins, true),
|
||||||
|
sourceType: 'module',
|
||||||
|
}).program.body
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ext === '.vue') {
|
||||||
const {
|
const {
|
||||||
descriptor: { script, scriptSetup },
|
descriptor: { script, scriptSetup },
|
||||||
} = parse(content)
|
} = parse(content)
|
||||||
|
@ -1554,6 +1569,14 @@ export function inferRuntimeType(
|
||||||
case 'TSTypeReference': {
|
case 'TSTypeReference': {
|
||||||
const resolved = resolveTypeReference(ctx, node, scope)
|
const resolved = resolveTypeReference(ctx, node, scope)
|
||||||
if (resolved) {
|
if (resolved) {
|
||||||
|
if (resolved.type === 'TSTypeAliasDeclaration') {
|
||||||
|
return inferRuntimeType(
|
||||||
|
ctx,
|
||||||
|
resolved.typeAnnotation,
|
||||||
|
resolved._ownerScope,
|
||||||
|
isKeyOf,
|
||||||
|
)
|
||||||
|
}
|
||||||
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
return inferRuntimeType(ctx, resolved, resolved._ownerScope, isKeyOf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ function rewriteSelector(
|
||||||
slotted = false,
|
slotted = false,
|
||||||
) {
|
) {
|
||||||
let node: selectorParser.Node | null = null
|
let node: selectorParser.Node | null = null
|
||||||
|
let starNode: selectorParser.Node | null = null
|
||||||
let shouldInject = !deep
|
let shouldInject = !deep
|
||||||
// find the last child node to insert attribute selector
|
// find the last child node to insert attribute selector
|
||||||
selector.each(n => {
|
selector.each(n => {
|
||||||
|
@ -189,8 +190,7 @@ function rewriteSelector(
|
||||||
// global: replace with inner selector and do not inject [id].
|
// global: replace with inner selector and do not inject [id].
|
||||||
// ::v-global(.foo) -> .foo
|
// ::v-global(.foo) -> .foo
|
||||||
if (value === ':global' || value === '::v-global') {
|
if (value === ':global' || value === '::v-global') {
|
||||||
selectorRoot.insertAfter(selector, n.nodes[0])
|
selector.replaceWith(n.nodes[0])
|
||||||
selectorRoot.removeChild(selector)
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,17 +217,21 @@ function rewriteSelector(
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// .foo * -> .foo[xxxxxxx] *
|
// store the universal selector so it can be rewritten later
|
||||||
if (node) return
|
// .foo * -> .foo[xxxxxxx] [xxxxxxx]
|
||||||
|
starNode = n
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
(n.type !== 'pseudo' && n.type !== 'combinator') ||
|
(n.type !== 'pseudo' &&
|
||||||
|
n.type !== 'combinator' &&
|
||||||
|
n.type !== 'universal') ||
|
||||||
(n.type === 'pseudo' &&
|
(n.type === 'pseudo' &&
|
||||||
(n.value === ':is' || n.value === ':where') &&
|
(n.value === ':is' || n.value === ':where') &&
|
||||||
!node)
|
!node)
|
||||||
) {
|
) {
|
||||||
node = n
|
node = n
|
||||||
|
starNode = null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -275,6 +279,20 @@ function rewriteSelector(
|
||||||
quoteMark: `"`,
|
quoteMark: `"`,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
// Used for trailing universal selectors (#12906)
|
||||||
|
// `.foo * {}` -> `.foo[xxxxxxx] [xxxxxxx] {}`
|
||||||
|
if (starNode) {
|
||||||
|
selector.insertBefore(
|
||||||
|
starNode,
|
||||||
|
selectorParser.attribute({
|
||||||
|
attribute: idToAdd,
|
||||||
|
value: idToAdd,
|
||||||
|
raws: {},
|
||||||
|
quoteMark: `"`,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
selector.removeChild(starNode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -337,6 +337,39 @@ describe('ssr: element', () => {
|
||||||
`)
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('custom dir with v-text', () => {
|
||||||
|
expect(getCompiledString(`<div v-xxx v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_ssrGetDirectiveProps(_ctx, _directive_xxx))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('custom dir with v-text and normal attrs', () => {
|
||||||
|
expect(getCompiledString(`<div class="test" v-xxx v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_mergeProps({ class: "test" }, _ssrGetDirectiveProps(_ctx, _directive_xxx)))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('mulptiple custom dirs with v-text', () => {
|
||||||
|
expect(getCompiledString(`<div v-xxx v-yyy v-text="foo" />`))
|
||||||
|
.toMatchInlineSnapshot(`
|
||||||
|
"\`<div\${
|
||||||
|
_ssrRenderAttrs(_mergeProps(_ssrGetDirectiveProps(_ctx, _directive_xxx), _ssrGetDirectiveProps(_ctx, _directive_yyy)))
|
||||||
|
}>\${
|
||||||
|
_ssrInterpolate(_ctx.foo)
|
||||||
|
}</div>\`"
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
|
||||||
test('custom dir with object v-bind', () => {
|
test('custom dir with object v-bind', () => {
|
||||||
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
||||||
.toMatchInlineSnapshot(`
|
.toMatchInlineSnapshot(`
|
||||||
|
|
|
@ -52,6 +52,52 @@ describe('ssr: v-model', () => {
|
||||||
}"
|
}"
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
compileWithWrapper(
|
||||||
|
`<select v-model="model"><option v-for="i in items" :value="i"></option></select>`,
|
||||||
|
).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select><!--[-->\`)
|
||||||
|
_ssrRenderList(_ctx.items, (i) => {
|
||||||
|
_push(\`<option\${
|
||||||
|
_ssrRenderAttr("value", i)
|
||||||
|
}\${
|
||||||
|
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||||
|
? _ssrLooseContain(_ctx.model, i)
|
||||||
|
: _ssrLooseEqual(_ctx.model, i))) ? " selected" : ""
|
||||||
|
}></option>\`)
|
||||||
|
})
|
||||||
|
_push(\`<!--]--></select></div>\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
|
expect(
|
||||||
|
compileWithWrapper(
|
||||||
|
`<select v-model="model"><option v-if="true" :value="i"></option></select>`,
|
||||||
|
).code,
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
"const { ssrRenderAttr: _ssrRenderAttr, ssrIncludeBooleanAttr: _ssrIncludeBooleanAttr, ssrLooseContain: _ssrLooseContain, ssrLooseEqual: _ssrLooseEqual, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer")
|
||||||
|
|
||||||
|
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||||
|
_push(\`<div\${_ssrRenderAttrs(_attrs)}><select>\`)
|
||||||
|
if (true) {
|
||||||
|
_push(\`<option\${
|
||||||
|
_ssrRenderAttr("value", _ctx.i)
|
||||||
|
}\${
|
||||||
|
(_ssrIncludeBooleanAttr((Array.isArray(_ctx.model))
|
||||||
|
? _ssrLooseContain(_ctx.model, _ctx.i)
|
||||||
|
: _ssrLooseEqual(_ctx.model, _ctx.i))) ? " selected" : ""
|
||||||
|
}></option>\`)
|
||||||
|
} else {
|
||||||
|
_push(\`<!---->\`)
|
||||||
|
}
|
||||||
|
_push(\`</select></div>\`)
|
||||||
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileWithWrapper(
|
compileWithWrapper(
|
||||||
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
|
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.5.12",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-ssr",
|
"description": "@vue/compiler-ssr",
|
||||||
"main": "dist/compiler-ssr.cjs.js",
|
"main": "dist/compiler-ssr.cjs.js",
|
||||||
"types": "dist/compiler-ssr.d.ts",
|
"types": "dist/compiler-ssr.d.ts",
|
||||||
|
|
|
@ -28,6 +28,7 @@ import {
|
||||||
createSequenceExpression,
|
createSequenceExpression,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
createTemplateLiteral,
|
createTemplateLiteral,
|
||||||
|
findDir,
|
||||||
hasDynamicKeyVBind,
|
hasDynamicKeyVBind,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
isStaticExp,
|
isStaticExp,
|
||||||
|
@ -164,24 +165,28 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
} else if (directives.length && !node.children.length) {
|
} else if (directives.length && !node.children.length) {
|
||||||
const tempId = `_temp${context.temps++}`
|
// v-text directive has higher priority than the merged props
|
||||||
propsExp.arguments = [
|
const vText = findDir(node, 'text')
|
||||||
createAssignmentExpression(
|
if (!vText) {
|
||||||
createSimpleExpression(tempId, false),
|
const tempId = `_temp${context.temps++}`
|
||||||
mergedProps,
|
propsExp.arguments = [
|
||||||
),
|
createAssignmentExpression(
|
||||||
]
|
createSimpleExpression(tempId, false),
|
||||||
rawChildrenMap.set(
|
mergedProps,
|
||||||
node,
|
),
|
||||||
createConditionalExpression(
|
]
|
||||||
createSimpleExpression(`"textContent" in ${tempId}`, false),
|
rawChildrenMap.set(
|
||||||
createCallExpression(context.helper(SSR_INTERPOLATE), [
|
node,
|
||||||
createSimpleExpression(`${tempId}.textContent`, false),
|
createConditionalExpression(
|
||||||
]),
|
createSimpleExpression(`"textContent" in ${tempId}`, false),
|
||||||
createSimpleExpression(`${tempId}.innerHTML ?? ''`, false),
|
createCallExpression(context.helper(SSR_INTERPOLATE), [
|
||||||
false,
|
createSimpleExpression(`${tempId}.textContent`, false),
|
||||||
),
|
]),
|
||||||
)
|
createSimpleExpression(`${tempId}.innerHTML ?? ''`, false),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (needTagForRuntime) {
|
if (needTagForRuntime) {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
type ExpressionNode,
|
type ExpressionNode,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
type PlainElementNode,
|
type PlainElementNode,
|
||||||
|
type TemplateChildNode,
|
||||||
createCallExpression,
|
createCallExpression,
|
||||||
createConditionalExpression,
|
createConditionalExpression,
|
||||||
createDOMCompilerError,
|
createDOMCompilerError,
|
||||||
|
@ -162,11 +163,18 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
||||||
checkDuplicatedValue()
|
checkDuplicatedValue()
|
||||||
node.children = [createInterpolation(model, model.loc)]
|
node.children = [createInterpolation(model, model.loc)]
|
||||||
} else if (node.tag === 'select') {
|
} else if (node.tag === 'select') {
|
||||||
node.children.forEach(child => {
|
const processChildren = (children: TemplateChildNode[]) => {
|
||||||
if (child.type === NodeTypes.ELEMENT) {
|
children.forEach(child => {
|
||||||
processOption(child as PlainElementNode)
|
if (child.type === NodeTypes.ELEMENT) {
|
||||||
}
|
processOption(child as PlainElementNode)
|
||||||
})
|
} else if (child.type === NodeTypes.FOR) {
|
||||||
|
processChildren(child.children)
|
||||||
|
} else if (child.type === NodeTypes.IF) {
|
||||||
|
child.branches.forEach(b => processChildren(b.children))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
processChildren(node.children)
|
||||||
} else {
|
} else {
|
||||||
context.onError(
|
context.onError(
|
||||||
createDOMCompilerError(
|
createDOMCompilerError(
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { type ComputedRef, type Ref, computed, effect, ref } from '../src'
|
import type { ComputedRef, Ref } from '../src'
|
||||||
|
import { computed, effect, ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
|
declare module '../dist/reactivity.esm-browser.prod' {
|
||||||
|
function computed(...args: any[]): any
|
||||||
|
}
|
||||||
|
|
||||||
describe('computed', () => {
|
describe('computed', () => {
|
||||||
bench('create computed', () => {
|
bench('create computed', () => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { type Ref, effect, ref } from '../src'
|
import type { Ref } from '../src'
|
||||||
|
import { effect, ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
describe('effect', () => {
|
describe('effect', () => {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,9 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { effect, reactive, shallowReadArray } from '../src'
|
import {
|
||||||
|
effect,
|
||||||
|
reactive,
|
||||||
|
shallowReadArray,
|
||||||
|
} from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { type ComputedRef, computed, reactive } from '../src'
|
import type { ComputedRef } from '../src'
|
||||||
|
import { computed, reactive } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
function createMap(obj: Record<string, any>) {
|
function createMap(obj: Record<string, any>) {
|
||||||
const map = new Map()
|
const map = new Map()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { bench } from 'vitest'
|
import { bench } from 'vitest'
|
||||||
import { reactive } from '../src'
|
import { reactive } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
bench('create reactive obj', () => {
|
bench('create reactive obj', () => {
|
||||||
reactive({ a: 1 })
|
reactive({ a: 1 })
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { bench, describe } from 'vitest'
|
import { bench, describe } from 'vitest'
|
||||||
import { ref } from '../src/index'
|
import { ref } from '../dist/reactivity.esm-browser.prod'
|
||||||
|
|
||||||
describe('ref', () => {
|
describe('ref', () => {
|
||||||
bench('create ref', () => {
|
bench('create ref', () => {
|
||||||
|
|
|
@ -1012,6 +1012,17 @@ describe('reactivity/computed', () => {
|
||||||
expect(cValue.value).toBe(1)
|
expect(cValue.value).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not recompute if computed does not track reactive data', async () => {
|
||||||
|
const spy = vi.fn()
|
||||||
|
const c1 = computed(() => spy())
|
||||||
|
|
||||||
|
c1.value
|
||||||
|
ref(0).value++ // update globalVersion
|
||||||
|
c1.value
|
||||||
|
|
||||||
|
expect(spy).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('computed should remain live after losing all subscribers', () => {
|
test('computed should remain live after losing all subscribers', () => {
|
||||||
const state = reactive({ a: 1 })
|
const state = reactive({ a: 1 })
|
||||||
const p = computed(() => state.a + 1)
|
const p = computed(() => state.a + 1)
|
||||||
|
|
|
@ -176,7 +176,7 @@ describe('reactivity/effect/scope', () => {
|
||||||
|
|
||||||
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
expect('[Vue warn] cannot run an inactive effect scope.').toHaveBeenWarned()
|
||||||
|
|
||||||
expect(scope.effects.length).toBe(1)
|
expect(scope.effects.length).toBe(0)
|
||||||
|
|
||||||
counter.num = 7
|
counter.num = 7
|
||||||
expect(dummy).toBe(0)
|
expect(dummy).toBe(0)
|
||||||
|
@ -322,4 +322,44 @@ describe('reactivity/effect/scope', () => {
|
||||||
scope.resume()
|
scope.resume()
|
||||||
expect(fnSpy).toHaveBeenCalledTimes(3)
|
expect(fnSpy).toHaveBeenCalledTimes(3)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('removing a watcher while stopping its effectScope', async () => {
|
||||||
|
const count = ref(0)
|
||||||
|
const scope = effectScope()
|
||||||
|
let watcherCalls = 0
|
||||||
|
let cleanupCalls = 0
|
||||||
|
|
||||||
|
scope.run(() => {
|
||||||
|
const stop1 = watch(count, () => {
|
||||||
|
watcherCalls++
|
||||||
|
})
|
||||||
|
watch(count, (val, old, onCleanup) => {
|
||||||
|
watcherCalls++
|
||||||
|
onCleanup(() => {
|
||||||
|
cleanupCalls++
|
||||||
|
stop1()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
watch(count, () => {
|
||||||
|
watcherCalls++
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(watcherCalls).toBe(0)
|
||||||
|
expect(cleanupCalls).toBe(0)
|
||||||
|
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(watcherCalls).toBe(3)
|
||||||
|
expect(cleanupCalls).toBe(0)
|
||||||
|
|
||||||
|
scope.stop()
|
||||||
|
count.value++
|
||||||
|
await nextTick()
|
||||||
|
expect(watcherCalls).toBe(3)
|
||||||
|
expect(cleanupCalls).toBe(1)
|
||||||
|
|
||||||
|
expect(scope.effects.length).toBe(0)
|
||||||
|
expect(scope.cleanups.length).toBe(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { isRef, ref } from '../src/ref'
|
import { isRef, ref, shallowRef } from '../src/ref'
|
||||||
import {
|
import {
|
||||||
isProxy,
|
isProxy,
|
||||||
isReactive,
|
isReactive,
|
||||||
|
@ -301,6 +301,13 @@ describe('reactivity/reactive', () => {
|
||||||
expect(() => markRaw(obj)).not.toThrowError()
|
expect(() => markRaw(obj)).not.toThrowError()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not markRaw object as reactive', () => {
|
||||||
|
const a = reactive({ a: 1 })
|
||||||
|
const b = reactive({ b: 2 }) as any
|
||||||
|
b.a = markRaw(toRaw(a))
|
||||||
|
expect(b.a === a).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
test('should not observe non-extensible objects', () => {
|
test('should not observe non-extensible objects', () => {
|
||||||
const obj = reactive({
|
const obj = reactive({
|
||||||
foo: Object.preventExtensions({ a: 1 }),
|
foo: Object.preventExtensions({ a: 1 }),
|
||||||
|
@ -419,4 +426,17 @@ describe('reactivity/reactive', () => {
|
||||||
map.set(void 0, 1)
|
map.set(void 0, 1)
|
||||||
expect(c.value).toBe(1)
|
expect(c.value).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should return true for reactive objects', () => {
|
||||||
|
expect(isReactive(reactive({}))).toBe(true)
|
||||||
|
expect(isReactive(readonly(reactive({})))).toBe(true)
|
||||||
|
expect(isReactive(ref({}).value)).toBe(true)
|
||||||
|
expect(isReactive(readonly(ref({})).value)).toBe(true)
|
||||||
|
expect(isReactive(shallowReactive({}))).toBe(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should return false for non-reactive objects', () => {
|
||||||
|
expect(isReactive(ref(true))).toBe(false)
|
||||||
|
expect(isReactive(shallowRef({}).value)).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -277,4 +277,16 @@ describe('watch', () => {
|
||||||
|
|
||||||
expect(dummy).toEqual([1, 2, 3])
|
expect(dummy).toEqual([1, 2, 3])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('watch with immediate reset and sync flush', () => {
|
||||||
|
const value = ref(false)
|
||||||
|
|
||||||
|
watch(value, () => {
|
||||||
|
value.value = false
|
||||||
|
})
|
||||||
|
|
||||||
|
value.value = true
|
||||||
|
value.value = true
|
||||||
|
expect(value.value).toBe(false)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.5.12",
|
"version": "3.5.14",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
@ -53,6 +53,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
get(target: Target, key: string | symbol, receiver: object): any {
|
get(target: Target, key: string | symbol, receiver: object): any {
|
||||||
|
if (key === ReactiveFlags.SKIP) return target[ReactiveFlags.SKIP]
|
||||||
|
|
||||||
const isReadonly = this._isReadonly,
|
const isReadonly = this._isReadonly,
|
||||||
isShallow = this._isShallow
|
isShallow = this._isShallow
|
||||||
if (key === ReactiveFlags.IS_REACTIVE) {
|
if (key === ReactiveFlags.IS_REACTIVE) {
|
||||||
|
|
|
@ -49,6 +49,7 @@ export enum EffectFlags {
|
||||||
DIRTY = 1 << 4,
|
DIRTY = 1 << 4,
|
||||||
ALLOW_RECURSE = 1 << 5,
|
ALLOW_RECURSE = 1 << 5,
|
||||||
PAUSED = 1 << 6,
|
PAUSED = 1 << 6,
|
||||||
|
EVALUATED = 1 << 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -377,22 +378,22 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
|
||||||
}
|
}
|
||||||
computed.globalVersion = globalVersion
|
computed.globalVersion = globalVersion
|
||||||
|
|
||||||
const dep = computed.dep
|
|
||||||
computed.flags |= EffectFlags.RUNNING
|
|
||||||
// In SSR there will be no render effect, so the computed has no subscriber
|
// In SSR there will be no render effect, so the computed has no subscriber
|
||||||
// and therefore tracks no deps, thus we cannot rely on the dirty check.
|
// and therefore tracks no deps, thus we cannot rely on the dirty check.
|
||||||
// Instead, computed always re-evaluate and relies on the globalVersion
|
// Instead, computed always re-evaluate and relies on the globalVersion
|
||||||
// fast path above for caching.
|
// fast path above for caching.
|
||||||
|
// #12337 if computed has no deps (does not rely on any reactive data) and evaluated,
|
||||||
|
// there is no need to re-evaluate.
|
||||||
if (
|
if (
|
||||||
dep.version > 0 &&
|
|
||||||
!computed.isSSR &&
|
!computed.isSSR &&
|
||||||
computed.deps &&
|
computed.flags & EffectFlags.EVALUATED &&
|
||||||
!isDirty(computed)
|
((!computed.deps && !(computed as any)._dirty) || !isDirty(computed))
|
||||||
) {
|
) {
|
||||||
computed.flags &= ~EffectFlags.RUNNING
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
computed.flags |= EffectFlags.RUNNING
|
||||||
|
|
||||||
|
const dep = computed.dep
|
||||||
const prevSub = activeSub
|
const prevSub = activeSub
|
||||||
const prevShouldTrack = shouldTrack
|
const prevShouldTrack = shouldTrack
|
||||||
activeSub = computed
|
activeSub = computed
|
||||||
|
@ -402,6 +403,7 @@ export function refreshComputed(computed: ComputedRefImpl): undefined {
|
||||||
prepareDeps(computed)
|
prepareDeps(computed)
|
||||||
const value = computed.fn(computed._value)
|
const value = computed.fn(computed._value)
|
||||||
if (dep.version === 0 || hasChanged(value, computed._value)) {
|
if (dep.version === 0 || hasChanged(value, computed._value)) {
|
||||||
|
computed.flags |= EffectFlags.EVALUATED
|
||||||
computed._value = value
|
computed._value = value
|
||||||
dep.version++
|
dep.version++
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,10 @@ export class EffectScope {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
private _active = true
|
private _active = true
|
||||||
|
/**
|
||||||
|
* @internal track `on` calls, allow `on` call multiple times
|
||||||
|
*/
|
||||||
|
private _on = 0
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
@ -99,12 +103,16 @@ export class EffectScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prevScope: EffectScope | undefined
|
||||||
/**
|
/**
|
||||||
* This should only be called on non-detached scopes
|
* This should only be called on non-detached scopes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
on(): void {
|
on(): void {
|
||||||
activeEffectScope = this
|
if (++this._on === 1) {
|
||||||
|
this.prevScope = activeEffectScope
|
||||||
|
activeEffectScope = this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -112,23 +120,33 @@ export class EffectScope {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
off(): void {
|
off(): void {
|
||||||
activeEffectScope = this.parent
|
if (this._on > 0 && --this._on === 0) {
|
||||||
|
activeEffectScope = this.prevScope
|
||||||
|
this.prevScope = undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop(fromParent?: boolean): void {
|
stop(fromParent?: boolean): void {
|
||||||
if (this._active) {
|
if (this._active) {
|
||||||
|
this._active = false
|
||||||
let i, l
|
let i, l
|
||||||
for (i = 0, l = this.effects.length; i < l; i++) {
|
for (i = 0, l = this.effects.length; i < l; i++) {
|
||||||
this.effects[i].stop()
|
this.effects[i].stop()
|
||||||
}
|
}
|
||||||
|
this.effects.length = 0
|
||||||
|
|
||||||
for (i = 0, l = this.cleanups.length; i < l; i++) {
|
for (i = 0, l = this.cleanups.length; i < l; i++) {
|
||||||
this.cleanups[i]()
|
this.cleanups[i]()
|
||||||
}
|
}
|
||||||
|
this.cleanups.length = 0
|
||||||
|
|
||||||
if (this.scopes) {
|
if (this.scopes) {
|
||||||
for (i = 0, l = this.scopes.length; i < l; i++) {
|
for (i = 0, l = this.scopes.length; i < l; i++) {
|
||||||
this.scopes[i].stop(true)
|
this.scopes[i].stop(true)
|
||||||
}
|
}
|
||||||
|
this.scopes.length = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// nested scope, dereference from parent to avoid memory leaks
|
// nested scope, dereference from parent to avoid memory leaks
|
||||||
if (!this.detached && this.parent && !fromParent) {
|
if (!this.detached && this.parent && !fromParent) {
|
||||||
// optimized O(1) removal
|
// optimized O(1) removal
|
||||||
|
@ -139,7 +157,6 @@ export class EffectScope {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.parent = undefined
|
this.parent = undefined
|
||||||
this._active = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -108,9 +108,9 @@ export declare const ShallowReactiveMarker: unique symbol
|
||||||
export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true }
|
export type ShallowReactive<T> = T & { [ShallowReactiveMarker]?: true }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shallow version of {@link reactive()}.
|
* Shallow version of {@link reactive}.
|
||||||
*
|
*
|
||||||
* Unlike {@link reactive()}, there is no deep conversion: only root-level
|
* Unlike {@link reactive}, there is no deep conversion: only root-level
|
||||||
* properties are reactive for a shallow reactive object. Property values are
|
* properties are reactive for a shallow reactive object. Property values are
|
||||||
* stored and exposed as-is - this also means properties with ref values will
|
* stored and exposed as-is - this also means properties with ref values will
|
||||||
* not be automatically unwrapped.
|
* not be automatically unwrapped.
|
||||||
|
@ -178,7 +178,7 @@ export type DeepReadonly<T> = T extends Builtin
|
||||||
* the original.
|
* the original.
|
||||||
*
|
*
|
||||||
* A readonly proxy is deep: any nested property accessed will be readonly as
|
* A readonly proxy is deep: any nested property accessed will be readonly as
|
||||||
* well. It also has the same ref-unwrapping behavior as {@link reactive()},
|
* well. It also has the same ref-unwrapping behavior as {@link reactive},
|
||||||
* except the unwrapped values will also be made readonly.
|
* except the unwrapped values will also be made readonly.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
|
@ -215,9 +215,9 @@ export function readonly<T extends object>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shallow version of {@link readonly()}.
|
* Shallow version of {@link readonly}.
|
||||||
*
|
*
|
||||||
* Unlike {@link readonly()}, there is no deep conversion: only root-level
|
* Unlike {@link readonly}, there is no deep conversion: only root-level
|
||||||
* properties are made readonly. Property values are stored and exposed as-is -
|
* properties are made readonly. Property values are stored and exposed as-is -
|
||||||
* this also means properties with ref values will not be automatically
|
* this also means properties with ref values will not be automatically
|
||||||
* unwrapped.
|
* unwrapped.
|
||||||
|
@ -279,16 +279,16 @@ function createReactiveObject(
|
||||||
) {
|
) {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
// target already has corresponding Proxy
|
|
||||||
const existingProxy = proxyMap.get(target)
|
|
||||||
if (existingProxy) {
|
|
||||||
return existingProxy
|
|
||||||
}
|
|
||||||
// only specific value types can be observed.
|
// only specific value types can be observed.
|
||||||
const targetType = getTargetType(target)
|
const targetType = getTargetType(target)
|
||||||
if (targetType === TargetType.INVALID) {
|
if (targetType === TargetType.INVALID) {
|
||||||
return target
|
return target
|
||||||
}
|
}
|
||||||
|
// target already has corresponding Proxy
|
||||||
|
const existingProxy = proxyMap.get(target)
|
||||||
|
if (existingProxy) {
|
||||||
|
return existingProxy
|
||||||
|
}
|
||||||
const proxy = new Proxy(
|
const proxy = new Proxy(
|
||||||
target,
|
target,
|
||||||
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
|
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers,
|
||||||
|
@ -298,8 +298,8 @@ function createReactiveObject(
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an object is a proxy created by {@link reactive()} or
|
* Checks if an object is a proxy created by {@link reactive} or
|
||||||
* {@link shallowReactive()} (or {@link ref()} in some cases).
|
* {@link shallowReactive} (or {@link ref} in some cases).
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```js
|
* ```js
|
||||||
|
@ -327,7 +327,7 @@ export function isReactive(value: unknown): boolean {
|
||||||
* readonly object can change, but they can't be assigned directly via the
|
* readonly object can change, but they can't be assigned directly via the
|
||||||
* passed object.
|
* passed object.
|
||||||
*
|
*
|
||||||
* The proxies created by {@link readonly()} and {@link shallowReadonly()} are
|
* The proxies created by {@link readonly} and {@link shallowReadonly} are
|
||||||
* both considered readonly, as is a computed ref without a set function.
|
* both considered readonly, as is a computed ref without a set function.
|
||||||
*
|
*
|
||||||
* @param value - The value to check.
|
* @param value - The value to check.
|
||||||
|
@ -343,7 +343,7 @@ export function isShallow(value: unknown): boolean {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if an object is a proxy created by {@link reactive},
|
* Checks if an object is a proxy created by {@link reactive},
|
||||||
* {@link readonly}, {@link shallowReactive} or {@link shallowReadonly()}.
|
* {@link readonly}, {@link shallowReactive} or {@link shallowReadonly}.
|
||||||
*
|
*
|
||||||
* @param value - The value to check.
|
* @param value - The value to check.
|
||||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isproxy}
|
* @see {@link https://vuejs.org/api/reactivity-utilities.html#isproxy}
|
||||||
|
@ -356,8 +356,8 @@ export function isProxy(value: any): boolean {
|
||||||
* Returns the raw, original object of a Vue-created proxy.
|
* Returns the raw, original object of a Vue-created proxy.
|
||||||
*
|
*
|
||||||
* `toRaw()` can return the original object from proxies created by
|
* `toRaw()` can return the original object from proxies created by
|
||||||
* {@link reactive()}, {@link readonly()}, {@link shallowReactive()} or
|
* {@link reactive}, {@link readonly}, {@link shallowReactive} or
|
||||||
* {@link shallowReadonly()}.
|
* {@link shallowReadonly}.
|
||||||
*
|
*
|
||||||
* This is an escape hatch that can be used to temporarily read without
|
* This is an escape hatch that can be used to temporarily read without
|
||||||
* incurring proxy access / tracking overhead or write without triggering
|
* incurring proxy access / tracking overhead or write without triggering
|
||||||
|
@ -397,7 +397,7 @@ export type Raw<T> = T & { [RawSymbol]?: true }
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* **Warning:** `markRaw()` together with the shallow APIs such as
|
* **Warning:** `markRaw()` together with the shallow APIs such as
|
||||||
* {@link shallowReactive()} allow you to selectively opt-out of the default
|
* {@link shallowReactive} allow you to selectively opt-out of the default
|
||||||
* deep reactive/readonly conversion and embed raw, non-proxied objects in your
|
* deep reactive/readonly conversion and embed raw, non-proxied objects in your
|
||||||
* state graph.
|
* state graph.
|
||||||
*
|
*
|
||||||
|
|
|
@ -67,7 +67,7 @@ export type ShallowRef<T = any, S = T> = Ref<T, S> & {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shallow version of {@link ref()}.
|
* Shallow version of {@link ref}.
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* ```js
|
* ```js
|
||||||
|
@ -229,7 +229,7 @@ export function unref<T>(ref: MaybeRef<T> | ComputedRef<T>): T {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Normalizes values / refs / getters to values.
|
* Normalizes values / refs / getters to values.
|
||||||
* This is similar to {@link unref()}, except that it also normalizes getters.
|
* This is similar to {@link unref}, except that it also normalizes getters.
|
||||||
* If the argument is a getter, it will be invoked and its return value will
|
* If the argument is a getter, it will be invoked and its return value will
|
||||||
* be returned.
|
* be returned.
|
||||||
*
|
*
|
||||||
|
@ -331,7 +331,7 @@ export type ToRefs<T = any> = {
|
||||||
/**
|
/**
|
||||||
* Converts a reactive object to a plain object where each property of the
|
* Converts a reactive object to a plain object where each property of the
|
||||||
* resulting object is a ref pointing to the corresponding property of the
|
* resulting object is a ref pointing to the corresponding property of the
|
||||||
* original object. Each individual ref is created using {@link toRef()}.
|
* original object. Each individual ref is created using {@link toRef}.
|
||||||
*
|
*
|
||||||
* @param object - Reactive object to be made into an object of linked refs.
|
* @param object - Reactive object to be made into an object of linked refs.
|
||||||
* @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs}
|
* @see {@link https://vuejs.org/api/reactivity-utilities.html#torefs}
|
||||||
|
|
|
@ -213,7 +213,7 @@ export function watch(
|
||||||
const scope = getCurrentScope()
|
const scope = getCurrentScope()
|
||||||
const watchHandle: WatchHandle = () => {
|
const watchHandle: WatchHandle = () => {
|
||||||
effect.stop()
|
effect.stop()
|
||||||
if (scope) {
|
if (scope && scope.active) {
|
||||||
remove(scope.effects, effect)
|
remove(scope.effects, effect)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -273,11 +273,11 @@ export function watch(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
oldValue = newValue
|
||||||
call
|
call
|
||||||
? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
|
? call(cb!, WatchErrorCodes.WATCH_CALLBACK, args)
|
||||||
: // @ts-expect-error
|
: // @ts-expect-error
|
||||||
cb!(...args)
|
cb!(...args)
|
||||||
oldValue = newValue
|
|
||||||
} finally {
|
} finally {
|
||||||
activeWatcher = currentWatcher
|
activeWatcher = currentWatcher
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,13 +25,13 @@ import {
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import {
|
import {
|
||||||
type DebuggerEvent,
|
type DebuggerEvent,
|
||||||
EffectFlags,
|
|
||||||
ITERATE_KEY,
|
ITERATE_KEY,
|
||||||
type Ref,
|
type Ref,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
TrackOpTypes,
|
TrackOpTypes,
|
||||||
TriggerOpTypes,
|
TriggerOpTypes,
|
||||||
effectScope,
|
effectScope,
|
||||||
|
onScopeDispose,
|
||||||
shallowReactive,
|
shallowReactive,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
toRef,
|
toRef,
|
||||||
|
@ -1341,7 +1341,7 @@ describe('api: watch', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
await nextTick()
|
await nextTick()
|
||||||
|
|
||||||
expect(instance!.scope.effects[0].flags & EffectFlags.ACTIVE).toBeFalsy()
|
expect(instance!.scope.effects.length).toBe(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
|
test('this.$watch should pass `this.proxy` to watch source as the first argument ', () => {
|
||||||
|
@ -1983,4 +1983,31 @@ describe('api: watch', () => {
|
||||||
expect(spy1).toHaveBeenCalled()
|
expect(spy1).toHaveBeenCalled()
|
||||||
expect(spy2).toHaveBeenCalled()
|
expect(spy2).toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12631
|
||||||
|
test('this.$watch w/ onScopeDispose', () => {
|
||||||
|
const onCleanup = vi.fn()
|
||||||
|
const toggle = ref(true)
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
render() {},
|
||||||
|
created(this: any) {
|
||||||
|
this.$watch(
|
||||||
|
() => 1,
|
||||||
|
function () {},
|
||||||
|
)
|
||||||
|
onScopeDispose(onCleanup)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const App = defineComponent({
|
||||||
|
render() {
|
||||||
|
return toggle.value ? h(Comp) : null
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
createApp(App).mount(root)
|
||||||
|
expect(onCleanup).toBeCalledTimes(0)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -324,4 +324,98 @@ describe('component: slots', () => {
|
||||||
'Slot "default" invoked outside of the render function',
|
'Slot "default" invoked outside of the render function',
|
||||||
).not.toHaveBeenWarned()
|
).not.toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('basic warn', () => {
|
||||||
|
const Comp = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
slots.default && slots.default()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Comp, () => h('div'))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('basic warn when mounting another app in setup', () => {
|
||||||
|
const Comp = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
slots.default?.()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mountComp = () => {
|
||||||
|
createApp({
|
||||||
|
setup() {
|
||||||
|
return () => h(Comp, () => 'msg')
|
||||||
|
},
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
mountComp()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not warn when render in setup', () => {
|
||||||
|
const container = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
return () => slots.default && slots.default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = h(container, null, () => h('div'))
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
render(h(comp), nodeOps.createElement('div'))
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('basic warn when render in setup', () => {
|
||||||
|
const container = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
slots.default && slots.default()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = h(container, null, () => h('div'))
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
render(h(comp), nodeOps.createElement('div'))
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1198,4 +1198,51 @@ describe('BaseTransition', () => {
|
||||||
test('should not error on KeepAlive w/ function children', () => {
|
test('should not error on KeepAlive w/ function children', () => {
|
||||||
expect(() => mount({}, () => () => h('div'), true)).not.toThrow()
|
expect(() => mount({}, () => () => h('div'), true)).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12465
|
||||||
|
test('mode: "out-in" w/ KeepAlive + fallthrough attrs (prod mode)', async () => {
|
||||||
|
__DEV__ = false
|
||||||
|
async function testOutIn({ trueBranch, falseBranch }: ToggleOptions) {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const { props, cbs } = mockProps({ mode: 'out-in' }, true)
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(
|
||||||
|
BaseTransition,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
class: 'test',
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
h(KeepAlive, null, toggle.value ? trueBranch() : falseBranch()),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toBe(`<div class="test">0</div>`)
|
||||||
|
|
||||||
|
// trigger toggle
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(props.onBeforeLeave).toHaveBeenCalledTimes(1)
|
||||||
|
expect(serialize((props.onBeforeLeave as any).mock.calls[0][0])).toBe(
|
||||||
|
`<div class="test">0</div>`,
|
||||||
|
)
|
||||||
|
expect(props.onLeave).toHaveBeenCalledTimes(1)
|
||||||
|
expect(serialize((props.onLeave as any).mock.calls[0][0])).toBe(
|
||||||
|
`<div class="test">0</div>`,
|
||||||
|
)
|
||||||
|
expect(props.onAfterLeave).not.toHaveBeenCalled()
|
||||||
|
// enter should not have started
|
||||||
|
expect(props.onBeforeEnter).not.toHaveBeenCalled()
|
||||||
|
expect(props.onEnter).not.toHaveBeenCalled()
|
||||||
|
expect(props.onAfterEnter).not.toHaveBeenCalled()
|
||||||
|
cbs.doneLeave[`<div class="test">0</div>`]()
|
||||||
|
expect(serializeInner(root)).toBe(`<span class="test">0</span>`)
|
||||||
|
}
|
||||||
|
await runTestWithKeepAlive(testOutIn)
|
||||||
|
__DEV__ = true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,14 +10,29 @@ import {
|
||||||
markRaw,
|
markRaw,
|
||||||
nextTick,
|
nextTick,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
|
onMounted,
|
||||||
h as originalH,
|
h as originalH,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
serialize,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
|
useModel,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
|
import {
|
||||||
|
Fragment,
|
||||||
|
createBlock,
|
||||||
|
createCommentVNode,
|
||||||
|
createTextVNode,
|
||||||
|
createVNode,
|
||||||
|
openBlock,
|
||||||
|
} from '../../src/vnode'
|
||||||
|
import { toDisplayString } from '@vue/shared'
|
||||||
import { compile, createApp as createDOMApp, render as domRender } from 'vue'
|
import { compile, createApp as createDOMApp, render as domRender } from 'vue'
|
||||||
|
import type { HMRRuntime } from '../../src/hmr'
|
||||||
|
|
||||||
|
declare var __VUE_HMR_RUNTIME__: HMRRuntime
|
||||||
|
const { rerender, createRecord } = __VUE_HMR_RUNTIME__
|
||||||
|
|
||||||
describe('renderer: teleport', () => {
|
describe('renderer: teleport', () => {
|
||||||
describe('eager mode', () => {
|
describe('eager mode', () => {
|
||||||
|
@ -87,6 +102,105 @@ describe('renderer: teleport', () => {
|
||||||
`</div>`,
|
`</div>`,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('update before mounted with defer', async () => {
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const foo = ref('foo')
|
||||||
|
const Header = {
|
||||||
|
props: { foo: String },
|
||||||
|
setup(props: any) {
|
||||||
|
return () => h('div', props.foo)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
const Footer = {
|
||||||
|
setup() {
|
||||||
|
foo.value = 'bar'
|
||||||
|
return () => h('div', 'Footer')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createDOMApp({
|
||||||
|
render() {
|
||||||
|
return show.value
|
||||||
|
? [
|
||||||
|
h(
|
||||||
|
Teleport,
|
||||||
|
{ to: '#targetId', defer: true },
|
||||||
|
h(Header, { foo: foo.value }),
|
||||||
|
),
|
||||||
|
h(Footer),
|
||||||
|
h('div', { id: 'targetId' }),
|
||||||
|
]
|
||||||
|
: [h('div')]
|
||||||
|
},
|
||||||
|
}).mount(root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(`"<div></div>"`)
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<!--teleport start--><!--teleport end--><div>Footer</div><div id="targetId"><div>bar</div></div>"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
// #13349
|
||||||
|
test('handle deferred teleport updates before and after mount', async () => {
|
||||||
|
const root = document.createElement('div')
|
||||||
|
document.body.appendChild(root)
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const data2 = ref('2')
|
||||||
|
const data3 = ref('3')
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
props: {
|
||||||
|
modelValue: {},
|
||||||
|
modelModifiers: {},
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
setup(props: any) {
|
||||||
|
const data2 = useModel(props, 'modelValue')
|
||||||
|
data2.value = '2+'
|
||||||
|
return () => h('span')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createDOMApp({
|
||||||
|
setup() {
|
||||||
|
setTimeout(() => (show.value = true), 5)
|
||||||
|
setTimeout(() => (data3.value = '3+'), 10)
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return h(Fragment, null, [
|
||||||
|
h('span', { id: 'targetId001' }),
|
||||||
|
show.value
|
||||||
|
? h(Fragment, null, [
|
||||||
|
h(Teleport, { to: '#targetId001', defer: true }, [
|
||||||
|
createTextVNode(String(data3.value)),
|
||||||
|
]),
|
||||||
|
h(Comp, {
|
||||||
|
modelValue: data2.value,
|
||||||
|
'onUpdate:modelValue': (event: any) =>
|
||||||
|
(data2.value = event),
|
||||||
|
}),
|
||||||
|
])
|
||||||
|
: createCommentVNode('v-if'),
|
||||||
|
])
|
||||||
|
},
|
||||||
|
}).mount(root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<span id="targetId001"></span><!--v-if-->"`,
|
||||||
|
)
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r, 10))
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<span id="targetId001">3+</span><!--teleport start--><!--teleport end--><span></span>"`,
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function runSharedTests(deferMode: boolean) {
|
function runSharedTests(deferMode: boolean) {
|
||||||
|
@ -200,6 +314,39 @@ describe('renderer: teleport', () => {
|
||||||
expect(serializeInner(target)).toBe(`teleported`)
|
expect(serializeInner(target)).toBe(`teleported`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should traverse comment node after updating in optimize mode', async () => {
|
||||||
|
const target = nodeOps.createElement('div')
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const count = ref(0)
|
||||||
|
let teleport
|
||||||
|
|
||||||
|
__DEV__ = false
|
||||||
|
render(
|
||||||
|
h(() => {
|
||||||
|
teleport =
|
||||||
|
(openBlock(),
|
||||||
|
createBlock(Teleport, { to: target }, [
|
||||||
|
createCommentVNode('comment in teleport'),
|
||||||
|
]))
|
||||||
|
return h('div', null, [
|
||||||
|
createTextVNode(toDisplayString(count.value)),
|
||||||
|
teleport,
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
root,
|
||||||
|
)
|
||||||
|
const commentNode = teleport!.children[0].el
|
||||||
|
expect(serializeInner(root)).toBe(`<div>0</div>`)
|
||||||
|
expect(serializeInner(target)).toBe(`<!--comment in teleport-->`)
|
||||||
|
expect(serialize(commentNode)).toBe(`<!--comment in teleport-->`)
|
||||||
|
|
||||||
|
count.value = 1
|
||||||
|
await nextTick()
|
||||||
|
__DEV__ = true
|
||||||
|
expect(serializeInner(root)).toBe(`<div>1</div>`)
|
||||||
|
expect(teleport!.children[0].el).toBe(commentNode)
|
||||||
|
})
|
||||||
|
|
||||||
test('should remove children when unmounted', () => {
|
test('should remove children when unmounted', () => {
|
||||||
const target = nodeOps.createElement('div')
|
const target = nodeOps.createElement('div')
|
||||||
const root = nodeOps.createElement('div')
|
const root = nodeOps.createElement('div')
|
||||||
|
@ -226,6 +373,34 @@ describe('renderer: teleport', () => {
|
||||||
testUnmount({ to: null, disabled: true })
|
testUnmount({ to: null, disabled: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10747
|
||||||
|
test('should unmount correctly when using top level comment in teleport', async () => {
|
||||||
|
const target = nodeOps.createElement('div')
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
__DEV__ = false
|
||||||
|
render(
|
||||||
|
h(() => {
|
||||||
|
return h('div', null, [
|
||||||
|
createTextVNode(toDisplayString(count.value)),
|
||||||
|
(openBlock(),
|
||||||
|
createBlock(Teleport, { to: target }, [
|
||||||
|
createCommentVNode('comment in teleport'),
|
||||||
|
])),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
root,
|
||||||
|
)
|
||||||
|
|
||||||
|
count.value = 1
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
__DEV__ = true
|
||||||
|
render(null, root)
|
||||||
|
expect(root.children.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
test('component with multi roots should be removed when unmounted', () => {
|
test('component with multi roots should be removed when unmounted', () => {
|
||||||
const target = nodeOps.createElement('div')
|
const target = nodeOps.createElement('div')
|
||||||
const root = nodeOps.createElement('div')
|
const root = nodeOps.createElement('div')
|
||||||
|
@ -698,4 +873,56 @@ describe('renderer: teleport', () => {
|
||||||
expect(tRefInMounted).toBe(target.children[1])
|
expect(tRefInMounted).toBe(target.children[1])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('handle update and hmr rerender', async () => {
|
||||||
|
const target = document.createElement('div')
|
||||||
|
const root = document.createElement('div')
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
const cls = ref('foo')
|
||||||
|
onMounted(() => {
|
||||||
|
// trigger update
|
||||||
|
cls.value = 'bar'
|
||||||
|
})
|
||||||
|
return { cls, target }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Teleport :to="target">
|
||||||
|
<div :class="cls">
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = 'test-app-id'
|
||||||
|
const App = {
|
||||||
|
__hmrId: appId,
|
||||||
|
components: { Comp },
|
||||||
|
render() {
|
||||||
|
return originalH(Comp, null, { default: () => originalH('div', 'foo') })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createRecord(appId, App)
|
||||||
|
|
||||||
|
domRender(originalH(App), root)
|
||||||
|
expect(target.innerHTML).toBe(
|
||||||
|
'<div class="foo"><div><div>foo</div></div></div>',
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(target.innerHTML).toBe(
|
||||||
|
'<div class="bar"><div><div>foo</div></div></div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
rerender(appId, () =>
|
||||||
|
originalH(Comp, null, { default: () => originalH('div', 'bar') }),
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(target.innerHTML).toBe(
|
||||||
|
'<div class="bar"><div><div>bar</div></div></div>',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { isReactive, reactive, shallowReactive } from '../../src/index'
|
import {
|
||||||
|
effect,
|
||||||
|
isReactive,
|
||||||
|
reactive,
|
||||||
|
readonly,
|
||||||
|
shallowReactive,
|
||||||
|
} from '../../src/index'
|
||||||
import { renderList } from '../../src/helpers/renderList'
|
import { renderList } from '../../src/helpers/renderList'
|
||||||
|
|
||||||
describe('renderList', () => {
|
describe('renderList', () => {
|
||||||
|
@ -65,4 +71,31 @@ describe('renderList', () => {
|
||||||
const shallowReactiveArray = shallowReactive([{ foo: 1 }])
|
const shallowReactiveArray = shallowReactive([{ foo: 1 }])
|
||||||
expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
|
expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not allow mutation', () => {
|
||||||
|
const arr = readonly(reactive([{ foo: 1 }]))
|
||||||
|
expect(
|
||||||
|
renderList(arr, item => {
|
||||||
|
;(item as any).foo = 0
|
||||||
|
return item.foo
|
||||||
|
}),
|
||||||
|
).toEqual([1])
|
||||||
|
expect(
|
||||||
|
`Set operation on key "foo" failed: target is readonly.`,
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should trigger effect for deep mutations in readonly reactive arrays', () => {
|
||||||
|
const arr = reactive([{ foo: 1 }])
|
||||||
|
const readonlyArr = readonly(arr)
|
||||||
|
|
||||||
|
let dummy
|
||||||
|
effect(() => {
|
||||||
|
dummy = renderList(readonlyArr, item => item.foo)
|
||||||
|
})
|
||||||
|
expect(dummy).toEqual([1])
|
||||||
|
|
||||||
|
arr[0].foo = 2
|
||||||
|
expect(dummy).toEqual([2])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
onMounted,
|
onMounted,
|
||||||
|
onServerPrefetch,
|
||||||
openBlock,
|
openBlock,
|
||||||
reactive,
|
reactive,
|
||||||
ref,
|
ref,
|
||||||
|
@ -31,10 +32,13 @@ import {
|
||||||
withCtx,
|
withCtx,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
|
import type { HMRRuntime } from '../src/hmr'
|
||||||
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
||||||
import { PatchFlags, normalizeStyle } from '@vue/shared'
|
import { PatchFlags, normalizeStyle } from '@vue/shared'
|
||||||
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
||||||
import { expect } from 'vitest'
|
|
||||||
|
declare var __VUE_HMR_RUNTIME__: HMRRuntime
|
||||||
|
const { createRecord, reload } = __VUE_HMR_RUNTIME__
|
||||||
|
|
||||||
function mountWithHydration(html: string, render: () => any) {
|
function mountWithHydration(html: string, render: () => any) {
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
|
@ -518,6 +522,45 @@ describe('SSR hydration', () => {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with data-allow-mismatch component when using onServerPrefetch', async () => {
|
||||||
|
const Comp = {
|
||||||
|
template: `
|
||||||
|
<div>Comp2</div>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
let foo: any
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
const flag = ref(true)
|
||||||
|
foo = () => {
|
||||||
|
flag.value = false
|
||||||
|
}
|
||||||
|
onServerPrefetch(() => (flag.value = false))
|
||||||
|
return { flag }
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
Comp,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<span data-allow-mismatch>
|
||||||
|
<Comp v-if="flag"></Comp>
|
||||||
|
</span>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
// hydrate
|
||||||
|
const container = document.createElement('div')
|
||||||
|
container.innerHTML = await renderToString(h(App))
|
||||||
|
createSSRApp(App).mount(container)
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
'<span data-allow-mismatch=""><div>Comp2</div></span>',
|
||||||
|
)
|
||||||
|
foo()
|
||||||
|
await nextTick()
|
||||||
|
expect(container.innerHTML).toBe(
|
||||||
|
'<span data-allow-mismatch=""><!--v-if--></span>',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
test('Teleport unmount (full integration)', async () => {
|
test('Teleport unmount (full integration)', async () => {
|
||||||
const Comp1 = {
|
const Comp1 = {
|
||||||
template: `
|
template: `
|
||||||
|
@ -1284,6 +1327,84 @@ describe('SSR hydration', () => {
|
||||||
resolve({})
|
resolve({})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
//#12362
|
||||||
|
test('nested async wrapper', async () => {
|
||||||
|
const Toggle = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(r => {
|
||||||
|
r(
|
||||||
|
defineComponent({
|
||||||
|
setup(_, { slots }) {
|
||||||
|
const show = ref(false)
|
||||||
|
onMounted(() => {
|
||||||
|
nextTick(() => {
|
||||||
|
show.value = true
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return () =>
|
||||||
|
withDirectives(
|
||||||
|
h('div', null, [renderSlot(slots, 'default')]),
|
||||||
|
[[vShow, show.value]],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
const Wrapper = defineAsyncComponent(() => {
|
||||||
|
return new Promise(r => {
|
||||||
|
r(
|
||||||
|
defineComponent({
|
||||||
|
render(this: any) {
|
||||||
|
return renderSlot(this.$slots, 'default')
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const count = ref(0)
|
||||||
|
const fn = vi.fn()
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onMounted(() => {
|
||||||
|
fn()
|
||||||
|
count.value++
|
||||||
|
})
|
||||||
|
return () => h('div', count.value)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(Toggle, null, {
|
||||||
|
default: () =>
|
||||||
|
h(Wrapper, null, {
|
||||||
|
default: () =>
|
||||||
|
h(Wrapper, null, {
|
||||||
|
default: () => h(Child),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
})
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.innerHTML = await renderToString(h(App))
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<div style="display:none;"><!--[--><!--[--><!--[--><div>0</div><!--]--><!--]--><!--]--></div>"`,
|
||||||
|
)
|
||||||
|
|
||||||
|
createSSRApp(App).mount(root)
|
||||||
|
await nextTick()
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toMatchInlineSnapshot(
|
||||||
|
`"<div style=""><!--[--><!--[--><!--[--><div>1</div><!--]--><!--]--><!--]--></div>"`,
|
||||||
|
)
|
||||||
|
expect(fn).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('unmount async wrapper before load (fragment)', async () => {
|
test('unmount async wrapper before load (fragment)', async () => {
|
||||||
let resolve: any
|
let resolve: any
|
||||||
const AsyncComp = defineAsyncComponent(
|
const AsyncComp = defineAsyncComponent(
|
||||||
|
@ -1533,6 +1654,29 @@ describe('SSR hydration', () => {
|
||||||
expect(`mismatch`).not.toHaveBeenWarned()
|
expect(`mismatch`).not.toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('transition appear work with pre-existing class', () => {
|
||||||
|
const { vnode, container } = mountWithHydration(
|
||||||
|
`<template><div class="foo">foo</div></template>`,
|
||||||
|
() =>
|
||||||
|
h(
|
||||||
|
Transition,
|
||||||
|
{ appear: true },
|
||||||
|
{
|
||||||
|
default: () => h('div', { class: 'foo' }, 'foo'),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||||
|
<div
|
||||||
|
class="foo v-enter-from v-enter-active"
|
||||||
|
>
|
||||||
|
foo
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
expect(vnode.el).toBe(container.firstChild)
|
||||||
|
expect(`mismatch`).not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
test('transition appear with v-if', () => {
|
test('transition appear with v-if', () => {
|
||||||
const show = false
|
const show = false
|
||||||
const { vnode, container } = mountWithHydration(
|
const { vnode, container } = mountWithHydration(
|
||||||
|
@ -1725,6 +1869,60 @@ describe('SSR hydration', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('hmr reload child wrapped in KeepAlive', async () => {
|
||||||
|
const id = 'child-reload'
|
||||||
|
const Child = {
|
||||||
|
__hmrId: id,
|
||||||
|
template: `<div>foo</div>`,
|
||||||
|
}
|
||||||
|
createRecord(id, Child)
|
||||||
|
|
||||||
|
const appId = 'test-app-id'
|
||||||
|
const App = {
|
||||||
|
__hmrId: appId,
|
||||||
|
components: { Child },
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<KeepAlive>
|
||||||
|
<Child />
|
||||||
|
</KeepAlive>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.innerHTML = await renderToString(h(App))
|
||||||
|
createSSRApp(App).mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<div><div>foo</div></div>')
|
||||||
|
|
||||||
|
reload(id, {
|
||||||
|
__hmrId: id,
|
||||||
|
template: `<div>bar</div>`,
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toBe('<div><div>bar</div></div>')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hmr root reload', async () => {
|
||||||
|
const appId = 'test-app-id'
|
||||||
|
const App = {
|
||||||
|
__hmrId: appId,
|
||||||
|
template: `<div>foo</div>`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.innerHTML = await renderToString(h(App))
|
||||||
|
createSSRApp(App).mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<div>foo</div>')
|
||||||
|
|
||||||
|
reload(appId, {
|
||||||
|
__hmrId: appId,
|
||||||
|
template: `<div>bar</div>`,
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toBe('<div>bar</div>')
|
||||||
|
})
|
||||||
|
|
||||||
describe('mismatch handling', () => {
|
describe('mismatch handling', () => {
|
||||||
test('text node', () => {
|
test('text node', () => {
|
||||||
const { container } = mountWithHydration(`foo`, () => 'bar')
|
const { container } = mountWithHydration(`foo`, () => 'bar')
|
||||||
|
|
|
@ -17,6 +17,7 @@ import {
|
||||||
serializeInner as inner,
|
serializeInner as inner,
|
||||||
nextTick,
|
nextTick,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
|
onBeforeMount,
|
||||||
onBeforeUnmount,
|
onBeforeUnmount,
|
||||||
onUnmounted,
|
onUnmounted,
|
||||||
openBlock,
|
openBlock,
|
||||||
|
@ -860,6 +861,114 @@ describe('renderer: optimized mode', () => {
|
||||||
expect(inner(root)).toBe('<div><div>true</div></div>')
|
expect(inner(root)).toBe('<div><div>true</div></div>')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #13305
|
||||||
|
test('patch Suspense nested in list nodes in optimized mode', async () => {
|
||||||
|
const deps: Promise<any>[] = []
|
||||||
|
|
||||||
|
const Item = {
|
||||||
|
props: {
|
||||||
|
someId: { type: Number, required: true },
|
||||||
|
},
|
||||||
|
async setup(props: any) {
|
||||||
|
const p = new Promise(resolve => setTimeout(resolve, 1))
|
||||||
|
deps.push(p)
|
||||||
|
|
||||||
|
await p
|
||||||
|
return () => (
|
||||||
|
openBlock(),
|
||||||
|
createElementBlock('li', null, [
|
||||||
|
createElementVNode(
|
||||||
|
'p',
|
||||||
|
null,
|
||||||
|
String(props.someId),
|
||||||
|
PatchFlags.TEXT,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = ref([1, 2, 3])
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
return () => (
|
||||||
|
openBlock(),
|
||||||
|
createElementBlock(
|
||||||
|
Fragment,
|
||||||
|
null,
|
||||||
|
[
|
||||||
|
createElementVNode(
|
||||||
|
'p',
|
||||||
|
null,
|
||||||
|
JSON.stringify(list.value),
|
||||||
|
PatchFlags.TEXT,
|
||||||
|
),
|
||||||
|
createElementVNode('ol', null, [
|
||||||
|
(openBlock(),
|
||||||
|
createBlock(SuspenseImpl, null, {
|
||||||
|
fallback: withCtx(() => [
|
||||||
|
createElementVNode('li', null, 'Loading…'),
|
||||||
|
]),
|
||||||
|
default: withCtx(() => [
|
||||||
|
(openBlock(true),
|
||||||
|
createElementBlock(
|
||||||
|
Fragment,
|
||||||
|
null,
|
||||||
|
renderList(list.value, id => {
|
||||||
|
return (
|
||||||
|
openBlock(),
|
||||||
|
createBlock(
|
||||||
|
Item,
|
||||||
|
{
|
||||||
|
key: id,
|
||||||
|
'some-id': id,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
PatchFlags.PROPS,
|
||||||
|
['some-id'],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}),
|
||||||
|
PatchFlags.KEYED_FRAGMENT,
|
||||||
|
)),
|
||||||
|
]),
|
||||||
|
_: 1 /* STABLE */,
|
||||||
|
})),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
PatchFlags.STABLE_FRAGMENT,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
app.mount(root)
|
||||||
|
expect(inner(root)).toBe(`<p>[1,2,3]</p>` + `<ol><li>Loading…</li></ol>`)
|
||||||
|
|
||||||
|
await Promise.all(deps)
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toBe(
|
||||||
|
`<p>[1,2,3]</p>` +
|
||||||
|
`<ol>` +
|
||||||
|
`<li><p>1</p></li>` +
|
||||||
|
`<li><p>2</p></li>` +
|
||||||
|
`<li><p>3</p></li>` +
|
||||||
|
`</ol>`,
|
||||||
|
)
|
||||||
|
|
||||||
|
list.value = [3, 1, 2]
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toBe(
|
||||||
|
`<p>[3,1,2]</p>` +
|
||||||
|
`<ol>` +
|
||||||
|
`<li><p>3</p></li>` +
|
||||||
|
`<li><p>1</p></li>` +
|
||||||
|
`<li><p>2</p></li>` +
|
||||||
|
`</ol>`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
// #4183
|
// #4183
|
||||||
test('should not take unmount children fast path /w Suspense', async () => {
|
test('should not take unmount children fast path /w Suspense', async () => {
|
||||||
const show = ref(true)
|
const show = ref(true)
|
||||||
|
@ -1199,7 +1308,7 @@ describe('renderer: optimized mode', () => {
|
||||||
createBlock('div', null, [
|
createBlock('div', null, [
|
||||||
createVNode('div', null, [
|
createVNode('div', null, [
|
||||||
cache[0] ||
|
cache[0] ||
|
||||||
(setBlockTracking(-1),
|
(setBlockTracking(-1, true),
|
||||||
((cache[0] = createVNode('div', null, [
|
((cache[0] = createVNode('div', null, [
|
||||||
createVNode(Child),
|
createVNode(Child),
|
||||||
])).cacheIndex = 0),
|
])).cacheIndex = 0),
|
||||||
|
@ -1233,4 +1342,64 @@ describe('renderer: optimized mode', () => {
|
||||||
expect(inner(root)).toBe('<!--v-if-->')
|
expect(inner(root)).toBe('<!--v-if-->')
|
||||||
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
expect(spyUnmounted).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12371
|
||||||
|
test('unmount children when the user calls a compiled slot', async () => {
|
||||||
|
const beforeMountSpy = vi.fn()
|
||||||
|
const beforeUnmountSpy = vi.fn()
|
||||||
|
|
||||||
|
const Child = {
|
||||||
|
setup() {
|
||||||
|
onBeforeMount(beforeMountSpy)
|
||||||
|
onBeforeUnmount(beforeUnmountSpy)
|
||||||
|
return () => 'child'
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const Wrapper = {
|
||||||
|
setup(_: any, { slots }: SetupContext) {
|
||||||
|
return () => (
|
||||||
|
openBlock(),
|
||||||
|
createElementBlock('section', null, [
|
||||||
|
(openBlock(),
|
||||||
|
createElementBlock('div', { key: 1 }, [
|
||||||
|
createTextVNode(slots.header!() ? 'foo' : 'bar', 1 /* TEXT */),
|
||||||
|
renderSlot(slots, 'content'),
|
||||||
|
])),
|
||||||
|
])
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const show = ref(false)
|
||||||
|
const app = createApp({
|
||||||
|
render() {
|
||||||
|
return show.value
|
||||||
|
? (openBlock(),
|
||||||
|
createBlock(Wrapper, null, {
|
||||||
|
header: withCtx(() => [createVNode({})]),
|
||||||
|
content: withCtx(() => [createVNode(Child)]),
|
||||||
|
_: 1,
|
||||||
|
}))
|
||||||
|
: createCommentVNode('v-if', true)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
app.mount(root)
|
||||||
|
expect(inner(root)).toMatchInlineSnapshot(`"<!--v-if-->"`)
|
||||||
|
expect(beforeMountSpy).toHaveBeenCalledTimes(0)
|
||||||
|
expect(beforeUnmountSpy).toHaveBeenCalledTimes(0)
|
||||||
|
|
||||||
|
show.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toMatchInlineSnapshot(
|
||||||
|
`"<section><div>foochild</div></section>"`,
|
||||||
|
)
|
||||||
|
expect(beforeMountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
|
||||||
|
show.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(inner(root)).toBe('<!--v-if-->')
|
||||||
|
expect(beforeUnmountSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
import {
|
import {
|
||||||
|
KeepAlive,
|
||||||
|
defineAsyncComponent,
|
||||||
defineComponent,
|
defineComponent,
|
||||||
h,
|
h,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
@ -538,4 +540,68 @@ describe('api: template refs', () => {
|
||||||
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
|
'<div><div>[object Object],[object Object]</div><ul><li>2</li><li>3</li></ul></div>',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('with async component which nested in KeepAlive', async () => {
|
||||||
|
const AsyncComp = defineAsyncComponent(
|
||||||
|
() =>
|
||||||
|
new Promise(resolve =>
|
||||||
|
setTimeout(() =>
|
||||||
|
resolve(
|
||||||
|
defineComponent({
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
name: 'AsyncComp',
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
}) as any,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
const Comp = defineComponent({
|
||||||
|
setup(_, { expose }) {
|
||||||
|
expose({
|
||||||
|
name: 'Comp',
|
||||||
|
})
|
||||||
|
return () => h('div')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const toggle = ref(false)
|
||||||
|
const instanceRef = ref<any>(null)
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
render: () => {
|
||||||
|
return h(KeepAlive, () =>
|
||||||
|
toggle.value
|
||||||
|
? h(AsyncComp, { ref: instanceRef })
|
||||||
|
: h(Comp, { ref: instanceRef }),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
render(h(App), root)
|
||||||
|
expect(instanceRef.value.name).toBe('Comp')
|
||||||
|
|
||||||
|
// switch to async component
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value).toBe(null)
|
||||||
|
|
||||||
|
await new Promise(r => setTimeout(r))
|
||||||
|
expect(instanceRef.value.name).toBe('AsyncComp')
|
||||||
|
|
||||||
|
// switch back to normal component
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value.name).toBe('Comp')
|
||||||
|
|
||||||
|
// switch to async component again
|
||||||
|
toggle.value = true
|
||||||
|
await nextTick()
|
||||||
|
expect(instanceRef.value.name).toBe('AsyncComp')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue