mirror of https://github.com/vuejs/core.git
Merge branch 'main' into 11521-warn-about-incorrect-usage
This commit is contained in:
commit
9759be0451
|
@ -1,18 +1,17 @@
|
|||
{
|
||||
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
|
||||
extends: ['config:base', 'schedule:weekly', 'group:allNonMajor'],
|
||||
extends: ['config:recommended', 'schedule:weekly', 'group:allNonMajor'],
|
||||
labels: ['dependencies'],
|
||||
ignorePaths: ['**/__tests__/**'],
|
||||
rangeStrategy: 'bump',
|
||||
packageRules: [
|
||||
{
|
||||
depTypeList: ['peerDependencies'],
|
||||
matchDepTypes: ['peerDependencies'],
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
groupName: 'test',
|
||||
matchPackageNames: ['vitest', 'jsdom', 'puppeteer'],
|
||||
matchPackagePrefixes: ['@vitest'],
|
||||
matchPackageNames: ['vitest', 'jsdom', 'puppeteer', '@vitest{/,}**'],
|
||||
},
|
||||
{
|
||||
groupName: 'playground',
|
||||
|
@ -23,18 +22,28 @@
|
|||
},
|
||||
{
|
||||
groupName: 'compiler',
|
||||
matchPackageNames: ['magic-string'],
|
||||
matchPackagePrefixes: ['@babel', 'postcss'],
|
||||
matchPackageNames: ['magic-string', '@babel{/,}**', 'postcss{/,}**'],
|
||||
},
|
||||
{
|
||||
groupName: 'build',
|
||||
matchPackageNames: ['vite', '@swc/core'],
|
||||
matchPackagePrefixes: ['rollup', 'esbuild', '@rollup', '@vitejs'],
|
||||
matchPackageNames: [
|
||||
'vite',
|
||||
'@swc/core',
|
||||
'rollup{/,}**',
|
||||
'esbuild{/,}**',
|
||||
'@rollup{/,}**',
|
||||
'@vitejs{/,}**',
|
||||
],
|
||||
},
|
||||
{
|
||||
groupName: 'lint',
|
||||
matchPackageNames: ['simple-git-hooks', 'lint-staged'],
|
||||
matchPackagePrefixes: ['typescript-eslint', 'eslint', 'prettier'],
|
||||
matchPackageNames: [
|
||||
'simple-git-hooks',
|
||||
'lint-staged',
|
||||
'typescript-eslint{/,}**',
|
||||
'eslint{/,}**',
|
||||
'prettier{/,}**',
|
||||
],
|
||||
},
|
||||
],
|
||||
ignoreDeps: [
|
||||
|
|
|
@ -16,6 +16,7 @@ jobs:
|
|||
uses: ./.github/workflows/test.yml
|
||||
|
||||
continuous-release:
|
||||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
|
|
@ -18,6 +18,7 @@ env:
|
|||
|
||||
jobs:
|
||||
upload:
|
||||
if: github.repository == 'vuejs/core'
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
|
|
@ -18,6 +18,7 @@ jobs:
|
|||
size-report:
|
||||
runs-on: ubuntu-latest
|
||||
if: >
|
||||
github.repository == 'vuejs/core' &&
|
||||
github.event.workflow_run.event == 'pull_request' &&
|
||||
github.event.workflow_run.conclusion == 'success'
|
||||
steps:
|
||||
|
@ -65,7 +66,7 @@ jobs:
|
|||
if_no_artifact_found: warn
|
||||
|
||||
- name: Prepare report
|
||||
run: pnpm tsx scripts/size-report.ts > size-report.md
|
||||
run: node scripts/size-report.js > size-report.md
|
||||
|
||||
- name: Read Size Report
|
||||
id: size-report
|
||||
|
|
|
@ -13,5 +13,6 @@
|
|||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
}
|
||||
},
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
https://vuejs.org/funding.json
|
313
CHANGELOG.md
313
CHANGELOG.md
|
@ -1,3 +1,316 @@
|
|||
## [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)
|
||||
* **reactiivty:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
|
||||
* **reactivity:** 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)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-dom:** avoid stringify option with null value ([#12096](https://github.com/vuejs/core/issues/12096)) ([f6d9926](https://github.com/vuejs/core/commit/f6d99262364b7444ebab8742158599e8cdd79eaa)), closes [#12093](https://github.com/vuejs/core/issues/12093)
|
||||
* **compiler-sfc:** do not skip TSInstantiationExpression when transforming props destructure ([#12064](https://github.com/vuejs/core/issues/12064)) ([d3ecde8](https://github.com/vuejs/core/commit/d3ecde8a696ff62c8d0ab067fd1d7ee0565b63c5))
|
||||
* **compiler-sfc:** use sass modern api if available and avoid deprecation warning ([#11992](https://github.com/vuejs/core/issues/11992)) ([4474c11](https://github.com/vuejs/core/commit/4474c113d1fb1c26298dd6794275d5b5c7cc4d93))
|
||||
* **compiler:** clone loc to `ifNode` ([#12131](https://github.com/vuejs/core/issues/12131)) ([cde2c06](https://github.com/vuejs/core/commit/cde2c0671b00d4f6111fcbd7aa76e45872f20b0c)), closes [vuejs/language-tools#4911](https://github.com/vuejs/language-tools/issues/4911)
|
||||
* **custom-element:** properly remove hyphenated attribute ([#12143](https://github.com/vuejs/core/issues/12143)) ([e16e9a7](https://github.com/vuejs/core/commit/e16e9a7341e7cfb3c443da4e5e5b06e8158712c3)), closes [#12139](https://github.com/vuejs/core/issues/12139)
|
||||
* **defineModel:** handle kebab-case model correctly ([#12063](https://github.com/vuejs/core/issues/12063)) ([c0418a3](https://github.com/vuejs/core/commit/c0418a3b8fa96a0b108ab71b7aab5d3388f90557)), closes [#12060](https://github.com/vuejs/core/issues/12060)
|
||||
* **deps:** update dependency monaco-editor to ^0.52.0 ([#12119](https://github.com/vuejs/core/issues/12119)) ([f7cbea2](https://github.com/vuejs/core/commit/f7cbea2111c7770a180b640f36f6a5d4d6abc698))
|
||||
* **hydration:** provide compat fallback for idle callback hydration strategy ([#11935](https://github.com/vuejs/core/issues/11935)) ([1ae545a](https://github.com/vuejs/core/commit/1ae545a3786abef983be1c969726489685569c92))
|
||||
* **reactivity:** trigger reactivity for Map key `undefined` ([#12055](https://github.com/vuejs/core/issues/12055)) ([7ad289e](https://github.com/vuejs/core/commit/7ad289e1e7fea654524008ff91e43a8b8a55ef22)), closes [#12054](https://github.com/vuejs/core/issues/12054)
|
||||
* **runtime-core:** allow symbol values for slot prop key ([#12069](https://github.com/vuejs/core/issues/12069)) ([d9d4d4e](https://github.com/vuejs/core/commit/d9d4d4e158cd51a9ddda249f29de8467f60b2792)), closes [#12068](https://github.com/vuejs/core/issues/12068)
|
||||
* **runtime-core:** fix required prop check false positive for kebab-case edge cases ([#12034](https://github.com/vuejs/core/issues/12034)) ([9da1ac1](https://github.com/vuejs/core/commit/9da1ac156552ac449754e1373aac7e349841becb)), closes [#12011](https://github.com/vuejs/core/issues/12011)
|
||||
* **runtime-dom:** prevent unnecessary updates in v-model checkbox when value is unchanged ([#12146](https://github.com/vuejs/core/issues/12146)) ([ea943af](https://github.com/vuejs/core/commit/ea943afe404c4ca4b729906c5e8daf7aa2ccde9b)), closes [#12144](https://github.com/vuejs/core/issues/12144)
|
||||
* **teleport:** handle disabled teleport with updateCssVars ([#12113](https://github.com/vuejs/core/issues/12113)) ([76a8223](https://github.com/vuejs/core/commit/76a8223199c148b79a5c0ea19e235164809760cd)), closes [#12112](https://github.com/vuejs/core/issues/12112)
|
||||
* **transition/ssr:** make transition appear work with Suspense in SSR ([#12047](https://github.com/vuejs/core/issues/12047)) ([f1a4f67](https://github.com/vuejs/core/commit/f1a4f67aedfe83e440c54222213f070774faa421)), closes [#12046](https://github.com/vuejs/core/issues/12046)
|
||||
* **types:** ensure `this.$props` type does not include `string` ([#12123](https://github.com/vuejs/core/issues/12123)) ([704173e](https://github.com/vuejs/core/commit/704173e24276706de672cca6c9507e4dd9651197)), closes [#12122](https://github.com/vuejs/core/issues/12122)
|
||||
* **types:** retain union type narrowing with defaults applied ([#12108](https://github.com/vuejs/core/issues/12108)) ([05685a9](https://github.com/vuejs/core/commit/05685a9d7c42d4cd37169b867833776b91154fed)), closes [#12106](https://github.com/vuejs/core/issues/12106)
|
||||
* **useId:** ensure useId consistency when using serverPrefetch ([#12128](https://github.com/vuejs/core/issues/12128)) ([b4d3534](https://github.com/vuejs/core/commit/b4d35349d8bc39aa15bd3f1094d230e5928b177c)), closes [#12102](https://github.com/vuejs/core/issues/12102)
|
||||
* **watch:** watchEffect clean-up with SSR ([#12097](https://github.com/vuejs/core/issues/12097)) ([b094c72](https://github.com/vuejs/core/commit/b094c72b3d40c52c7124f145a9db028509a11202)), closes [#11956](https://github.com/vuejs/core/issues/11956)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **reactivity:** avoid unnecessary recursion in removeSub ([#12135](https://github.com/vuejs/core/issues/12135)) ([ec917cf](https://github.com/vuejs/core/commit/ec917cfdb9d0169cd0835d3a0e28244242657dc9))
|
||||
|
||||
|
||||
|
||||
## [3.5.11](https://github.com/vuejs/core/compare/v3.5.10...v3.5.11) (2024-10-03)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-sfc:** do not skip `TSSatisfiesExpression` when transforming props destructure ([#12062](https://github.com/vuejs/core/issues/12062)) ([2328b05](https://github.com/vuejs/core/commit/2328b051f4efa1f1394b7d4e73b7c3f76e430e7c)), closes [#12061](https://github.com/vuejs/core/issues/12061)
|
||||
* **reactivity:** prevent overwriting `next` property during batch processing ([#12075](https://github.com/vuejs/core/issues/12075)) ([d3f5e6e](https://github.com/vuejs/core/commit/d3f5e6e5319b4ffaa55ca9a2ea3d95d78e76fa58)), closes [#12072](https://github.com/vuejs/core/issues/12072)
|
||||
* **scheduler:** job ordering when the post queue is flushing ([#12090](https://github.com/vuejs/core/issues/12090)) ([577edca](https://github.com/vuejs/core/commit/577edca8e7795436efd710d1c289ea8ea2642b0e))
|
||||
* **types:** correctly infer `TypeProps` when it is `any` ([#12073](https://github.com/vuejs/core/issues/12073)) ([57315ab](https://github.com/vuejs/core/commit/57315ab9688c9741a271d1075bbd28cbe5f71e2f)), closes [#12058](https://github.com/vuejs/core/issues/12058)
|
||||
* **types:** should not intersect `PublicProps` with `Props` ([#12077](https://github.com/vuejs/core/issues/12077)) ([6f85894](https://github.com/vuejs/core/commit/6f8589437635706f825ccec51800effba1d2bf5f))
|
||||
* **types:** infer the first generic type of `Ref` correctly ([#12094](https://github.com/vuejs/core/issues/12094)) ([c97bb84](https://github.com/vuejs/core/commit/c97bb84d0b0a16b012f886b6498e924415ed63e5))
|
||||
|
||||
|
||||
|
||||
## [3.5.10](https://github.com/vuejs/core/compare/v3.5.9...v3.5.10) (2024-09-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **custom-element:** properly set kebab-case props on Vue custom elements ([ea3efa0](https://github.com/vuejs/core/commit/ea3efa09e008918c1d9ba7226833a8b1a7a57244)), closes [#12030](https://github.com/vuejs/core/issues/12030) [#12032](https://github.com/vuejs/core/issues/12032)
|
||||
* **reactivity:** fix nested batch edge case ([93c95dd](https://github.com/vuejs/core/commit/93c95dd4cd416503f43a98a1455f62658d22b0b2))
|
||||
* **reactivity:** only clear notified flags for computed in first batch iteration ([aa9ef23](https://github.com/vuejs/core/commit/aa9ef2386a0cd39a174e5a887ec2b1a3525034fc)), closes [#12045](https://github.com/vuejs/core/issues/12045)
|
||||
* **types/ref:** handle nested refs in UnwrapRef ([#12049](https://github.com/vuejs/core/issues/12049)) ([e2c19c2](https://github.com/vuejs/core/commit/e2c19c20cfee9788519a80c0e53e216b78505994)), closes [#12044](https://github.com/vuejs/core/issues/12044)
|
||||
|
||||
|
||||
|
||||
## [3.5.9](https://github.com/vuejs/core/compare/v3.5.8...v3.5.9) (2024-09-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **reactivity:** fix property dep removal regression ([6001e5c](https://github.com/vuejs/core/commit/6001e5c81a05c894586f9287fbd991677bdd0455)), closes [#12020](https://github.com/vuejs/core/issues/12020) [#12021](https://github.com/vuejs/core/issues/12021)
|
||||
* **reactivity:** fix recursive sync watcher on computed edge case ([10ff159](https://github.com/vuejs/core/commit/10ff15924053d9bd95ad706f78ce09e288213fcf)), closes [#12033](https://github.com/vuejs/core/issues/12033) [#12037](https://github.com/vuejs/core/issues/12037)
|
||||
* **runtime-core:** avoid rendering plain object as VNode ([#12038](https://github.com/vuejs/core/issues/12038)) ([cb34b28](https://github.com/vuejs/core/commit/cb34b28a4a9bf868be4785b001c526163eda342e)), closes [#12035](https://github.com/vuejs/core/issues/12035) [vitejs/vite-plugin-vue#353](https://github.com/vitejs/vite-plugin-vue/issues/353)
|
||||
* **runtime-core:** make useId() always return a string ([a177092](https://github.com/vuejs/core/commit/a177092754642af2f98c33a4feffe8f198c3c950))
|
||||
* **types:** correct type inference of union event names ([#12022](https://github.com/vuejs/core/issues/12022)) ([4da6881](https://github.com/vuejs/core/commit/4da688141d9e7c15b622c289deaa81b11845b2c7))
|
||||
* **vue:** properly cache runtime compilation ([#12019](https://github.com/vuejs/core/issues/12019)) ([fa0ba24](https://github.com/vuejs/core/commit/fa0ba24b3ace02d7ecab65e57c2bea89a2550dcb))
|
||||
|
||||
|
||||
|
||||
## [3.5.8](https://github.com/vuejs/core/compare/v3.5.7...v3.5.8) (2024-09-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **reactivity:** do not remove dep from depsMap when cleaning up deps of computed ([#11995](https://github.com/vuejs/core/issues/11995)) ([0267a58](https://github.com/vuejs/core/commit/0267a588017eee4951ac2a877fe1ccae84cad905))
|
||||
|
||||
|
||||
|
||||
## [3.5.7](https://github.com/vuejs/core/compare/v3.5.6...v3.5.7) (2024-09-20)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compile-core:** fix v-model with newlines edge case ([#11960](https://github.com/vuejs/core/issues/11960)) ([6224288](https://github.com/vuejs/core/commit/62242886d705ece88dbcad45bb78072ecccad0ca)), closes [#8306](https://github.com/vuejs/core/issues/8306)
|
||||
* **compiler-sfc:** initialize scope with null prototype object ([#11963](https://github.com/vuejs/core/issues/11963)) ([215e154](https://github.com/vuejs/core/commit/215e15407294bf667261360218f975b88c99c2e5))
|
||||
* **hydration:** avoid observing non-Element node ([#11954](https://github.com/vuejs/core/issues/11954)) ([7257e6a](https://github.com/vuejs/core/commit/7257e6a34200409b3fc347d3bb807e11e2785974)), closes [#11952](https://github.com/vuejs/core/issues/11952)
|
||||
* **reactivity:** do not remove dep from depsMap when unsubbed by computed ([960706e](https://github.com/vuejs/core/commit/960706eebf73f08ebc9d5dd853a05def05e2c153))
|
||||
* **reactivity:** fix dev-only memory leak by updating dep.subsHead on sub removal ([5c8b76e](https://github.com/vuejs/core/commit/5c8b76ed6cfbbcee4cbaac0b72beab7291044e4f)), closes [#11956](https://github.com/vuejs/core/issues/11956)
|
||||
* **reactivity:** fix memory leak from dep instances of garbage collected objects ([235ea47](https://github.com/vuejs/core/commit/235ea4772ed2972914cf142da8b7ac1fb04f7585)), closes [#11979](https://github.com/vuejs/core/issues/11979) [#11971](https://github.com/vuejs/core/issues/11971)
|
||||
* **reactivity:** fix triggerRef call on ObjectRefImpl returned by toRef ([#11986](https://github.com/vuejs/core/issues/11986)) ([b030c8b](https://github.com/vuejs/core/commit/b030c8bc7327877efb98aa3d9a58eb287a6ff07a)), closes [#11982](https://github.com/vuejs/core/issues/11982)
|
||||
* **scheduler:** ensure recursive jobs can't be queued twice ([#11955](https://github.com/vuejs/core/issues/11955)) ([d18d6aa](https://github.com/vuejs/core/commit/d18d6aa1b20dc57a8103c51ec4d61e8e53ed936d))
|
||||
* **ssr:** don't render comments in TransitionGroup ([#11961](https://github.com/vuejs/core/issues/11961)) ([a2f6ede](https://github.com/vuejs/core/commit/a2f6edeb02faedbb673c4bc5c6a59d9a79a37d07)), closes [#11958](https://github.com/vuejs/core/issues/11958)
|
||||
* **transition:** respect `duration` setting even when it is `0` ([#11967](https://github.com/vuejs/core/issues/11967)) ([f927a4a](https://github.com/vuejs/core/commit/f927a4ae6f7c453f70ba89498ee0c737dc9866fd))
|
||||
* **types:** correct type inference of all-optional props ([#11644](https://github.com/vuejs/core/issues/11644)) ([9eca65e](https://github.com/vuejs/core/commit/9eca65ee9871d1ac878755afa9a3eb1b02030350)), closes [#11733](https://github.com/vuejs/core/issues/11733) [vuejs/language-tools#4704](https://github.com/vuejs/language-tools/issues/4704)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **hydration:** avoid observer if element is in viewport ([#11639](https://github.com/vuejs/core/issues/11639)) ([e075dfa](https://github.com/vuejs/core/commit/e075dfad5c7649c6045e3711687ec888e7aa1a39))
|
||||
|
||||
|
||||
|
||||
## [3.5.6](https://github.com/vuejs/core/compare/v3.5.5...v3.5.6) (2024-09-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compile-dom:** should be able to stringify mathML ([#11891](https://github.com/vuejs/core/issues/11891)) ([85c138c](https://github.com/vuejs/core/commit/85c138ced108268f7656b568dfd3036a1e0aae34))
|
||||
* **compiler-sfc:** preserve old behavior when using withDefaults with desutructure ([8492c3c](https://github.com/vuejs/core/commit/8492c3c49a922363d6c77ef192c133a8fbce6514)), closes [#11930](https://github.com/vuejs/core/issues/11930)
|
||||
* **reactivity:** avoid exponential perf cost and reduce call stack depth for deeply chained computeds ([#11944](https://github.com/vuejs/core/issues/11944)) ([c74bb8c](https://github.com/vuejs/core/commit/c74bb8c2dd9e82aaabb0a2a2b368e900929b513b)), closes [#11928](https://github.com/vuejs/core/issues/11928)
|
||||
* **reactivity:** rely on dirty check only when computed has deps ([#11931](https://github.com/vuejs/core/issues/11931)) ([aa5dafd](https://github.com/vuejs/core/commit/aa5dafd2b55d42d6a29316a3bc91aea85c676a0b)), closes [#11929](https://github.com/vuejs/core/issues/11929)
|
||||
* **watch:** `once` option should be ignored by watchEffect ([#11884](https://github.com/vuejs/core/issues/11884)) ([49fa673](https://github.com/vuejs/core/commit/49fa673493d93b77ddba2165ab6545bae84fd1ae))
|
||||
* **watch:** unwatch should be callable during SSR ([#11925](https://github.com/vuejs/core/issues/11925)) ([2d6adf7](https://github.com/vuejs/core/commit/2d6adf78a047eed091db277ffbd9df0822fb0bdd)), closes [#11924](https://github.com/vuejs/core/issues/11924)
|
||||
|
||||
|
||||
|
||||
## [3.5.5](https://github.com/vuejs/core/compare/v3.5.4...v3.5.5) (2024-09-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** fix handling of delimiterOpen in VPre ([#11915](https://github.com/vuejs/core/issues/11915)) ([706d4ac](https://github.com/vuejs/core/commit/706d4ac1d0210b2d9134b3228280187fe02fc971)), closes [#11913](https://github.com/vuejs/core/issues/11913)
|
||||
* **compiler-dom:** fix stringify static edge for partially eligible chunks in cached parent ([1d99d61](https://github.com/vuejs/core/commit/1d99d61c1bd77f9ea6743f6214a82add8346a121)), closes [#11879](https://github.com/vuejs/core/issues/11879) [#11890](https://github.com/vuejs/core/issues/11890)
|
||||
* **compiler-dom:** should ignore leading newline in <textarea> per spec ([3c4bf76](https://github.com/vuejs/core/commit/3c4bf7627649ec1e3220f8c4e4163c20d2afb367))
|
||||
* **compiler-sfc:** nested css supports atrule and comment ([#11899](https://github.com/vuejs/core/issues/11899)) ([0e7bc71](https://github.com/vuejs/core/commit/0e7bc717e6640644f062957ec5031506f0dab215)), closes [#11896](https://github.com/vuejs/core/issues/11896)
|
||||
* **custom-element:** handle nested customElement mount w/ shadowRoot false ([#11861](https://github.com/vuejs/core/issues/11861)) ([f2d8019](https://github.com/vuejs/core/commit/f2d801918841e7673ff3f048d0d895592a2f7e23)), closes [#11851](https://github.com/vuejs/core/issues/11851) [#11871](https://github.com/vuejs/core/issues/11871)
|
||||
* **hmr:** reload async child wrapped in Suspense + KeepAlive ([#11907](https://github.com/vuejs/core/issues/11907)) ([10a2c60](https://github.com/vuejs/core/commit/10a2c6053bd30d160d0214bb3566f540187e6874)), closes [#11868](https://github.com/vuejs/core/issues/11868)
|
||||
* **hydration:** fix mismatch of leading newline in `<textarea>` and `<pre>` ([a5f3c2e](https://github.com/vuejs/core/commit/a5f3c2eb4d2e7fae93ff93ce865b269f01cc825e)), closes [#11873](https://github.com/vuejs/core/issues/11873) [#11874](https://github.com/vuejs/core/issues/11874)
|
||||
* **reactivity:** properly clean up deps, fix memory leak ([8ea5d6d](https://github.com/vuejs/core/commit/8ea5d6d6981ab7febda0be43c3c92b18869c3a2a)), closes [#11901](https://github.com/vuejs/core/issues/11901)
|
||||
* **runtime-core:** properly update async component nested in KeepAlive ([#11917](https://github.com/vuejs/core/issues/11917)) ([7fe6c79](https://github.com/vuejs/core/commit/7fe6c795a1fc7ddcea5ad91a56141561192373ac)), closes [#11916](https://github.com/vuejs/core/issues/11916)
|
||||
* **TransitionGroup:** not warn unkeyed text children with whitespece preserve ([#11888](https://github.com/vuejs/core/issues/11888)) ([7571f20](https://github.com/vuejs/core/commit/7571f20bc3d1854377a146f41d211e05bb68cd47)), closes [#11885](https://github.com/vuejs/core/issues/11885)
|
||||
|
||||
|
||||
|
||||
## [3.5.4](https://github.com/vuejs/core/compare/v3.5.3...v3.5.4) (2024-09-10)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-sfc:** correct scoped injection for nesting selector ([#11854](https://github.com/vuejs/core/issues/11854)) ([b1de75e](https://github.com/vuejs/core/commit/b1de75ed04626b6423085dfde91fb0cb481a25e8)), closes [#10567](https://github.com/vuejs/core/issues/10567)
|
||||
* **reactivity:** fix markRaw error on already marked object ([#11864](https://github.com/vuejs/core/issues/11864)) ([67d6596](https://github.com/vuejs/core/commit/67d6596d40b1807b9cd8eb0d9282932ea77be3c0)), closes [#11862](https://github.com/vuejs/core/issues/11862)
|
||||
* Revert "fix: Revert "fix(reactivity): self-referencing computed should refresh"" ([e596378](https://github.com/vuejs/core/commit/e596378e0be728dad7d60938449f3fa557ca2ec9))
|
||||
* **runtime-core:** handle shallow reactive arrays in renderList correctly ([#11870](https://github.com/vuejs/core/issues/11870)) ([ced59ab](https://github.com/vuejs/core/commit/ced59ab8f2f2e89c13119bab3a0c25a1a1f1c3d6)), closes [#11869](https://github.com/vuejs/core/issues/11869)
|
||||
* **types:** correctly infer `TypeEmits` with both tuple and function syntax ([#11840](https://github.com/vuejs/core/issues/11840)) ([dad6738](https://github.com/vuejs/core/commit/dad673809929c084dcb8e42640eb7daa675d4ea4)), closes [#11836](https://github.com/vuejs/core/issues/11836)
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* **reactivity:** trigger deps directly instead of storing in an array first ([#11695](https://github.com/vuejs/core/issues/11695)) ([f80d447](https://github.com/vuejs/core/commit/f80d447c17662556e9e3f99f6d199967f4c8cf3d))
|
||||
|
||||
|
||||
|
||||
## [3.5.3](https://github.com/vuejs/core/compare/v3.5.2...v3.5.3) (2024-09-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **hydration:** check __asyncHydrate presence for vue3-lazy-hydration compat ([#11825](https://github.com/vuejs/core/issues/11825)) ([8e6c337](https://github.com/vuejs/core/commit/8e6c3378676be05cea7f53664442acdfb86784f9)), closes [#11793](https://github.com/vuejs/core/issues/11793)
|
||||
* Revert "fix(reactivity): self-referencing computed should refresh" ([35c760f](https://github.com/vuejs/core/commit/35c760f82f749f7c6e3f9bfead8221ce498e892f))
|
||||
* **ssr:** respect app.config.warnHandler during ssr ([bf3d9a2](https://github.com/vuejs/core/commit/bf3d9a2af41659a743706306fc798b3d215df5af)), closes [#11830](https://github.com/vuejs/core/issues/11830)
|
||||
* **Transition:** handle KeepAlive child unmount in Transition out-in mode ([#11833](https://github.com/vuejs/core/issues/11833)) ([6b7901d](https://github.com/vuejs/core/commit/6b7901d28ed3a6a9242c666cc1b8e3c0b0b0fe62)), closes [#11775](https://github.com/vuejs/core/issues/11775)
|
||||
* **useId:** make generated IDs selector compatible ([babfb4c](https://github.com/vuejs/core/commit/babfb4cbcbf98601d76c1d7653eae8d250ce2710)), closes [#11828](https://github.com/vuejs/core/issues/11828)
|
||||
|
||||
|
||||
|
||||
## [3.5.2](https://github.com/vuejs/core/compare/v3.5.1...v3.5.2) (2024-09-05)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **reactivity:** make toRaw work on proxies created by proxyRef ([46c3ab1](https://github.com/vuejs/core/commit/46c3ab1d714024894fa1d33e495d5d35c7817d4d))
|
||||
* **reactivity:** pass oldValue to computed getter ([#11813](https://github.com/vuejs/core/issues/11813)) ([98864a7](https://github.com/vuejs/core/commit/98864a7ef5c8080c407166c8221488a4eacbbc81)), closes [#11812](https://github.com/vuejs/core/issues/11812)
|
||||
* **reactivity:** prevent endless recursion in computed getters ([#11797](https://github.com/vuejs/core/issues/11797)) ([716275d](https://github.com/vuejs/core/commit/716275d1b1d2383d8ef0306fcd94558d4d9170f2))
|
||||
* **reactivity:** self-referencing computed should refresh ([e84c4a6](https://github.com/vuejs/core/commit/e84c4a608e9dc96fb2a4a29d538bcc64f26103a2)), closes [/github.com/vuejs/core/pull/11797#issuecomment-2330738633](https://github.com//github.com/vuejs/core/pull/11797/issues/issuecomment-2330738633)
|
||||
* **scheduler:** prevent duplicate jobs being queued ([#11826](https://github.com/vuejs/core/issues/11826)) ([df56cc5](https://github.com/vuejs/core/commit/df56cc528793b1d6131a1e64095dd5cb95c56bee)), closes [#11712](https://github.com/vuejs/core/issues/11712) [#11807](https://github.com/vuejs/core/issues/11807)
|
||||
* **suspense:** avoid updating anchor if activeBranch has not been rendered to the actual container ([#11818](https://github.com/vuejs/core/issues/11818)) ([3c0d531](https://github.com/vuejs/core/commit/3c0d531fa7fe762bfe46fbe63f318adc95221795)), closes [#11806](https://github.com/vuejs/core/issues/11806)
|
||||
* **Transition:** handle KeepAlive child unmount in Transition out-in mode ([#11778](https://github.com/vuejs/core/issues/11778)) ([3116553](https://github.com/vuejs/core/commit/311655352931863dfcf520b8cf29cebc5b7e1e00)), closes [#11775](https://github.com/vuejs/core/issues/11775)
|
||||
* **types:** add HTMLDialogElement missing close event ([#11811](https://github.com/vuejs/core/issues/11811)) ([3634f7a](https://github.com/vuejs/core/commit/3634f7a4c1649ad2e7e969eb4512512868c61d01))
|
||||
* **types:** added name attribute support to details tag ([#11823](https://github.com/vuejs/core/issues/11823)) ([c74176e](https://github.com/vuejs/core/commit/c74176ec7b4d1d34159ce21d600c04b157ac5549)), closes [#11821](https://github.com/vuejs/core/issues/11821)
|
||||
* **types:** fix defineComponent props inference when setup() has explicit annotation ([fca20a3](https://github.com/vuejs/core/commit/fca20a39aa4a6f98c8f972bd435ebb7dc535648a)), closes [#11803](https://github.com/vuejs/core/issues/11803)
|
||||
* **useTemplateRef:** properly fix readonly warning in dev and ensure prod behavior consistency ([9b7797d](https://github.com/vuejs/core/commit/9b7797d0d1fc773e979e042673d5b9b3151c40fc)), closes [#11808](https://github.com/vuejs/core/issues/11808) [#11816](https://github.com/vuejs/core/issues/11816) [#11810](https://github.com/vuejs/core/issues/11810)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** parse modifiers as expression to provide location data ([#11819](https://github.com/vuejs/core/issues/11819)) ([3f13203](https://github.com/vuejs/core/commit/3f13203564164eeb2945bdc0b9ef755c37477d75))
|
||||
|
||||
|
||||
|
||||
## [3.5.1](https://github.com/vuejs/core/compare/v3.5.0...v3.5.1) (2024-09-04)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** improve built-in components treeshakability ([4eee630](https://github.com/vuejs/core/commit/4eee630b3122a10d0baf9b91358cfffa92d6fd81))
|
||||
* **reactivity:** handle non-array arguments in reactive `concat` method ([#11794](https://github.com/vuejs/core/issues/11794)) ([475977a](https://github.com/vuejs/core/commit/475977a6f76b77392610e0a3ec2b0e076d1e1d59)), closes [#11792](https://github.com/vuejs/core/issues/11792)
|
||||
* **Transition:** avoid applying transition hooks on comment vnode ([#11788](https://github.com/vuejs/core/issues/11788)) ([51912f8](https://github.com/vuejs/core/commit/51912f8a02e35f172f6d30ed7a2f3a92c1407cf9)), closes [#11782](https://github.com/vuejs/core/issues/11782)
|
||||
* **types:** avoid using intersection type in `Readonly<...>` to fix JSDoc emit ([#11799](https://github.com/vuejs/core/issues/11799)) ([7518bc1](https://github.com/vuejs/core/commit/7518bc19dc73ba46dcf1eef6e23f9e6e75552675))
|
||||
* **useTemplateRef:** fix readonly warning when useTemplateRef has same variable name as template ref ([bc63df0](https://github.com/vuejs/core/commit/bc63df01992fdbf0b6749ad234153725697ed896)), closes [#11795](https://github.com/vuejs/core/issues/11795) [#11802](https://github.com/vuejs/core/issues/11802) [#11804](https://github.com/vuejs/core/issues/11804)
|
||||
|
||||
|
||||
|
||||
# [3.5.0](https://github.com/vuejs/core/compare/v3.5.0-rc.1...v3.5.0) (2024-09-03)
|
||||
|
||||
## Aggregated Features List for 3.5 (alpha to stable)
|
||||
|
||||
### Reactivity
|
||||
|
||||
- **reactivity**: Refactor reactivity system to use version counting and doubly-linked list tracking ([#10397](https://github.com/vuejs/core/pull/10397)) ([05eb4e0](https://github.com/vuejs/core/commit/05eb4e0fefd585125dd60b7f8fe9c36928d921aa))
|
||||
- **reactivity**: Optimize array tracking ([#9511](https://github.com/vuejs/core/pull/9511)) ([70196a4](https://github.com/vuejs/core/commit/70196a40cc078f50fcc1110c38c06fbcc70b205e))
|
||||
- **compiler-sfc:** enable reactive props destructure by default ([d2dac0e](https://github.com/vuejs/core/commit/d2dac0e359c47d1ed0aa77eda488e76fd6466d2d))
|
||||
- **reactivity:** `onEffectCleanup` API ([2cc5615](https://github.com/vuejs/core/commit/2cc5615590de77126e8df46136de0240dbde5004)), closes [#10173](https://github.com/vuejs/core/issues/10173)
|
||||
- **reactivity:** add `failSilently` argument for `onScopeDispose` ([9a936aa](https://github.com/vuejs/core/commit/9a936aaec489c79433a32791ecf5ddb1739a62bd))
|
||||
- **reactivity/watch:** base `watch`, `getCurrentWatcher`, and `onWatcherCleanup` ([#9927](https://github.com/vuejs/core/issues/9927)) ([205e5b5](https://github.com/vuejs/core/commit/205e5b5e277243c3af2c937d9bd46cf671296b72))
|
||||
- **reactivity/watch:** add pause/resume for ReactiveEffect, EffectScope, and WatchHandle ([#9651](https://github.com/vuejs/core/issues/9651)) ([267093c](https://github.com/vuejs/core/commit/267093c31490050bfcf3ff2b30a2aefee2dad582))
|
||||
- **watch:** support passing number to `deep` option to control the watch depth ([#9572](https://github.com/vuejs/core/issues/9572)) ([22f7d96](https://github.com/vuejs/core/commit/22f7d96757956ebe0baafe52256aa327908cc51c))
|
||||
- **types:** export `MultiWatchSources` type ([#9563](https://github.com/vuejs/core/issues/9563)) ([998dca5](https://github.com/vuejs/core/commit/998dca59f140420280803233f41707580688562c))
|
||||
- **types:** allow computed getter and setter types to be unrelated ([#11472](https://github.com/vuejs/core/issues/11472)) ([a01675e](https://github.com/vuejs/core/commit/a01675ef8f99b5acd6832c53051f4415b18609f2)), closes [#7271](https://github.com/vuejs/core/issues/7271)
|
||||
|
||||
### SSR
|
||||
|
||||
- **runtime-core:** `useId()` and `app.config.idPrefix` ([#11404](https://github.com/vuejs/core/issues/11404)) ([73ef156](https://github.com/vuejs/core/commit/73ef1561f6905d69f968c094d0180c61824f1247))
|
||||
- **hydration:** lazy hydration strategies for async components ([#11458](https://github.com/vuejs/core/issues/11458)) ([d14a11c](https://github.com/vuejs/core/commit/d14a11c1cdcee88452f17ce97758743c863958f4))
|
||||
- **hydration:** support suppressing hydration mismatch via data-allow-mismatch ([94fb2b8](https://github.com/vuejs/core/commit/94fb2b8106a66bcca1a3f922a246a29fdd1274b1))
|
||||
|
||||
### Custom Element
|
||||
|
||||
- **custom-element:** `useHost()` helper ([775103a](https://github.com/vuejs/core/commit/775103af37df69d34c79f12c4c1776c47d07f0a0))
|
||||
- **custom-element:** `useShadowRoot()` helper ([5a1a89b](https://github.com/vuejs/core/commit/5a1a89bd6178cc2f84ba91da7d72aee4c6ec1282)), closes [#6113](https://github.com/vuejs/core/issues/6113) [#8195](https://github.com/vuejs/core/issues/8195)
|
||||
- **custom-element:** expose `this.$host` in Options API ([1ef8f46](https://github.com/vuejs/core/commit/1ef8f46af0cfdec2fed66376772409e0aa25ad50))
|
||||
- **custom-element:** inject child components styles to custom element shadow root ([#11517](https://github.com/vuejs/core/issues/11517)) ([56c76a8](https://github.com/vuejs/core/commit/56c76a8b05c45f782ed3a16ec77c6292b71a17f1)), closes [#4662](https://github.com/vuejs/core/issues/4662) [#7941](https://github.com/vuejs/core/issues/7941) [#7942](https://github.com/vuejs/core/issues/7942)
|
||||
- **custom-element:** support configurable app instance in defineCustomElement ([6758c3c](https://github.com/vuejs/core/commit/6758c3cd0427f97394d95168c655dae3b7fa62cd)), closes [#4356](https://github.com/vuejs/core/issues/4356) [#4635](https://github.com/vuejs/core/issues/4635)
|
||||
- **custom-element:** support css `:host` selector by applying css vars on host element ([#8830](https://github.com/vuejs/core/issues/8830)) ([03a9ea2](https://github.com/vuejs/core/commit/03a9ea2b88df0842a820e09f7445c4b9189e3fcb)), closes [#8826](https://github.com/vuejs/core/issues/8826)
|
||||
- **custom-element:** support emit with options ([e181bff](https://github.com/vuejs/core/commit/e181bff6dc39d5cef92000c10291243c7d6e4d08)), closes [#7605](https://github.com/vuejs/core/issues/7605)
|
||||
- **custom-element:** support expose on customElement ([#6256](https://github.com/vuejs/core/issues/6256)) ([af838c1](https://github.com/vuejs/core/commit/af838c1b5ec23552e52e64ffa7db0eb0246c3624)), closes [#5540](https://github.com/vuejs/core/issues/5540)
|
||||
- **custom-element:** support `nonce` option for injected style tags ([bb4a02a](https://github.com/vuejs/core/commit/bb4a02a70c30e739a3c705b3d96d09258d7d7ded)), closes [#6530](https://github.com/vuejs/core/issues/6530)
|
||||
- **custom-element:** support passing custom-element-specific options via 2nd argument of defineCustomElement ([60a88a2](https://github.com/vuejs/core/commit/60a88a2b129714186cf6ba66f30f31d733d0311e))
|
||||
- **custom-element:** support `shadowRoot: false` in `defineCustomElement()` ([37d2ce5](https://github.com/vuejs/core/commit/37d2ce5d8e0fac4a00064f02b05f91f69b2d5d5e)), closes [#4314](https://github.com/vuejs/core/issues/4314) [#4404](https://github.com/vuejs/core/issues/4404)
|
||||
|
||||
### Teleport
|
||||
|
||||
- **teleport:** support deferred Teleport ([#11387](https://github.com/vuejs/core/issues/11387)) ([59a3e88](https://github.com/vuejs/core/commit/59a3e88903b10ac2278170a44d5a03f24fef23ef)), closes [#2015](https://github.com/vuejs/core/issues/2015) [#11386](https://github.com/vuejs/core/issues/11386)
|
||||
- **teleport/transition:** support directly nesting Teleport inside Transition ([#6548](https://github.com/vuejs/core/issues/6548)) ([0e6e3c7](https://github.com/vuejs/core/commit/0e6e3c7eb0e5320b7c1818e025cb4a490fede9c0)), closes [#5836](https://github.com/vuejs/core/issues/5836)
|
||||
|
||||
### Misc
|
||||
|
||||
- **runtime-core:** `useTemplateRef()` ([3ba70e4](https://github.com/vuejs/core/commit/3ba70e49b5856c53611c314d4855d679a546a7df))
|
||||
- **runtime-core:** add `app.onUnmount()` for registering cleanup functions ([#4619](https://github.com/vuejs/core/issues/4619)) ([582a3a3](https://github.com/vuejs/core/commit/582a3a382b1adda565bac576b913a88d9e8d7a9e)), closes [#4516](https://github.com/vuejs/core/issues/4516)
|
||||
- **runtime-core:** add `app.config.throwUnhandledErrorInProduction` ([f476b7f](https://github.com/vuejs/core/commit/f476b7f030f2dd427ca655fcea36f4933a4b4da0)), closes [#7876](https://github.com/vuejs/core/issues/7876)
|
||||
- **runtime-dom:** Trusted Types compatibility ([#10844](https://github.com/vuejs/core/issues/10844)) ([6d4eb94](https://github.com/vuejs/core/commit/6d4eb94853ed1b2b1675bdd7d5ba9c75cc6daed5))
|
||||
- **compiler-core:** support `Symbol` global in template expressions ([#9069](https://github.com/vuejs/core/issues/9069)) ([a501a85](https://github.com/vuejs/core/commit/a501a85a7c910868e01a5c70a2abea4e9d9e87f3))
|
||||
- **types:** export more emit related types ([#11017](https://github.com/vuejs/core/issues/11017)) ([189573d](https://github.com/vuejs/core/commit/189573dcee2a16bd3ed36ff5589d43f535e5e733))
|
||||
* **types:** add loading prop to iframe ([#11767](https://github.com/vuejs/core/issues/11767)) ([d86fe0e](https://github.com/vuejs/core/commit/d86fe0ec002901dc359a0e85f3a421b4a8538d68))
|
||||
|
||||
### Internals
|
||||
|
||||
- **reactivity:** store value cache on CustomRefs impls ([#11539](https://github.com/vuejs/core/issues/11539)) ([e044b6e](https://github.com/vuejs/core/commit/e044b6e737efc9433d1d84590036b82280da6292))
|
||||
- **types:** provide internal options for directly using user types in language tools ([#10801](https://github.com/vuejs/core/issues/10801)) ([75c8cf6](https://github.com/vuejs/core/commit/75c8cf63a1ef30ac84f91282d66ad3f57c6612e9))
|
||||
- **types:** provide internal options for using refs type in language tools ([#11492](https://github.com/vuejs/core/issues/11492)) ([5ffd1a8](https://github.com/vuejs/core/commit/5ffd1a89455807d5069eb2c28eba0379641dca76))
|
||||
|
||||
|
||||
|
||||
## Bug Fixes
|
||||
|
||||
* **compiler-sfc:** fix import usage check for kebab-case same name shorthand binding ([0f7c0e5](https://github.com/vuejs/core/commit/0f7c0e5dc0eedada7a5194db87fd0a7dbd1d3354)), closes [#11745](https://github.com/vuejs/core/issues/11745) [#11754](https://github.com/vuejs/core/issues/11754)
|
||||
* **cssVars:** correctly escape double quotes in SSR ([#11784](https://github.com/vuejs/core/issues/11784)) ([7b5b6e0](https://github.com/vuejs/core/commit/7b5b6e0275f35748dca6d7eb842f8ab2364c6b9a)), closes [#11779](https://github.com/vuejs/core/issues/11779)
|
||||
* **deps:** update dependency postcss to ^8.4.44 ([#11774](https://github.com/vuejs/core/issues/11774)) ([cb843e0](https://github.com/vuejs/core/commit/cb843e0be31f9e563ccfc30eca0c06f2a224b505))
|
||||
* **hydration:** escape css var name to avoid mismatch ([#11739](https://github.com/vuejs/core/issues/11739)) ([ca12e77](https://github.com/vuejs/core/commit/ca12e776bc53aaa31f2df6bb6edc6be1b2f10c37)), closes [#11735](https://github.com/vuejs/core/issues/11735)
|
||||
* **hydration:** handle text nodes with 0 during hydration ([#11772](https://github.com/vuejs/core/issues/11772)) ([c756da2](https://github.com/vuejs/core/commit/c756da24b2d8635cf52b4c7d3abf5bf938852cc5)), closes [#11771](https://github.com/vuejs/core/issues/11771)
|
||||
* **reactivity:** correctly handle method calls on user-extended arrays ([#11760](https://github.com/vuejs/core/issues/11760)) ([9817c80](https://github.com/vuejs/core/commit/9817c80187bec6a3344c74d65fac92262de0fcdd)), closes [#11759](https://github.com/vuejs/core/issues/11759)
|
||||
* **runtime-dom:** avoid unnecessary prop patch for checkbox ([#11657](https://github.com/vuejs/core/issues/11657)) ([c3ce9fe](https://github.com/vuejs/core/commit/c3ce9fe3d8fc27d864ce7148cd36da882cfc21ab)), closes [#11647](https://github.com/vuejs/core/issues/11647)
|
||||
* **runtime-dom:** prevent unnecessary DOM update from v-model ([#11656](https://github.com/vuejs/core/issues/11656)) ([b1be9bd](https://github.com/vuejs/core/commit/b1be9bd64f2c7c4286fecb25bad5d5edd49efce9)), closes [#11647](https://github.com/vuejs/core/issues/11647)
|
||||
* **server-renderer:** Fix call to serverPrefetch in server renderer with an async setup ([#10893](https://github.com/vuejs/core/issues/10893)) ([6039e25](https://github.com/vuejs/core/commit/6039e25e04a8c1db5821955f011d57f1615807ab))
|
||||
* **server-renderer:** render `className` during SSR ([#11722](https://github.com/vuejs/core/issues/11722)) ([52cdb0f](https://github.com/vuejs/core/commit/52cdb0f991dc154ae32a2900874d5dbc4e078565))
|
||||
* **types/defineModel:** allow getter and setter types to be unrelated ([#11699](https://github.com/vuejs/core/issues/11699)) ([fe07f70](https://github.com/vuejs/core/commit/fe07f7073617df358c2f8cbc3de433359e873c96)), closes [#11697](https://github.com/vuejs/core/issues/11697)
|
||||
|
||||
|
||||
|
||||
# [3.5.0-rc.1](https://github.com/vuejs/core/compare/v3.5.0-beta.3...v3.5.0-rc.1) (2024-08-29)
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import importX from 'eslint-plugin-import-x'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import vitest from 'eslint-plugin-vitest'
|
||||
import vitest from '@vitest/eslint-plugin'
|
||||
import { builtinModules } from 'node:module'
|
||||
|
||||
const DOMGlobals = ['window', 'document']
|
||||
|
|
70
package.json
70
package.json
|
@ -1,14 +1,14 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.5.0-rc.1",
|
||||
"packageManager": "pnpm@9.9.0",
|
||||
"version": "3.5.13",
|
||||
"packageManager": "pnpm@9.14.4",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js",
|
||||
"build": "node scripts/build.js",
|
||||
"build-dts": "tsc -p tsconfig.build-browser.json && tsc -p tsconfig.build-node.json && rollup -c rollup.dts.config.js",
|
||||
"clean": "rimraf packages/*/dist temp .eslintcache",
|
||||
"size": "run-s \"size-*\" && tsx scripts/usage-size.ts",
|
||||
"build-dts": "tsc -p tsconfig.build.json --noCheck && rollup -c rollup.dts.config.js",
|
||||
"clean": "rimraf --glob packages/*/dist temp .eslintcache",
|
||||
"size": "run-s \"size-*\" && node scripts/usage-size.js",
|
||||
"size-global": "node scripts/build.js vue runtime-dom -f global -p --size",
|
||||
"size-esm-runtime": "node scripts/build.js vue -f esm-bundler-runtime",
|
||||
"size-esm": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler",
|
||||
|
@ -17,12 +17,15 @@
|
|||
"format": "prettier --write --cache .",
|
||||
"format-check": "prettier --check --cache .",
|
||||
"test": "vitest",
|
||||
"test-unit": "vitest -c vitest.unit.config.ts",
|
||||
"test-e2e": "node scripts/build.js vue -f global -d && vitest -c vitest.e2e.config.ts",
|
||||
"test-unit": "vitest --project unit",
|
||||
"test-e2e": "node scripts/build.js vue -f global -d && vitest --project e2e",
|
||||
"test-dts": "run-s build-dts test-dts-only",
|
||||
"test-dts-only": "tsc -p packages-private/dts-built-test/tsconfig.json && tsc -p ./packages-private/dts-test/tsconfig.test.json",
|
||||
"test-coverage": "vitest run -c vitest.unit.config.ts --coverage",
|
||||
"test-bench": "vitest bench",
|
||||
"test-coverage": "vitest run --project unit --coverage",
|
||||
"prebench": "node scripts/build.js -pf esm-browser reactivity",
|
||||
"prebench-compare": "node scripts/build.js -pf esm-browser reactivity",
|
||||
"bench": "vitest bench --project=unit --outputJson=temp/bench.json",
|
||||
"bench-compare": "vitest bench --project=unit --compare=temp/bench.json",
|
||||
"release": "node scripts/release.js",
|
||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||
|
@ -61,54 +64,53 @@
|
|||
"devDependencies": {
|
||||
"@babel/parser": "catalog:",
|
||||
"@babel/types": "catalog:",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^26.0.1",
|
||||
"@rollup/plugin-alias": "^5.1.1",
|
||||
"@rollup/plugin-commonjs": "^28.0.1",
|
||||
"@rollup/plugin-json": "^6.1.0",
|
||||
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||
"@rollup/plugin-node-resolve": "^15.3.0",
|
||||
"@rollup/plugin-replace": "5.0.4",
|
||||
"@swc/core": "^1.7.19",
|
||||
"@swc/core": "^1.9.3",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/node": "^20.16.2",
|
||||
"@types/node": "^22.10.1",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/serve-handler": "^6.1.4",
|
||||
"@vitest/coverage-v8": "^2.0.5",
|
||||
"@vitest/coverage-v8": "^2.1.5",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^5.0.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.23.1",
|
||||
"esbuild": "^0.24.0",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-plugin-import-x": "^3.1.0",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-import-x": "^4.4.2",
|
||||
"@vitest/eslint-plugin": "^1.1.10",
|
||||
"estree-walker": "catalog:",
|
||||
"jsdom": "^25.0.0",
|
||||
"lint-staged": "^15.2.9",
|
||||
"jsdom": "^25.0.1",
|
||||
"lint-staged": "^15.2.10",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.11",
|
||||
"markdown-table": "^3.0.3",
|
||||
"magic-string": "^0.30.14",
|
||||
"markdown-table": "^3.0.4",
|
||||
"marked": "13.0.3",
|
||||
"npm-run-all2": "^6.2.2",
|
||||
"picocolors": "^1.0.1",
|
||||
"npm-run-all2": "^7.0.1",
|
||||
"picocolors": "^1.1.1",
|
||||
"prettier": "^3.3.3",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~23.0.2",
|
||||
"puppeteer": "~23.3.0",
|
||||
"rimraf": "^6.0.1",
|
||||
"rollup": "^4.21.1",
|
||||
"rollup": "^4.27.4",
|
||||
"rollup-plugin-dts": "^6.1.1",
|
||||
"rollup-plugin-esbuild": "^6.1.1",
|
||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||
"semver": "^7.6.3",
|
||||
"serve": "^14.2.3",
|
||||
"serve-handler": "^6.1.5",
|
||||
"serve": "^14.2.4",
|
||||
"serve-handler": "^6.1.6",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.7.0",
|
||||
"tsx": "^4.19.0",
|
||||
"typescript": "~5.5.4",
|
||||
"typescript-eslint": "^8.3.0",
|
||||
"tslib": "^2.8.1",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.14.0",
|
||||
"vite": "catalog:",
|
||||
"vitest": "^2.0.5"
|
||||
"vitest": "^2.1.5"
|
||||
},
|
||||
"pnpm": {
|
||||
"peerDependencyRules": {
|
||||
|
|
|
@ -3,12 +3,17 @@ import { expectType } from './utils'
|
|||
|
||||
const app = createApp({})
|
||||
|
||||
app.directive<HTMLElement, string>('custom', {
|
||||
app.directive<HTMLElement, string, 'prevent' | 'stop', 'arg1' | 'arg2'>(
|
||||
'custom',
|
||||
{
|
||||
mounted(el, binding) {
|
||||
expectType<HTMLElement>(el)
|
||||
expectType<string>(binding.value)
|
||||
expectType<{ prevent: boolean; stop: boolean }>(binding.modifiers)
|
||||
expectType<'arg1' | 'arg2'>(binding.arg!)
|
||||
|
||||
// @ts-expect-error not any
|
||||
expectType<number>(binding.value)
|
||||
},
|
||||
})
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1041,6 +1041,18 @@ describe('emits', () => {
|
|||
},
|
||||
})
|
||||
|
||||
// #11803 manual props annotation in setup()
|
||||
const Hello = defineComponent({
|
||||
name: 'HelloWorld',
|
||||
inheritAttrs: false,
|
||||
props: { foo: String },
|
||||
emits: {
|
||||
customClick: (args: string) => typeof args === 'string',
|
||||
},
|
||||
setup(props: { foo?: string }) {},
|
||||
})
|
||||
;<Hello onCustomClick={() => {}} />
|
||||
|
||||
// without emits
|
||||
defineComponent({
|
||||
setup(props, { emit }) {
|
||||
|
@ -1810,6 +1822,15 @@ describe('__typeRefs backdoor, object syntax', () => {
|
|||
expectType<number>(refs.child.$refs.foo)
|
||||
})
|
||||
|
||||
describe('__typeEl backdoor', () => {
|
||||
const Comp = defineComponent({
|
||||
__typeEl: {} as HTMLAnchorElement,
|
||||
})
|
||||
const c = new Comp()
|
||||
|
||||
expectType<HTMLAnchorElement>(c.$el)
|
||||
})
|
||||
|
||||
defineComponent({
|
||||
props: {
|
||||
foo: [String, null],
|
||||
|
@ -2047,3 +2068,13 @@ expectString(instance.actionText)
|
|||
// public prop on $props should be optional
|
||||
// @ts-expect-error
|
||||
expectString(instance.$props.actionText)
|
||||
|
||||
// #12122
|
||||
defineComponent({
|
||||
props: { foo: String },
|
||||
render() {
|
||||
expectType<{ readonly foo?: string }>(this.$props)
|
||||
// @ts-expect-error
|
||||
expectType<string>(this.$props)
|
||||
},
|
||||
})
|
||||
|
|
|
@ -189,6 +189,24 @@ describe('allow getter and setter types to be unrelated', <T>() => {
|
|||
f.value = ref(1)
|
||||
})
|
||||
|
||||
describe('correctly unwraps nested refs', () => {
|
||||
const obj = {
|
||||
n: 24,
|
||||
ref: ref(24),
|
||||
nestedRef: ref({ n: ref(0) }),
|
||||
}
|
||||
|
||||
const a = ref(obj)
|
||||
expectType<number>(a.value.n)
|
||||
expectType<number>(a.value.ref)
|
||||
expectType<number>(a.value.nestedRef.n)
|
||||
|
||||
const b = reactive({ a })
|
||||
expectType<number>(b.a.n)
|
||||
expectType<number>(b.a.ref)
|
||||
expectType<number>(b.a.nestedRef.n)
|
||||
})
|
||||
|
||||
// computed
|
||||
describe('allow computed getter and setter types to be unrelated', () => {
|
||||
const obj = ref({
|
||||
|
|
|
@ -240,6 +240,23 @@ describe('withDefaults w/ defineProp type is different from the defaults type',
|
|||
res1.value
|
||||
})
|
||||
|
||||
describe('withDefaults w/ defineProp discriminate union type', () => {
|
||||
const props = withDefaults(
|
||||
defineProps<
|
||||
{ type: 'button'; buttonType?: 'submit' } | { type: 'link'; href: string }
|
||||
>(),
|
||||
{
|
||||
type: 'button',
|
||||
},
|
||||
)
|
||||
if (props.type === 'button') {
|
||||
expectType<'submit' | undefined>(props.buttonType)
|
||||
}
|
||||
if (props.type === 'link') {
|
||||
expectType<string>(props.href)
|
||||
}
|
||||
})
|
||||
|
||||
describe('defineProps w/ runtime declaration', () => {
|
||||
// runtime declaration
|
||||
const props = defineProps({
|
||||
|
@ -289,6 +306,14 @@ describe('defineEmits w/ type declaration', () => {
|
|||
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', () => {
|
||||
const emit = defineEmits<{
|
||||
foo: [id: string]
|
||||
|
@ -427,6 +452,51 @@ describe('defineModel', () => {
|
|||
defineModel<string>({ default: 123 })
|
||||
// @ts-expect-error unknown props option
|
||||
defineModel({ foo: 123 })
|
||||
|
||||
// unrelated getter and setter types
|
||||
{
|
||||
const modelVal = defineModel({
|
||||
get(_: string[]): string {
|
||||
return ''
|
||||
},
|
||||
set(_: number) {
|
||||
return 1
|
||||
},
|
||||
})
|
||||
expectType<string | undefined>(modelVal.value)
|
||||
modelVal.value = 1
|
||||
modelVal.value = undefined
|
||||
// @ts-expect-error
|
||||
modelVal.value = 'foo'
|
||||
|
||||
const [modelVal2] = modelVal
|
||||
expectType<string | undefined>(modelVal2.value)
|
||||
modelVal2.value = 1
|
||||
modelVal2.value = undefined
|
||||
// @ts-expect-error
|
||||
modelVal.value = 'foo'
|
||||
|
||||
const count = defineModel('count', {
|
||||
get(_: string[]): string {
|
||||
return ''
|
||||
},
|
||||
set(_: number) {
|
||||
return ''
|
||||
},
|
||||
})
|
||||
expectType<string | undefined>(count.value)
|
||||
count.value = 1
|
||||
count.value = undefined
|
||||
// @ts-expect-error
|
||||
count.value = 'foo'
|
||||
|
||||
const [count2] = count
|
||||
expectType<string | undefined>(count2.value)
|
||||
count2.value = 1
|
||||
count2.value = undefined
|
||||
// @ts-expect-error
|
||||
count2.value = 'foo'
|
||||
}
|
||||
})
|
||||
|
||||
describe('useModel', () => {
|
||||
|
|
|
@ -121,3 +121,5 @@ expectType<JSX.Element>(
|
|||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>,
|
||||
)
|
||||
// details
|
||||
expectType<JSX.Element>(<details name="details" />)
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
// Global compile-time constants
|
||||
declare var __COMMIT__: string
|
||||
|
||||
declare module 'file-saver' {
|
||||
export function saveAs(blob: any, name: any): void
|
||||
}
|
|
@ -13,7 +13,7 @@
|
|||
"vite": "catalog:"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^4.3.1",
|
||||
"@vue/repl": "^4.4.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"jszip": "^3.10.1",
|
||||
"vue": "workspace:*"
|
||||
|
|
|
@ -14,6 +14,12 @@ setVH()
|
|||
|
||||
const useSSRMode = ref(false)
|
||||
|
||||
const AUTO_SAVE_STORAGE_KEY = 'vue-sfc-playground-auto-save'
|
||||
const initAutoSave: boolean = JSON.parse(
|
||||
localStorage.getItem(AUTO_SAVE_STORAGE_KEY) ?? 'true',
|
||||
)
|
||||
const autoSave = ref(initAutoSave)
|
||||
|
||||
const { productionMode, vueVersion, importMap } = useVueImportMap({
|
||||
runtimeDev: import.meta.env.PROD
|
||||
? `${location.origin}/vue.runtime.esm-browser.js`
|
||||
|
@ -89,6 +95,11 @@ function toggleSSR() {
|
|||
useSSRMode.value = !useSSRMode.value
|
||||
}
|
||||
|
||||
function toggleAutoSave() {
|
||||
autoSave.value = !autoSave.value
|
||||
localStorage.setItem(AUTO_SAVE_STORAGE_KEY, String(autoSave.value))
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
replRef.value?.reload()
|
||||
}
|
||||
|
@ -111,9 +122,12 @@ onMounted(() => {
|
|||
:store="store"
|
||||
:prod="productionMode"
|
||||
:ssr="useSSRMode"
|
||||
:autoSave="autoSave"
|
||||
:theme="theme"
|
||||
@toggle-theme="toggleTheme"
|
||||
@toggle-prod="toggleProdMode"
|
||||
@toggle-ssr="toggleSSR"
|
||||
@toggle-autosave="toggleAutoSave"
|
||||
@reload-page="reloadPage"
|
||||
/>
|
||||
<Repl
|
||||
|
@ -123,6 +137,8 @@ onMounted(() => {
|
|||
@keydown.ctrl.s.prevent
|
||||
@keydown.meta.s.prevent
|
||||
:ssr="useSSRMode"
|
||||
:model-value="autoSave"
|
||||
:editorOptions="{ autoSaveText: false }"
|
||||
:store="store"
|
||||
:showCompileOutput="true"
|
||||
:autoResize="true"
|
||||
|
|
|
@ -14,11 +14,14 @@ const props = defineProps<{
|
|||
store: ReplStore
|
||||
prod: boolean
|
||||
ssr: boolean
|
||||
autoSave: boolean
|
||||
theme: 'dark' | 'light'
|
||||
}>()
|
||||
const emit = defineEmits([
|
||||
'toggle-theme',
|
||||
'toggle-ssr',
|
||||
'toggle-prod',
|
||||
'toggle-autosave',
|
||||
'reload-page',
|
||||
])
|
||||
|
||||
|
@ -43,6 +46,7 @@ function resetVueVersion() {
|
|||
|
||||
async function copyLink(e: MouseEvent) {
|
||||
if (e.metaKey) {
|
||||
resetVueVersion()
|
||||
// hidden logic for going to local debug from play.vuejs.org
|
||||
window.location.href = 'http://localhost:5173/' + window.location.hash
|
||||
return
|
||||
|
@ -107,7 +111,19 @@ function toggleDark() {
|
|||
>
|
||||
<span>{{ ssr ? 'SSR ON' : 'SSR OFF' }}</span>
|
||||
</button>
|
||||
<button title="Toggle dark mode" class="toggle-dark" @click="toggleDark">
|
||||
<button
|
||||
title="Toggle editor auto save mode"
|
||||
class="toggle-autosave"
|
||||
:class="{ enabled: autoSave }"
|
||||
@click="$emit('toggle-autosave')"
|
||||
>
|
||||
<span>{{ autoSave ? 'AutoSave ON' : 'AutoSave OFF' }}</span>
|
||||
</button>
|
||||
<button
|
||||
:title="`Switch to ${theme === 'dark' ? 'light' : 'dark'} theme`"
|
||||
class="toggle-dark"
|
||||
@click="toggleDark"
|
||||
>
|
||||
<Sun class="light" />
|
||||
<Moon class="dark" />
|
||||
</button>
|
||||
|
@ -199,7 +215,8 @@ h1 img {
|
|||
}
|
||||
|
||||
.toggle-prod span,
|
||||
.toggle-ssr span {
|
||||
.toggle-ssr span,
|
||||
.toggle-autosave span {
|
||||
font-size: 12px;
|
||||
border-radius: 4px;
|
||||
padding: 4px 6px;
|
||||
|
@ -214,11 +231,13 @@ h1 img {
|
|||
background: var(--purple);
|
||||
}
|
||||
|
||||
.toggle-ssr span {
|
||||
.toggle-ssr span,
|
||||
.toggle-autosave span {
|
||||
background-color: var(--btn-bg);
|
||||
}
|
||||
|
||||
.toggle-ssr.enabled span {
|
||||
.toggle-ssr.enabled span,
|
||||
.toggle-autosave.enabled span {
|
||||
color: #fff;
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"vue": "^3.4.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^5.1.2",
|
||||
"vite": "^5.4.2"
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"vite": "^5.4.11"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
"enableNonBrowserBranches": true
|
||||
},
|
||||
"dependencies": {
|
||||
"monaco-editor": "^0.51.0",
|
||||
"source-map-js": "^1.2.0"
|
||||
"monaco-editor": "^0.52.0",
|
||||
"source-map-js": "^1.2.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"isolatedDeclarations": false
|
||||
},
|
||||
"include": ["."]
|
||||
}
|
|
@ -1358,7 +1358,27 @@ describe('compiler: parse', () => {
|
|||
name: 'on',
|
||||
rawName: 'v-on.enter',
|
||||
arg: undefined,
|
||||
modifiers: ['enter'],
|
||||
modifiers: [
|
||||
{
|
||||
constType: 3,
|
||||
content: 'enter',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 16,
|
||||
line: 1,
|
||||
offset: 15,
|
||||
},
|
||||
source: 'enter',
|
||||
start: {
|
||||
column: 11,
|
||||
line: 1,
|
||||
offset: 10,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
],
|
||||
exp: undefined,
|
||||
loc: {
|
||||
start: { offset: 5, line: 1, column: 6 },
|
||||
|
@ -1377,7 +1397,46 @@ describe('compiler: parse', () => {
|
|||
name: 'on',
|
||||
rawName: 'v-on.enter.exact',
|
||||
arg: undefined,
|
||||
modifiers: ['enter', 'exact'],
|
||||
modifiers: [
|
||||
{
|
||||
constType: 3,
|
||||
content: 'enter',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 16,
|
||||
line: 1,
|
||||
offset: 15,
|
||||
},
|
||||
source: 'enter',
|
||||
start: {
|
||||
column: 11,
|
||||
line: 1,
|
||||
offset: 10,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
{
|
||||
constType: 3,
|
||||
content: 'exact',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 22,
|
||||
line: 1,
|
||||
offset: 21,
|
||||
},
|
||||
source: 'exact',
|
||||
start: {
|
||||
column: 17,
|
||||
line: 1,
|
||||
offset: 16,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
],
|
||||
exp: undefined,
|
||||
loc: {
|
||||
start: { offset: 5, line: 1, column: 6 },
|
||||
|
@ -1406,7 +1465,46 @@ describe('compiler: parse', () => {
|
|||
source: 'click',
|
||||
},
|
||||
},
|
||||
modifiers: ['enter', 'exact'],
|
||||
modifiers: [
|
||||
{
|
||||
constType: 3,
|
||||
content: 'enter',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 22,
|
||||
line: 1,
|
||||
offset: 21,
|
||||
},
|
||||
source: 'enter',
|
||||
start: {
|
||||
column: 17,
|
||||
line: 1,
|
||||
offset: 16,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
{
|
||||
constType: 3,
|
||||
content: 'exact',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 28,
|
||||
line: 1,
|
||||
offset: 27,
|
||||
},
|
||||
source: 'exact',
|
||||
start: {
|
||||
column: 23,
|
||||
line: 1,
|
||||
offset: 22,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
],
|
||||
exp: undefined,
|
||||
loc: {
|
||||
start: { offset: 5, line: 1, column: 6 },
|
||||
|
@ -1435,7 +1533,27 @@ describe('compiler: parse', () => {
|
|||
source: '[a.b]',
|
||||
},
|
||||
},
|
||||
modifiers: ['camel'],
|
||||
modifiers: [
|
||||
{
|
||||
constType: 3,
|
||||
content: 'camel',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 22,
|
||||
line: 1,
|
||||
offset: 21,
|
||||
},
|
||||
source: 'camel',
|
||||
start: {
|
||||
column: 17,
|
||||
line: 1,
|
||||
offset: 16,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
],
|
||||
exp: undefined,
|
||||
loc: {
|
||||
start: { offset: 5, line: 1, column: 6 },
|
||||
|
@ -1530,7 +1648,27 @@ describe('compiler: parse', () => {
|
|||
source: 'a',
|
||||
},
|
||||
},
|
||||
modifiers: ['prop'],
|
||||
modifiers: [
|
||||
{
|
||||
constType: 0,
|
||||
content: 'prop',
|
||||
isStatic: false,
|
||||
loc: {
|
||||
end: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
offset: 0,
|
||||
},
|
||||
source: '',
|
||||
start: {
|
||||
column: 1,
|
||||
line: 1,
|
||||
offset: 0,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
],
|
||||
exp: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'b',
|
||||
|
@ -1569,7 +1707,27 @@ describe('compiler: parse', () => {
|
|||
source: 'a',
|
||||
},
|
||||
},
|
||||
modifiers: ['sync'],
|
||||
modifiers: [
|
||||
{
|
||||
constType: 3,
|
||||
content: 'sync',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 13,
|
||||
line: 1,
|
||||
offset: 12,
|
||||
},
|
||||
source: 'sync',
|
||||
start: {
|
||||
column: 9,
|
||||
line: 1,
|
||||
offset: 8,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
],
|
||||
exp: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'b',
|
||||
|
@ -1649,7 +1807,27 @@ describe('compiler: parse', () => {
|
|||
source: 'a',
|
||||
},
|
||||
},
|
||||
modifiers: ['enter'],
|
||||
modifiers: [
|
||||
{
|
||||
constType: 3,
|
||||
content: 'enter',
|
||||
isStatic: true,
|
||||
loc: {
|
||||
end: {
|
||||
column: 14,
|
||||
line: 1,
|
||||
offset: 13,
|
||||
},
|
||||
source: 'enter',
|
||||
start: {
|
||||
column: 9,
|
||||
line: 1,
|
||||
offset: 8,
|
||||
},
|
||||
},
|
||||
type: 4,
|
||||
},
|
||||
],
|
||||
exp: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: 'b',
|
||||
|
@ -1841,6 +2019,21 @@ describe('compiler: parse', () => {
|
|||
children: [{ type: NodeTypes.TEXT, content: `{{ number ` }],
|
||||
},
|
||||
])
|
||||
|
||||
const ast3 = baseParse(`<div v-pre><textarea>{{ foo </textarea></div>`, {
|
||||
parseMode: 'html',
|
||||
})
|
||||
expect((ast3.children[0] as ElementNode).children).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
children: [
|
||||
{
|
||||
type: NodeTypes.TEXT,
|
||||
content: `{{ foo `,
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('self-closing v-pre', () => {
|
||||
|
@ -2176,6 +2369,7 @@ describe('compiler: parse', () => {
|
|||
test('should remove leading newline character immediately following the pre element start tag', () => {
|
||||
const ast = parse(`<pre>\n foo bar </pre>`, {
|
||||
isPreTag: tag => tag === 'pre',
|
||||
isIgnoreNewlineTag: tag => tag === 'pre',
|
||||
})
|
||||
expect(ast.children).toHaveLength(1)
|
||||
const preElement = ast.children[0] as ElementNode
|
||||
|
|
|
@ -3,6 +3,8 @@ import {
|
|||
ElementTypes,
|
||||
Namespaces,
|
||||
NodeTypes,
|
||||
type Property,
|
||||
type SimpleExpressionNode,
|
||||
type VNodeCall,
|
||||
locStub,
|
||||
} from '../src'
|
||||
|
@ -22,7 +24,10 @@ const bracketsRE = /^\[|\]$/g
|
|||
// e.g.
|
||||
// - createObjectMatcher({ 'foo': '[bar]' }) matches { foo: bar }
|
||||
// - createObjectMatcher({ '[foo]': 'bar' }) matches { [foo]: "bar" }
|
||||
export function createObjectMatcher(obj: Record<string, any>) {
|
||||
export function createObjectMatcher(obj: Record<string, any>): {
|
||||
type: NodeTypes
|
||||
properties: Partial<Property>[]
|
||||
} {
|
||||
return {
|
||||
type: NodeTypes.JS_OBJECT_EXPRESSION,
|
||||
properties: Object.keys(obj).map(key => ({
|
||||
|
@ -31,7 +36,7 @@ export function createObjectMatcher(obj: Record<string, any>) {
|
|||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: key.replace(bracketsRE, ''),
|
||||
isStatic: !leadingBracketRE.test(key),
|
||||
},
|
||||
} as SimpleExpressionNode,
|
||||
value: isString(obj[key])
|
||||
? {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
|
@ -78,7 +83,7 @@ type Flags = PatchFlags | ShapeFlags
|
|||
export function genFlagText(
|
||||
flag: Flags | Flags[],
|
||||
names: { [k: number]: string } = PatchFlagNames,
|
||||
) {
|
||||
): string {
|
||||
if (isArray(flag)) {
|
||||
let f = 0
|
||||
flag.forEach(ff => {
|
||||
|
|
|
@ -191,7 +191,7 @@ exports[`compiler: cacheStatic transform > prefixIdentifiers > hoist class with
|
|||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = {
|
||||
class: /*#__PURE__*/_normalizeClass({ foo: true })
|
||||
class: /*@__PURE__*/_normalizeClass({ foo: true })
|
||||
}
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
|
@ -1,5 +1,23 @@
|
|||
// 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`] = `
|
||||
"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
|
||||
|
||||
return _cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_setBlockTracking(-1, true),
|
||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
|
@ -28,7 +28,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_setBlockTracking(-1, true),
|
||||
(_cache[0] = _createVNode(_component_Comp, { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
|
@ -47,7 +47,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_setBlockTracking(-1, true),
|
||||
(_cache[0] = _createElementVNode("div", { id: foo }, null, 8 /* PROPS */, ["id"])).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
|
@ -66,7 +66,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_setBlockTracking(-1, true),
|
||||
(_cache[0] = _renderSlot($slots, "default")).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
|
@ -85,7 +85,7 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_cache[0] || (
|
||||
_setBlockTracking(-1),
|
||||
_setBlockTracking(-1, true),
|
||||
(_cache[0] = _createElementVNode("div")).cacheIndex = 0,
|
||||
_setBlockTracking(1),
|
||||
_cache[0]
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
type ForNode,
|
||||
type InterpolationNode,
|
||||
NodeTypes,
|
||||
type RootNode,
|
||||
type SimpleExpressionNode,
|
||||
} from '../../src/ast'
|
||||
import { ErrorCodes } from '../../src/errors'
|
||||
|
@ -24,7 +25,10 @@ import { createObjectMatcher } from '../testUtils'
|
|||
export function parseWithForTransform(
|
||||
template: string,
|
||||
options: CompilerOptions = {},
|
||||
) {
|
||||
): {
|
||||
root: RootNode
|
||||
node: ForNode & { codegenNode: ForCodegenNode }
|
||||
} {
|
||||
const ast = parse(template, options)
|
||||
transform(ast, {
|
||||
nodeTransforms: [
|
||||
|
|
|
@ -53,4 +53,12 @@ describe('compiler: v-memo transform', () => {
|
|||
),
|
||||
).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",
|
||||
"version": "3.5.0-rc.1",
|
||||
"version": "3.5.13",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
@ -203,7 +203,7 @@ export interface DirectiveNode extends Node {
|
|||
rawName?: string
|
||||
exp: ExpressionNode | undefined
|
||||
arg: ExpressionNode | undefined
|
||||
modifiers: string[]
|
||||
modifiers: SimpleExpressionNode[]
|
||||
/**
|
||||
* optional property to cache the expression parse result for v-for
|
||||
*/
|
||||
|
@ -418,6 +418,7 @@ export interface CacheExpression extends Node {
|
|||
index: number
|
||||
value: JSChildNode
|
||||
needPauseTracking: boolean
|
||||
inVOnce: boolean
|
||||
needArraySpread: boolean
|
||||
}
|
||||
|
||||
|
@ -774,12 +775,14 @@ export function createCacheExpression(
|
|||
index: number,
|
||||
value: JSChildNode,
|
||||
needPauseTracking: boolean = false,
|
||||
inVOnce: boolean = false,
|
||||
): CacheExpression {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index,
|
||||
value,
|
||||
needPauseTracking: needPauseTracking,
|
||||
inVOnce,
|
||||
needArraySpread: false,
|
||||
loc: locStub,
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ interface MappingItem {
|
|||
name: string | null
|
||||
}
|
||||
|
||||
const PURE_ANNOTATION = `/*#__PURE__*/`
|
||||
const PURE_ANNOTATION = `/*@__PURE__*/`
|
||||
|
||||
const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
|
||||
|
||||
|
@ -1017,7 +1017,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
|||
push(`_cache[${node.index}] || (`)
|
||||
if (needPauseTracking) {
|
||||
indent()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(-1`)
|
||||
if (node.inVOnce) push(`, true`)
|
||||
push(`),`)
|
||||
newline()
|
||||
push(`(`)
|
||||
}
|
||||
|
|
|
@ -52,6 +52,11 @@ export interface ParserOptions
|
|||
* e.g. elements that should preserve whitespace inside, e.g. `<pre>`
|
||||
*/
|
||||
isPreTag?: (tag: string) => boolean
|
||||
/**
|
||||
* Elements that should ignore the first newline token per parinsg spec
|
||||
* e.g. `<textarea>` and `<pre>`
|
||||
*/
|
||||
isIgnoreNewlineTag?: (tag: string) => boolean
|
||||
/**
|
||||
* Platform-specific built-in components e.g. `<Transition>`
|
||||
*/
|
||||
|
|
|
@ -72,6 +72,7 @@ export const defaultParserOptions: MergedParserOptions = {
|
|||
getNamespace: () => Namespaces.HTML,
|
||||
isVoidTag: NO,
|
||||
isPreTag: NO,
|
||||
isIgnoreNewlineTag: NO,
|
||||
isCustomElement: NO,
|
||||
onError: defaultOnError,
|
||||
onWarn: defaultOnWarn,
|
||||
|
@ -225,7 +226,7 @@ const tokenizer = new Tokenizer(stack, {
|
|||
rawName: raw,
|
||||
exp: undefined,
|
||||
arg: undefined,
|
||||
modifiers: raw === '.' ? ['prop'] : [],
|
||||
modifiers: raw === '.' ? [createSimpleExpression('prop')] : [],
|
||||
loc: getLoc(start),
|
||||
}
|
||||
if (name === 'pre') {
|
||||
|
@ -273,7 +274,8 @@ const tokenizer = new Tokenizer(stack, {
|
|||
setLocEnd(arg.loc, end)
|
||||
}
|
||||
} else {
|
||||
;(currentProp as DirectiveNode).modifiers.push(mod)
|
||||
const exp = createSimpleExpression(mod, true, getLoc(start, end))
|
||||
;(currentProp as DirectiveNode).modifiers.push(exp)
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -379,7 +381,9 @@ const tokenizer = new Tokenizer(stack, {
|
|||
if (
|
||||
__COMPAT__ &&
|
||||
currentProp.name === 'bind' &&
|
||||
(syncIndex = currentProp.modifiers.indexOf('sync')) > -1 &&
|
||||
(syncIndex = currentProp.modifiers.findIndex(
|
||||
mod => mod.content === 'sync',
|
||||
)) > -1 &&
|
||||
checkCompatEnabled(
|
||||
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
||||
currentOptions,
|
||||
|
@ -630,7 +634,7 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
|||
}
|
||||
|
||||
// refine element type
|
||||
const { tag, ns } = el
|
||||
const { tag, ns, children } = el
|
||||
if (!inVPre) {
|
||||
if (tag === 'slot') {
|
||||
el.tagType = ElementTypes.SLOT
|
||||
|
@ -643,8 +647,18 @@ function onCloseTag(el: ElementNode, end: number, isImplied = false) {
|
|||
|
||||
// whitespace management
|
||||
if (!tokenizer.inRCDATA) {
|
||||
el.children = condenseWhitespace(el.children, el.tag)
|
||||
el.children = condenseWhitespace(children, tag)
|
||||
}
|
||||
|
||||
if (ns === Namespaces.HTML && currentOptions.isIgnoreNewlineTag(tag)) {
|
||||
// remove leading newline for <textarea> and <pre> per html spec
|
||||
// https://html.spec.whatwg.org/multipage/parsing.html#parsing-main-inbody
|
||||
const first = children[0]
|
||||
if (first && first.type === NodeTypes.TEXT) {
|
||||
first.content = first.content.replace(/^\r?\n/, '')
|
||||
}
|
||||
}
|
||||
|
||||
if (ns === Namespaces.HTML && currentOptions.isPreTag(tag)) {
|
||||
inPre--
|
||||
}
|
||||
|
@ -879,14 +893,6 @@ function condenseWhitespace(
|
|||
}
|
||||
}
|
||||
}
|
||||
if (inPre && tag && currentOptions.isPreTag(tag)) {
|
||||
// remove leading newline per html spec
|
||||
// https://html.spec.whatwg.org/multipage/grouping-content.html#the-pre-element
|
||||
const first = nodes[0]
|
||||
if (first && first.type === NodeTypes.TEXT) {
|
||||
first.content = first.content.replace(/^\r?\n/, '')
|
||||
}
|
||||
}
|
||||
return removedWhitespace ? nodes.filter(Boolean) : nodes
|
||||
}
|
||||
|
||||
|
@ -940,6 +946,10 @@ function getLoc(start: number, end?: number): SourceLocation {
|
|||
}
|
||||
}
|
||||
|
||||
export function cloneLoc(loc: SourceLocation): SourceLocation {
|
||||
return getLoc(loc.start.offset, loc.end.offset)
|
||||
}
|
||||
|
||||
function setLocEnd(loc: SourceLocation, end: number) {
|
||||
loc.end = tokenizer.getPos(end)
|
||||
loc.source = getSlice(loc.start.offset, end)
|
||||
|
|
|
@ -438,7 +438,7 @@ export default class Tokenizer {
|
|||
// We have to parse entities in <title> and <textarea> tags.
|
||||
if (!__BROWSER__ && c === CharCodes.Amp) {
|
||||
this.startEntity()
|
||||
} else if (c === this.delimiterOpen[0]) {
|
||||
} else if (!this.inVPre && c === this.delimiterOpen[0]) {
|
||||
// We also need to handle interpolation
|
||||
this.state = State.InterpolationOpen
|
||||
this.delimiterIndex = 0
|
||||
|
|
|
@ -23,7 +23,6 @@ import {
|
|||
import {
|
||||
EMPTY_OBJ,
|
||||
NOOP,
|
||||
PatchFlagNames,
|
||||
PatchFlags,
|
||||
camelize,
|
||||
capitalize,
|
||||
|
@ -117,7 +116,7 @@ export interface TransformContext
|
|||
addIdentifiers(exp: ExpressionNode | string): void
|
||||
removeIdentifiers(exp: ExpressionNode | string): void
|
||||
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
||||
cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
|
||||
cache(exp: JSChildNode, isVNode?: boolean, inVOnce?: boolean): CacheExpression
|
||||
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
||||
|
||||
// 2.x Compat only
|
||||
|
@ -298,11 +297,12 @@ export function createTransformContext(
|
|||
identifier.hoisted = exp
|
||||
return identifier
|
||||
},
|
||||
cache(exp, isVNode = false) {
|
||||
cache(exp, isVNode = false, inVOnce = false) {
|
||||
const cacheExp = createCacheExpression(
|
||||
context.cached.length,
|
||||
exp,
|
||||
isVNode,
|
||||
inVOnce,
|
||||
)
|
||||
context.cached.push(cacheExp)
|
||||
return cacheExp
|
||||
|
@ -375,7 +375,6 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
} else if (children.length > 1) {
|
||||
// root has multiple nodes - return a fragment block.
|
||||
let patchFlag = PatchFlags.STABLE_FRAGMENT
|
||||
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
||||
// check if the fragment actually contains a single valid child with
|
||||
// the rest being comments
|
||||
if (
|
||||
|
@ -383,7 +382,6 @@ function createRootCodegen(root: RootNode, context: TransformContext) {
|
|||
children.filter(c => c.type !== NodeTypes.COMMENT).length === 1
|
||||
) {
|
||||
patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT
|
||||
patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}`
|
||||
}
|
||||
root.codegenNode = createVNodeCall(
|
||||
context,
|
||||
|
|
|
@ -665,7 +665,7 @@ export function buildProps(
|
|||
}
|
||||
|
||||
// force hydration for v-bind with .prop modifier
|
||||
if (isVBind && modifiers.includes('prop')) {
|
||||
if (isVBind && modifiers.some(mod => mod.content === 'prop')) {
|
||||
patchFlag |= PatchFlags.NEED_HYDRATION
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
isStaticPropertyKey,
|
||||
walkIdentifiers,
|
||||
} from '../babelUtils'
|
||||
import { advancePositionWithClone, isSimpleIdentifier } from '../utils'
|
||||
import { advancePositionWithClone, findDir, isSimpleIdentifier } from '../utils'
|
||||
import {
|
||||
genPropsAccessExp,
|
||||
hasOwn,
|
||||
|
@ -44,7 +44,7 @@ import { parseExpression } from '@babel/parser'
|
|||
import { IS_REF, UNREF } from '../runtimeHelpers'
|
||||
import { BindingTypes } from '../options'
|
||||
|
||||
const isLiteralWhitelisted = /*#__PURE__*/ makeMap('true,false,null,this')
|
||||
const isLiteralWhitelisted = /*@__PURE__*/ makeMap('true,false,null,this')
|
||||
|
||||
export const transformExpression: NodeTransform = (node, context) => {
|
||||
if (node.type === NodeTypes.INTERPOLATION) {
|
||||
|
@ -54,6 +54,7 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||
)
|
||||
} else if (node.type === NodeTypes.ELEMENT) {
|
||||
// handle directives on element
|
||||
const memo = findDir(node, 'memo')
|
||||
for (let i = 0; i < node.props.length; i++) {
|
||||
const dir = node.props[i]
|
||||
// do not process for v-on & v-for since they are special handled
|
||||
|
@ -65,7 +66,14 @@ export const transformExpression: NodeTransform = (node, context) => {
|
|||
if (
|
||||
exp &&
|
||||
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(
|
||||
exp,
|
||||
|
|
|
@ -69,7 +69,7 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
|
||||
// .sync is replaced by v-model:arg
|
||||
if (modifiers.includes('camel')) {
|
||||
if (modifiers.some(mod => mod.content === 'camel')) {
|
||||
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
if (arg.isStatic) {
|
||||
arg.content = camelize(arg.content)
|
||||
|
@ -83,10 +83,10 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
|
||||
if (!context.inSSR) {
|
||||
if (modifiers.includes('prop')) {
|
||||
if (modifiers.some(mod => mod.content === 'prop')) {
|
||||
injectPrefix(arg, '.')
|
||||
}
|
||||
if (modifiers.includes('attr')) {
|
||||
if (modifiers.some(mod => mod.content === 'attr')) {
|
||||
injectPrefix(arg, '^')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,17 +63,27 @@ export const transformFor: NodeTransform = createStructuralDirectiveTransform(
|
|||
const isTemplate = isTemplateNode(node)
|
||||
const memo = findDir(node, 'memo')
|
||||
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
|
||||
transformBindShorthand(keyProp, context)
|
||||
}
|
||||
const keyExp =
|
||||
let keyExp =
|
||||
keyProp &&
|
||||
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||
? keyProp.value
|
||||
? createSimpleExpression(keyProp.value.content, true)
|
||||
: undefined
|
||||
: keyProp.exp)
|
||||
|
||||
if (memo && keyExp && isDirKey) {
|
||||
if (!__BROWSER__) {
|
||||
keyProp.exp = keyExp = processExpression(
|
||||
keyExp as SimpleExpressionNode,
|
||||
context,
|
||||
)
|
||||
}
|
||||
}
|
||||
const keyProperty =
|
||||
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
||||
|
||||
|
|
|
@ -30,9 +30,10 @@ import {
|
|||
import { ErrorCodes, createCompilerError } from '../errors'
|
||||
import { processExpression } from './transformExpression'
|
||||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import { cloneLoc } from '../parser'
|
||||
import { CREATE_COMMENT, FRAGMENT } from '../runtimeHelpers'
|
||||
import { findDir, findProp, getMemoedVNodeCall, injectProp } from '../utils'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
export const transformIf: NodeTransform = createStructuralDirectiveTransform(
|
||||
/^(if|else|else-if)$/,
|
||||
|
@ -110,7 +111,7 @@ export function processIf(
|
|||
const branch = createIfBranch(node, dir)
|
||||
const ifNode: IfNode = {
|
||||
type: NodeTypes.IF,
|
||||
loc: node.loc,
|
||||
loc: cloneLoc(node.loc),
|
||||
branches: [branch],
|
||||
}
|
||||
context.replaceNode(ifNode)
|
||||
|
@ -264,7 +265,6 @@ function createChildrenCodegenNode(
|
|||
return vnodeCall
|
||||
} else {
|
||||
let patchFlag = PatchFlags.STABLE_FRAGMENT
|
||||
let patchFlagText = PatchFlagNames[PatchFlags.STABLE_FRAGMENT]
|
||||
// check if the fragment actually contains a single valid child with
|
||||
// the rest being comments
|
||||
if (
|
||||
|
@ -273,7 +273,6 @@ function createChildrenCodegenNode(
|
|||
children.filter(c => c.type !== NodeTypes.COMMENT).length === 1
|
||||
) {
|
||||
patchFlag |= PatchFlags.DEV_ROOT_FRAGMENT
|
||||
patchFlagText += `, ${PatchFlagNames[PatchFlags.DEV_ROOT_FRAGMENT]}`
|
||||
}
|
||||
|
||||
return createVNodeCall(
|
||||
|
|
|
@ -31,7 +31,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||
|
||||
// we assume v-model directives are always parsed
|
||||
// (not artificially created by a transform)
|
||||
const rawExp = exp.loc.source
|
||||
const rawExp = exp.loc.source.trim()
|
||||
const expString =
|
||||
exp.type === NodeTypes.SIMPLE_EXPRESSION ? exp.content : rawExp
|
||||
|
||||
|
@ -131,6 +131,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||
// modelModifiers: { foo: true, "bar-baz": true }
|
||||
if (dir.modifiers.length && node.tagType === ElementTypes.COMPONENT) {
|
||||
const modifiers = dir.modifiers
|
||||
.map(m => m.content)
|
||||
.map(m => (isSimpleIdentifier(m) ? m : JSON.stringify(m)) + `: true`)
|
||||
.join(`, `)
|
||||
const modifiersKey = arg
|
||||
|
|
|
@ -17,7 +17,11 @@ export const transformOnce: NodeTransform = (node, context) => {
|
|||
context.inVOnce = false
|
||||
const cur = context.currentNode as ElementNode | IfNode | ForNode
|
||||
if (cur.codegenNode) {
|
||||
cur.codegenNode = context.cache(cur.codegenNode, true /* isVNode */)
|
||||
cur.codegenNode = context.cache(
|
||||
cur.codegenNode,
|
||||
true /* isVNode */,
|
||||
true /* inVOnce */,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,22 @@ describe('DOM parser', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('<textarea> should remove leading newline', () => {
|
||||
const ast = parse('<textarea>\nhello</textarea>', parserOptions)
|
||||
const element = ast.children[0] as ElementNode
|
||||
const text = element.children[0] as TextNode
|
||||
expect(element.children.length).toBe(1)
|
||||
expect(text).toStrictEqual({
|
||||
type: NodeTypes.TEXT,
|
||||
content: 'hello',
|
||||
loc: {
|
||||
start: { offset: 10, line: 1, column: 11 },
|
||||
end: { offset: 16, line: 2, column: 6 },
|
||||
source: '\nhello',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('should not treat Uppercase component as special tag', () => {
|
||||
const ast = parse(
|
||||
'<TextArea>some<div>text</div>and<!--comment--></TextArea>',
|
||||
|
|
|
@ -1,5 +1,17 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`stringify static html > eligible content (elements > 20) + non-eligible content 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20),
|
||||
_createElementVNode("div", { key: "1" }, "1", -1 /* HOISTED */),
|
||||
_createStaticVNode("<span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span>", 20)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > escape 1`] = `
|
||||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
|
@ -20,6 +32,33 @@ 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`] = `
|
||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("select", null, [
|
||||
_createElementVNode("option", { value: null }),
|
||||
_createElementVNode("option", { value: "1" }),
|
||||
_createElementVNode("option", { value: "1" }),
|
||||
_createElementVNode("option", { value: "1" }),
|
||||
_createElementVNode("option", { value: "1" }),
|
||||
_createElementVNode("option", { value: "1" })
|
||||
], -1 /* HOISTED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should bail for <option> elements with number values 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
|
|
|
@ -162,6 +162,27 @@ describe('stringify static html', () => {
|
|||
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', () => {
|
||||
const { ast, code } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
|
@ -389,6 +410,24 @@ describe('stringify static html', () => {
|
|||
])
|
||||
})
|
||||
|
||||
test('should stringify mathML', () => {
|
||||
const math = `<math xmlns="http://www.w3.org/1998/Math/MathML">`
|
||||
const repeated = `<ms>1</ms>`
|
||||
const { ast } = compileWithStringify(
|
||||
`<div>${math}${repeat(
|
||||
repeated,
|
||||
StringifyThresholds.NODE_COUNT,
|
||||
)}</math></div>`,
|
||||
)
|
||||
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
`${math}${repeat(repeated, StringifyThresholds.NODE_COUNT)}</math>`,
|
||||
1,
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
// #5439
|
||||
test('stringify v-html', () => {
|
||||
const { code } = compileWithStringify(`
|
||||
|
@ -451,4 +490,29 @@ describe('stringify static html', () => {
|
|||
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should bail for <option> elements with null values', () => {
|
||||
const { ast, code } = compileWithStringify(
|
||||
`<div><select><option :value="null" />${repeat(
|
||||
`<option value="1" />`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</select></div>`,
|
||||
)
|
||||
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('eligible content (elements > 20) + non-eligible content', () => {
|
||||
const { code } = compileWithStringify(
|
||||
`<div>${repeat(
|
||||
`<span/>`,
|
||||
StringifyThresholds.NODE_COUNT,
|
||||
)}<div key="1">1</div>${repeat(
|
||||
`<span/>`,
|
||||
StringifyThresholds.NODE_COUNT,
|
||||
)}</div>`,
|
||||
)
|
||||
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.5.0-rc.1",
|
||||
"version": "3.5.13",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -8,6 +8,7 @@ export const parserOptions: ParserOptions = {
|
|||
isVoidTag,
|
||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),
|
||||
isPreTag: tag => tag === 'pre',
|
||||
isIgnoreNewlineTag: tag => tag === 'pre' || tag === 'textarea',
|
||||
decodeEntities: __BROWSER__ ? decodeHtmlBrowser : undefined,
|
||||
|
||||
isBuiltInComponent: tag => {
|
||||
|
|
|
@ -16,8 +16,6 @@ import {
|
|||
type TemplateChildNode,
|
||||
type TextCallNode,
|
||||
type TransformContext,
|
||||
type VNodeCall,
|
||||
createArrayExpression,
|
||||
createCallExpression,
|
||||
isStaticArgOf,
|
||||
} from '@vue/compiler-core'
|
||||
|
@ -26,6 +24,7 @@ import {
|
|||
isArray,
|
||||
isBooleanAttr,
|
||||
isKnownHtmlAttr,
|
||||
isKnownMathMLAttr,
|
||||
isKnownSvgAttr,
|
||||
isString,
|
||||
isSymbol,
|
||||
|
@ -106,15 +105,23 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
|
|||
String(currentChunk.length),
|
||||
])
|
||||
|
||||
const deleteCount = currentChunk.length - 1
|
||||
|
||||
if (isParentCached) {
|
||||
;((parent.codegenNode as VNodeCall).children as CacheExpression).value =
|
||||
createArrayExpression([staticCall])
|
||||
// if the parent is cached, then `children` is also the value of the
|
||||
// CacheExpression. Just replace the corresponding range in the cached
|
||||
// list with staticCall.
|
||||
children.splice(
|
||||
currentIndex - currentChunk.length,
|
||||
currentChunk.length,
|
||||
// @ts-expect-error
|
||||
staticCall,
|
||||
)
|
||||
} else {
|
||||
// replace the first node's hoisted expression with the static vnode call
|
||||
;(currentChunk[0].codegenNode as CacheExpression).value = staticCall
|
||||
if (currentChunk.length > 1) {
|
||||
// remove merged nodes from children
|
||||
const deleteCount = currentChunk.length - 1
|
||||
children.splice(currentIndex - currentChunk.length + 1, deleteCount)
|
||||
// also adjust index for the remaining cache items
|
||||
const cacheIndex = context.cached.indexOf(
|
||||
|
@ -128,10 +135,10 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
|
|||
}
|
||||
context.cached.splice(cacheIndex - deleteCount + 1, deleteCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
return deleteCount
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
@ -184,11 +191,13 @@ const isStringifiableAttr = (name: string, ns: Namespaces) => {
|
|||
? isKnownHtmlAttr(name)
|
||||
: ns === Namespaces.SVG
|
||||
? isKnownSvgAttr(name)
|
||||
: ns === Namespaces.MATH_ML
|
||||
? isKnownMathMLAttr(name)
|
||||
: false) || dataAriaRE.test(name)
|
||||
)
|
||||
}
|
||||
|
||||
const isNonStringifiable = /*#__PURE__*/ makeMap(
|
||||
const isNonStringifiable = /*@__PURE__*/ makeMap(
|
||||
`caption,thead,tr,th,tbody,td,tfoot,colgroup,col`,
|
||||
)
|
||||
|
||||
|
@ -252,8 +261,7 @@ function analyzeNode(node: StringifiableNode): [number, number] | false {
|
|||
isOptionTag &&
|
||||
isStaticArgOf(p.arg, 'value') &&
|
||||
p.exp &&
|
||||
p.exp.ast &&
|
||||
p.exp.ast.type !== 'StringLiteral'
|
||||
!p.exp.isStatic
|
||||
) {
|
||||
return bail()
|
||||
}
|
||||
|
|
|
@ -17,8 +17,8 @@ import {
|
|||
import { V_ON_WITH_KEYS, V_ON_WITH_MODIFIERS } from '../runtimeHelpers'
|
||||
import { capitalize, makeMap } from '@vue/shared'
|
||||
|
||||
const isEventOptionModifier = /*#__PURE__*/ makeMap(`passive,once,capture`)
|
||||
const isNonKeyModifier = /*#__PURE__*/ makeMap(
|
||||
const isEventOptionModifier = /*@__PURE__*/ makeMap(`passive,once,capture`)
|
||||
const isNonKeyModifier = /*@__PURE__*/ makeMap(
|
||||
// event propagation management
|
||||
`stop,prevent,self,` +
|
||||
// system modifiers + exact
|
||||
|
@ -27,15 +27,12 @@ const isNonKeyModifier = /*#__PURE__*/ makeMap(
|
|||
`middle`,
|
||||
)
|
||||
// left & right could be mouse or key modifiers based on event type
|
||||
const maybeKeyModifier = /*#__PURE__*/ makeMap('left,right')
|
||||
const isKeyboardEvent = /*#__PURE__*/ makeMap(
|
||||
`onkeyup,onkeydown,onkeypress`,
|
||||
true,
|
||||
)
|
||||
const maybeKeyModifier = /*@__PURE__*/ makeMap('left,right')
|
||||
const isKeyboardEvent = /*@__PURE__*/ makeMap(`onkeyup,onkeydown,onkeypress`)
|
||||
|
||||
const resolveModifiers = (
|
||||
key: ExpressionNode,
|
||||
modifiers: string[],
|
||||
modifiers: SimpleExpressionNode[],
|
||||
context: TransformContext,
|
||||
loc: SourceLocation,
|
||||
) => {
|
||||
|
@ -44,7 +41,7 @@ const resolveModifiers = (
|
|||
const eventOptionModifiers = []
|
||||
|
||||
for (let i = 0; i < modifiers.length; i++) {
|
||||
const modifier = modifiers[i]
|
||||
const modifier = modifiers[i].content
|
||||
|
||||
if (
|
||||
__COMPAT__ &&
|
||||
|
@ -64,7 +61,9 @@ const resolveModifiers = (
|
|||
// runtimeModifiers: modifiers that needs runtime guards
|
||||
if (maybeKeyModifier(modifier)) {
|
||||
if (isStaticExp(key)) {
|
||||
if (isKeyboardEvent((key as SimpleExpressionNode).content)) {
|
||||
if (
|
||||
isKeyboardEvent((key as SimpleExpressionNode).content.toLowerCase())
|
||||
) {
|
||||
keyModifiers.push(modifier)
|
||||
} else {
|
||||
nonKeyModifiers.push(modifier)
|
||||
|
@ -133,7 +132,7 @@ export const transformOn: DirectiveTransform = (dir, node, context) => {
|
|||
if (
|
||||
keyModifiers.length &&
|
||||
// if event name is dynamic, always wrap with keys guard
|
||||
(!isStaticExp(key) || isKeyboardEvent(key.content))
|
||||
(!isStaticExp(key) || isKeyboardEvent(key.content.toLowerCase()))
|
||||
) {
|
||||
handlerExp = createCallExpression(context.helper(V_ON_WITH_KEYS), [
|
||||
handlerExp,
|
||||
|
|
|
@ -20,7 +20,7 @@ exports[`SFC analyze <script> bindings > auto name inference > do not overwrite
|
|||
name: 'Baz'
|
||||
})
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
const a = 1
|
||||
|
@ -36,7 +36,7 @@ exports[`SFC analyze <script> bindings > auto name inference > do not overwrite
|
|||
name: 'Baz'
|
||||
}
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
const a = 1
|
||||
|
@ -53,7 +53,7 @@ exports[`SFC compile <script setup> > <script> and <script setup> co-usage > exp
|
|||
}
|
||||
const __default__ = fn();
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -91,7 +91,7 @@ exports[`SFC compile <script setup> > <script> and <script setup> co-usage > scr
|
|||
|
||||
const __default__ = {}
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -110,7 +110,7 @@ exports[`SFC compile <script setup> > <script> and <script setup> co-usage > scr
|
|||
const __default__ = {}
|
||||
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -131,7 +131,7 @@ import { x } from './x'
|
|||
}
|
||||
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
...__default__,
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
@ -154,7 +154,7 @@ exports[`SFC compile <script setup> > <script> and <script setup> co-usage > scr
|
|||
const __default__ = def
|
||||
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -174,7 +174,7 @@ exports[`SFC compile <script setup> > <script> and <script setup> co-usage > spa
|
|||
some:'option'
|
||||
}
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -194,7 +194,7 @@ exports[`SFC compile <script setup> > <script> and <script setup> co-usage > spa
|
|||
some:'option'
|
||||
}
|
||||
|
||||
export default /*#__PURE__*/Object.assign(__default__, {
|
||||
export default /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -880,11 +880,13 @@ export default {
|
|||
|
||||
const count = ref(0)
|
||||
const style = { color: 'red' }
|
||||
const height = ref(0)
|
||||
|
||||
return (_ctx, _push, _parent, _attrs) => {
|
||||
const _cssVars = { style: {
|
||||
"--xxxxxxxx-count": (count.value),
|
||||
"--xxxxxxxx-style\\\\.color": (style.color)
|
||||
"--xxxxxxxx-style\\\\.color": (style.color),
|
||||
"--xxxxxxxx-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")
|
||||
}}
|
||||
_push(\`<!--[--><div\${
|
||||
_ssrRenderAttrs(_cssVars)
|
||||
|
@ -1082,6 +1084,29 @@ return (_ctx, _cache) => {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > inlineTemplate mode > v-model w/ newlines codegen 1`] = `
|
||||
"import { unref as _unref, isRef as _isRef, vModelText as _vModelText, withDirectives as _withDirectives, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
|
||||
export default {
|
||||
setup(__props) {
|
||||
|
||||
const count = ref(0)
|
||||
|
||||
return (_ctx, _cache) => {
|
||||
return _withDirectives((_openBlock(), _createElementBlock("input", {
|
||||
"onUpdate:modelValue": _cache[0] || (_cache[0] = $event => (_isRef(count) ? (count).value = $event : null))
|
||||
}, null, 512 /* NEED_PATCH */)), [
|
||||
[_vModelText,
|
||||
_unref(count)
|
||||
]
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC compile <script setup> > inlineTemplate mode > with defineExpose() 1`] = `
|
||||
"
|
||||
export default {
|
||||
|
@ -1140,7 +1165,7 @@ exports[`SFC compile <script setup> > with TypeScript > const Enum 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
const enum Foo { A = 123 }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1156,7 +1181,7 @@ exports[`SFC compile <script setup> > with TypeScript > hoist type declarations
|
|||
export interface Foo {}
|
||||
type Bar = {}
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1172,7 +1197,7 @@ exports[`SFC compile <script setup> > with TypeScript > import type 1`] = `
|
|||
import type { Foo } from './main.ts'
|
||||
import { type Bar, Baz } from './main.ts'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1187,7 +1212,7 @@ exports[`SFC compile <script setup> > with TypeScript > runtime Enum 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
enum Foo { A = 123 }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1205,7 +1230,7 @@ exports[`SFC compile <script setup> > with TypeScript > runtime Enum in normal s
|
|||
const enum C { C = "C" }
|
||||
enum B { B = "B" }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1221,7 +1246,7 @@ exports[`SFC compile <script setup> > with TypeScript > with generic attribute 1
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type Bar = {}
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1250,7 +1275,7 @@ exports[`SFC genDefaultAs > <script setup> only w/ ts 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
const a = 1
|
||||
|
||||
const _sfc_ = /*#__PURE__*/_defineComponent({
|
||||
const _sfc_ = /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1265,7 +1290,7 @@ exports[`SFC genDefaultAs > <script> + <script setup> 1`] = `
|
|||
"
|
||||
const __default__ = {}
|
||||
|
||||
const _sfc_ = /*#__PURE__*/Object.assign(__default__, {
|
||||
const _sfc_ = /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1281,7 +1306,7 @@ exports[`SFC genDefaultAs > <script> + <script setup> 2`] = `
|
|||
"
|
||||
const __default__ = {}
|
||||
|
||||
const _sfc_ = /*#__PURE__*/Object.assign(__default__, {
|
||||
const _sfc_ = /*@__PURE__*/Object.assign(__default__, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -1298,7 +1323,7 @@ exports[`SFC genDefaultAs > <script> + <script setup> w/ ts 1`] = `
|
|||
|
||||
const __default__ = {}
|
||||
|
||||
const _sfc_ = /*#__PURE__*/_defineComponent({
|
||||
const _sfc_ = /*@__PURE__*/_defineComponent({
|
||||
...__default__,
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
|
|
@ -472,6 +472,23 @@ describe('SFC compile <script setup>', () => {
|
|||
assertCode(content)
|
||||
})
|
||||
|
||||
test('v-model w/ newlines codegen', () => {
|
||||
const { content } = compile(
|
||||
`<script setup>
|
||||
const count = ref(0)
|
||||
</script>
|
||||
<template>
|
||||
<input v-model="
|
||||
count
|
||||
">
|
||||
</template>
|
||||
`,
|
||||
{ inlineTemplate: true },
|
||||
)
|
||||
expect(content).toMatch(`_isRef(count) ? (count).value = $event : null`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('v-model should not generate ref assignment code for non-setup bindings', () => {
|
||||
const { content } = compile(
|
||||
`<script setup>
|
||||
|
@ -606,6 +623,7 @@ describe('SFC compile <script setup>', () => {
|
|||
import { ref } from 'vue'
|
||||
const count = ref(0)
|
||||
const style = { color: 'red' }
|
||||
const height = ref(0)
|
||||
</script>
|
||||
<template>
|
||||
<div>{{ count }}</div>
|
||||
|
@ -614,6 +632,7 @@ describe('SFC compile <script setup>', () => {
|
|||
<style>
|
||||
div { color: v-bind(count) }
|
||||
span { color: v-bind(style.color) }
|
||||
span { color: v-bind(height + "px") }
|
||||
</style>
|
||||
`,
|
||||
{
|
||||
|
@ -629,6 +648,9 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(content).not.toMatch(`useCssVars`)
|
||||
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
|
||||
expect(content).toMatch(`"--${mockId}-style\\\\.color": (style.color)`)
|
||||
expect(content).toMatch(
|
||||
`"--${mockId}-height\\\\ \\\\+\\\\ \\\\\\"px\\\\\\"": (height.value + "px")`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
|
@ -1352,7 +1374,7 @@ describe('SFC genDefaultAs', () => {
|
|||
)
|
||||
expect(content).not.toMatch('export default')
|
||||
expect(content).toMatch(
|
||||
`const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
|
||||
`const _sfc_ = /*@__PURE__*/Object.assign(__default__`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
@ -1371,7 +1393,7 @@ describe('SFC genDefaultAs', () => {
|
|||
)
|
||||
expect(content).not.toMatch('export default')
|
||||
expect(content).toMatch(
|
||||
`const _sfc_ = /*#__PURE__*/Object.assign(__default__`,
|
||||
`const _sfc_ = /*@__PURE__*/Object.assign(__default__`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
@ -1400,7 +1422,7 @@ describe('SFC genDefaultAs', () => {
|
|||
},
|
||||
)
|
||||
expect(content).not.toMatch('export default')
|
||||
expect(content).toMatch(`const _sfc_ = /*#__PURE__*/_defineComponent(`)
|
||||
expect(content).toMatch(`const _sfc_ = /*@__PURE__*/_defineComponent(`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
|
@ -1418,7 +1440,7 @@ describe('SFC genDefaultAs', () => {
|
|||
)
|
||||
expect(content).not.toMatch('export default')
|
||||
expect(content).toMatch(
|
||||
`const _sfc_ = /*#__PURE__*/_defineComponent({\n ...__default__`,
|
||||
`const _sfc_ = /*@__PURE__*/_defineComponent({\n ...__default__`,
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
|
|
|
@ -18,7 +18,7 @@ return { myEmit }
|
|||
exports[`defineEmits > w/ runtime options 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ['a', 'b'],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -35,7 +35,7 @@ exports[`defineEmits > w/ type (exported interface) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
export interface Emits { (e: 'foo' | 'bar'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -52,7 +52,7 @@ exports[`defineEmits > w/ type (exported type alias) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
export type Emits = { (e: 'foo' | 'bar'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -69,7 +69,7 @@ exports[`defineEmits > w/ type (interface ts type) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Emits { (e: 'foo'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ['foo'],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -87,7 +87,7 @@ exports[`defineEmits > w/ type (interface w/ extends) 1`] = `
|
|||
interface Base { (e: 'foo'): void }
|
||||
interface Emits extends Base { (e: 'bar'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["bar", "foo"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -104,7 +104,7 @@ exports[`defineEmits > w/ type (interface) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Emits { (e: 'foo' | 'bar'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -120,7 +120,7 @@ return { emit }
|
|||
exports[`defineEmits > w/ type (property syntax string literal) 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo:bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -136,7 +136,7 @@ return { emit }
|
|||
exports[`defineEmits > w/ type (property syntax) 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -153,7 +153,7 @@ exports[`defineEmits > w/ type (referenced exported function type) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
export type Emits = (e: 'foo' | 'bar') => void
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -170,7 +170,7 @@ exports[`defineEmits > w/ type (referenced function type) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type Emits = (e: 'foo' | 'bar') => void
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -187,7 +187,7 @@ exports[`defineEmits > w/ type (type alias) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type Emits = { (e: 'foo' | 'bar'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -203,7 +203,7 @@ return { emit }
|
|||
exports[`defineEmits > w/ type (type literal w/ call signatures) 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar", "baz"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -221,7 +221,7 @@ exports[`defineEmits > w/ type (type references in union) 1`] = `
|
|||
type BaseEmit = "change"
|
||||
type Emit = "some" | "emit" | BaseEmit
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["some", "emit", "change", "another"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -237,7 +237,7 @@ return { emit }
|
|||
exports[`defineEmits > w/ type (union) 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar", "baz"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -253,7 +253,7 @@ return { emit }
|
|||
exports[`defineEmits > w/ type 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
@ -271,7 +271,7 @@ exports[`defineEmits > w/ type from normal script 1`] = `
|
|||
|
||||
export interface Emits { (e: 'foo' | 'bar'): void }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
emits: ["foo", "bar"],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {
|
||||
__expose();
|
||||
|
|
|
@ -29,7 +29,7 @@ return { modelValue, c, toString }
|
|||
exports[`defineModel() > get / set transformers 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": {
|
||||
required: true
|
||||
|
@ -54,7 +54,7 @@ return { modelValue }
|
|||
exports[`defineModel() > get / set transformers 2`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": {
|
||||
default: 0,
|
||||
|
@ -80,8 +80,8 @@ return { modelValue }
|
|||
exports[`defineModel() > usage w/ props destructure 1`] = `
|
||||
"import { useModel as _useModel, mergeModels as _mergeModels, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: /*#__PURE__*/_mergeModels({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: /*@__PURE__*/_mergeModels({
|
||||
x: { type: Number, required: true }
|
||||
}, {
|
||||
"modelValue": {
|
||||
|
@ -106,7 +106,7 @@ return { modelValue }
|
|||
exports[`defineModel() > w/ Boolean And Function types, production mode 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Boolean, String] },
|
||||
"modelModifiers": {},
|
||||
|
@ -127,7 +127,7 @@ exports[`defineModel() > w/ array props 1`] = `
|
|||
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
|
||||
|
||||
export default {
|
||||
props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
props: /*@__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
"count": {},
|
||||
"countModifiers": {},
|
||||
}),
|
||||
|
@ -148,11 +148,11 @@ exports[`defineModel() > w/ defineProps and defineEmits 1`] = `
|
|||
"import { useModel as _useModel, mergeModels as _mergeModels } from 'vue'
|
||||
|
||||
export default {
|
||||
props: /*#__PURE__*/_mergeModels({ foo: String }, {
|
||||
props: /*@__PURE__*/_mergeModels({ foo: String }, {
|
||||
"modelValue": { default: 0 },
|
||||
"modelModifiers": {},
|
||||
}),
|
||||
emits: /*#__PURE__*/_mergeModels(['change'], ["update:modelValue"]),
|
||||
emits: /*@__PURE__*/_mergeModels(['change'], ["update:modelValue"]),
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -169,7 +169,7 @@ return { count }
|
|||
exports[`defineModel() > w/ types, basic usage 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Boolean, String] },
|
||||
"modelModifiers": {},
|
||||
|
@ -198,7 +198,7 @@ return { modelValue, count, disabled, any }
|
|||
exports[`defineModel() > w/ types, production mode 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: Boolean },
|
||||
"modelModifiers": {},
|
||||
|
@ -230,7 +230,7 @@ return { modelValue, fn, fnWithDefault, str, optional }
|
|||
exports[`defineModel() > w/ types, production mode, boolean + multiple types 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Boolean, String, Object] },
|
||||
"modelModifiers": {},
|
||||
|
@ -250,7 +250,7 @@ return { modelValue }
|
|||
exports[`defineModel() > w/ types, production mode, function + runtime opts + multiple types 1`] = `
|
||||
"import { useModel as _useModel, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"modelValue": { type: [Number, Function], ...{ default: () => 1 } },
|
||||
"modelModifiers": {},
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`defineOptions() > basic usage 1`] = `
|
||||
"
|
||||
export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, {
|
||||
export default /*@__PURE__*/Object.assign({ name: 'FooApp' }, {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ interface Props {
|
|||
foo?: number;
|
||||
}
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
__name: 'app.ce',
|
||||
props: {
|
||||
foo: { default: 5.5, type: Number }
|
||||
|
@ -43,7 +43,7 @@ return { props }
|
|||
exports[`defineProps > custom element retains the props type & production mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
__name: 'app.ce',
|
||||
props: {
|
||||
foo: {type: Number}
|
||||
|
@ -62,7 +62,7 @@ return { props }
|
|||
exports[`defineProps > defineProps w/ runtime options 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: { foo: String },
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
@ -78,7 +78,7 @@ return { props }
|
|||
exports[`defineProps > destructure without enabling reactive destructure 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
foo: { type: null, required: true }
|
||||
},
|
||||
|
@ -96,7 +96,7 @@ return { foo }
|
|||
exports[`defineProps > should escape names w/ special symbols 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
"spa ce": { type: null, required: true },
|
||||
"exclamation!mark": { type: null, required: true },
|
||||
|
@ -141,7 +141,7 @@ return { }
|
|||
exports[`defineProps > w/ TS assertion 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: ['foo'],
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
@ -158,7 +158,7 @@ exports[`defineProps > w/ exported interface 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
export interface Props { x?: number }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
x: { type: Number, required: false }
|
||||
},
|
||||
|
@ -178,7 +178,7 @@ exports[`defineProps > w/ exported interface in normal script 1`] = `
|
|||
|
||||
export interface Props { x?: number }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
x: { type: Number, required: false }
|
||||
},
|
||||
|
@ -197,7 +197,7 @@ exports[`defineProps > w/ exported type alias 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
export type Props = { x?: number }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
x: { type: Number, required: false }
|
||||
},
|
||||
|
@ -222,7 +222,34 @@ interface Bar extends Foo { y?: number }
|
|||
|
||||
interface Foo { x?: number }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
z: { type: Number, required: true },
|
||||
y: { type: String, required: true },
|
||||
x: { type: Number, required: false }
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > w/ extends intersection type 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type Foo = {
|
||||
x?: number;
|
||||
};
|
||||
interface Props extends Foo {
|
||||
z: number
|
||||
y: string
|
||||
}
|
||||
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
z: { type: Number, required: true },
|
||||
y: { type: String, required: true },
|
||||
|
@ -259,7 +286,7 @@ exports[`defineProps > w/ interface 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Props { x?: number }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
x: { type: Number, required: false }
|
||||
},
|
||||
|
@ -268,6 +295,31 @@ export default /*#__PURE__*/_defineComponent({
|
|||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > w/ intersection type 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type Foo = {
|
||||
x?: number;
|
||||
};
|
||||
type Bar = {
|
||||
y: string;
|
||||
};
|
||||
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
x: { type: Number, required: false },
|
||||
y: { type: String, required: true }
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
|
||||
return { }
|
||||
}
|
||||
|
||||
|
@ -296,7 +348,7 @@ interface Test {}
|
|||
type Alias = number[]
|
||||
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
string: { type: String, required: true },
|
||||
number: { type: Number, required: true },
|
||||
|
@ -353,7 +405,7 @@ exports[`defineProps > w/ type alias 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type Props = { x?: number }
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
x: { type: Number, required: false }
|
||||
},
|
||||
|
@ -372,8 +424,8 @@ exports[`defineProps > withDefaults (dynamic) 1`] = `
|
|||
"import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from 'vue'
|
||||
import { defaults } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: /*@__PURE__*/_mergeDefaults({
|
||||
foo: { type: String, required: false },
|
||||
bar: { type: Number, required: false },
|
||||
baz: { type: Boolean, required: true }
|
||||
|
@ -393,8 +445,8 @@ exports[`defineProps > withDefaults (dynamic) w/ production mode 1`] = `
|
|||
"import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from 'vue'
|
||||
import { defaults } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: /*@__PURE__*/_mergeDefaults({
|
||||
foo: { type: Function },
|
||||
bar: { type: Boolean },
|
||||
baz: { type: [Boolean, Function] },
|
||||
|
@ -415,8 +467,8 @@ exports[`defineProps > withDefaults (reference) 1`] = `
|
|||
"import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from 'vue'
|
||||
import { defaults } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: /*@__PURE__*/_mergeDefaults({
|
||||
foo: { type: String, required: false },
|
||||
bar: { type: Number, required: false },
|
||||
baz: { type: Boolean, required: true }
|
||||
|
@ -439,7 +491,7 @@ exports[`defineProps > withDefaults (static) + normal script 1`] = `
|
|||
a?: string;
|
||||
}
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
a: { type: String, required: false, default: "a" }
|
||||
},
|
||||
|
@ -457,7 +509,7 @@ return { props }
|
|||
exports[`defineProps > withDefaults (static) 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
foo: { type: String, required: false, default: 'hi' },
|
||||
bar: { type: Number, required: false },
|
||||
|
@ -481,7 +533,7 @@ return { props }
|
|||
exports[`defineProps > withDefaults (static) w/ production mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
foo: {},
|
||||
bar: { type: Boolean },
|
||||
|
@ -502,8 +554,8 @@ return { props }
|
|||
exports[`defineProps > withDefaults w/ dynamic object method 1`] = `
|
||||
"import { mergeDefaults as _mergeDefaults, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
props: /*#__PURE__*/_mergeDefaults({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: /*@__PURE__*/_mergeDefaults({
|
||||
foo: { type: Function, required: false }
|
||||
}, {
|
||||
['fo' + 'o']() { return 'foo' }
|
||||
|
|
|
@ -62,7 +62,7 @@ exports[`sfc reactive props destructure > default values w/ array runtime declar
|
|||
"import { mergeDefaults as _mergeDefaults } from 'vue'
|
||||
|
||||
export default {
|
||||
props: /*#__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true
|
||||
|
@ -81,7 +81,7 @@ exports[`sfc reactive props destructure > default values w/ object runtime decla
|
|||
"import { mergeDefaults as _mergeDefaults } from 'vue'
|
||||
|
||||
export default {
|
||||
props: /*#__PURE__*/_mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
props: /*@__PURE__*/_mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true,
|
||||
|
@ -101,7 +101,7 @@ exports[`sfc reactive props destructure > default values w/ runtime declaration
|
|||
"import { mergeDefaults as _mergeDefaults } from 'vue'
|
||||
|
||||
export default {
|
||||
props: /*#__PURE__*/_mergeDefaults(['foo', 'foo:bar'], {
|
||||
props: /*@__PURE__*/_mergeDefaults(['foo', 'foo:bar'], {
|
||||
foo: 1,
|
||||
"foo:bar": 'foo-bar'
|
||||
}),
|
||||
|
@ -118,7 +118,7 @@ return () => {}
|
|||
exports[`sfc reactive props destructure > default values w/ type declaration & key is string 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
foo: { type: Number, required: true, default: 1 },
|
||||
bar: { type: Number, required: true, default: 2 },
|
||||
|
@ -138,7 +138,7 @@ return () => {}
|
|||
exports[`sfc reactive props destructure > default values w/ type declaration 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
foo: { type: Number, required: false, default: 1 },
|
||||
bar: { type: Object, required: false, default: () => ({}) },
|
||||
|
@ -157,7 +157,7 @@ return () => {}
|
|||
exports[`sfc reactive props destructure > default values w/ type declaration, prod mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
foo: { default: 1 },
|
||||
bar: { default: () => ({}) },
|
||||
|
@ -320,3 +320,22 @@ return { rest }
|
|||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`sfc reactive props destructure > with TSInstantiationExpression 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
type Foo = <T extends string | number>(data: T) => void
|
||||
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
props: {
|
||||
value: { type: Function }
|
||||
},
|
||||
setup(__props: any) {
|
||||
|
||||
|
||||
const foo = __props.value<123>
|
||||
|
||||
return () => {}
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
exports[`defineSlots() > basic usage 1`] = `
|
||||
"import { useSlots as _useSlots, defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -33,7 +33,7 @@ return { slots }
|
|||
exports[`defineSlots() > w/o return value 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
|
|
@ -71,7 +71,7 @@ return () => {}
|
|||
exports[`sfc hoist static > should not hoist a constant initialized to a reference value 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props) {
|
||||
|
||||
const KEY1 = Boolean
|
||||
|
|
|
@ -5,7 +5,7 @@ exports[`TS annotations 1`] = `
|
|||
import { Foo, Bar, Baz, Qux, Fred } from './x'
|
||||
const a = 1
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -22,7 +22,7 @@ exports[`attribute expressions 1`] = `
|
|||
import { bar, baz } from './x'
|
||||
const cond = true
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -38,7 +38,7 @@ exports[`components 1`] = `
|
|||
import { FooBar, FooBaz, FooQux, foo } from './x'
|
||||
const fooBar: FooBar = 1
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -53,7 +53,7 @@ exports[`directive 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { vMyDir } from './x'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -68,7 +68,7 @@ exports[`dynamic arguments 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { FooBar, foo, bar, unused, baz, msg } from './x'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -83,7 +83,7 @@ exports[`js template string interpolations 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { VAR, VAR2, VAR3 } from './x'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -98,7 +98,7 @@ exports[`last tag 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { FooBaz, Last } from './x'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -113,7 +113,7 @@ exports[`namespace / dot component usage 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import * as Foo from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -128,7 +128,7 @@ exports[`property access (whitespace) 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { Foo, Bar, Baz } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -143,7 +143,7 @@ exports[`property access 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { Foo, Bar, Baz } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -158,7 +158,7 @@ exports[`spread operator 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { Foo, Bar, Baz } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -173,7 +173,7 @@ exports[`template ref 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { foo, bar, Baz } from './foo'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
@ -188,7 +188,7 @@ exports[`vue interpolations 1`] = `
|
|||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
import { x, y, z, x$y } from './x'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
export default /*@__PURE__*/_defineComponent({
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ const emit = defineEmits(['a', 'b'])
|
|||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
|
||||
expect(content).toMatch(`export default /*@__PURE__*/_defineComponent({
|
||||
emits: ['a', 'b'],
|
||||
setup(__props, { expose: __expose, emit: __emit }) {`)
|
||||
expect(content).toMatch('const emit = __emit')
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('defineModel()', () => {
|
|||
`,
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels({ foo: String }`)
|
||||
expect(content).toMatch(`props: /*@__PURE__*/_mergeModels({ foo: String }`)
|
||||
expect(content).toMatch(`"modelValue": { default: 0 }`)
|
||||
expect(content).toMatch(`const count = _useModel(__props, "modelValue")`)
|
||||
expect(content).not.toMatch('defineModel')
|
||||
|
@ -68,7 +68,7 @@ describe('defineModel()', () => {
|
|||
`,
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
expect(content).toMatch(`props: /*@__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
"count": {},
|
||||
"countModifiers": {},
|
||||
})`)
|
||||
|
|
|
@ -12,7 +12,7 @@ describe('defineOptions()', () => {
|
|||
expect(content).not.toMatch('defineOptions')
|
||||
// should include context options in default export
|
||||
expect(content).toMatch(
|
||||
`export default /*#__PURE__*/Object.assign({ name: 'FooApp' }, `,
|
||||
`export default /*@__PURE__*/Object.assign({ name: 'FooApp' }, `,
|
||||
)
|
||||
})
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ const props = defineProps({ foo: String })
|
|||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`export default /*#__PURE__*/_defineComponent({
|
||||
expect(content).toMatch(`export default /*@__PURE__*/_defineComponent({
|
||||
props: { foo: String },
|
||||
setup(__props, { expose: __expose }) {`)
|
||||
})
|
||||
|
@ -261,6 +261,51 @@ const props = defineProps({ foo: String })
|
|||
})
|
||||
})
|
||||
|
||||
test('w/ extends intersection type', () => {
|
||||
const { content, bindings } = compile(`
|
||||
<script setup lang="ts">
|
||||
type Foo = {
|
||||
x?: number;
|
||||
};
|
||||
interface Props extends Foo {
|
||||
z: number
|
||||
y: string
|
||||
}
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`z: { type: Number, required: true }`)
|
||||
expect(content).toMatch(`y: { type: String, required: true }`)
|
||||
expect(content).toMatch(`x: { type: Number, required: false }`)
|
||||
expect(bindings).toStrictEqual({
|
||||
x: BindingTypes.PROPS,
|
||||
y: BindingTypes.PROPS,
|
||||
z: BindingTypes.PROPS,
|
||||
})
|
||||
})
|
||||
|
||||
test('w/ intersection type', () => {
|
||||
const { content, bindings } = compile(`
|
||||
<script setup lang="ts">
|
||||
type Foo = {
|
||||
x?: number;
|
||||
};
|
||||
type Bar = {
|
||||
y: string;
|
||||
};
|
||||
defineProps<Foo & Bar>()
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`y: { type: String, required: true }`)
|
||||
expect(content).toMatch(`x: { type: Number, required: false }`)
|
||||
expect(bindings).toStrictEqual({
|
||||
x: BindingTypes.PROPS,
|
||||
y: BindingTypes.PROPS,
|
||||
})
|
||||
})
|
||||
|
||||
test('w/ exported interface', () => {
|
||||
const { content, bindings } = compile(`
|
||||
<script setup lang="ts">
|
||||
|
|
|
@ -78,7 +78,7 @@ describe('sfc reactive props destructure', () => {
|
|||
// function
|
||||
// functions need to be marked with a skip marker
|
||||
expect(content)
|
||||
.toMatch(`props: /*#__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
.toMatch(`props: /*@__PURE__*/_mergeDefaults(['foo', 'bar', 'baz'], {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true
|
||||
|
@ -98,7 +98,7 @@ describe('sfc reactive props destructure', () => {
|
|||
// safely infer whether runtime type is Function (e.g. if the runtime decl
|
||||
// is imported, or spreads another object)
|
||||
expect(content)
|
||||
.toMatch(`props: /*#__PURE__*/_mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
.toMatch(`props: /*@__PURE__*/_mergeDefaults({ foo: Number, bar: Object, func: Function, ext: null }, {
|
||||
foo: 1,
|
||||
bar: () => ({}),
|
||||
func: () => {}, __skip_func: true,
|
||||
|
@ -122,7 +122,7 @@ describe('sfc reactive props destructure', () => {
|
|||
})
|
||||
|
||||
expect(content).toMatch(`
|
||||
props: /*#__PURE__*/_mergeDefaults(['foo', 'foo:bar'], {
|
||||
props: /*@__PURE__*/_mergeDefaults(['foo', 'foo:bar'], {
|
||||
foo: 1,
|
||||
"foo:bar": 'foo-bar'
|
||||
}),`)
|
||||
|
@ -198,6 +198,21 @@ describe('sfc reactive props destructure', () => {
|
|||
}`)
|
||||
})
|
||||
|
||||
test('with TSInstantiationExpression', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
<script setup lang="ts">
|
||||
type Foo = <T extends string | number>(data: T) => void
|
||||
const { value } = defineProps<{ value: Foo }>()
|
||||
const foo = value<123>
|
||||
</script>
|
||||
`,
|
||||
{ isProd: true },
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`const foo = __props.value<123>`)
|
||||
})
|
||||
|
||||
test('aliasing', () => {
|
||||
const { content, bindings } = compile(`
|
||||
<script setup>
|
||||
|
@ -378,14 +393,15 @@ describe('sfc reactive props destructure', () => {
|
|||
).toThrow(`destructure cannot use computed key`)
|
||||
})
|
||||
|
||||
test('should error when used with withDefaults', () => {
|
||||
expect(() =>
|
||||
test('should warn when used with withDefaults', () => {
|
||||
compile(
|
||||
`<script setup lang="ts">
|
||||
const { foo } = withDefaults(defineProps<{ foo: string }>(), { foo: 'foo' })
|
||||
</script>`,
|
||||
),
|
||||
).toThrow(`withDefaults() is unnecessary when using destructure`)
|
||||
)
|
||||
expect(
|
||||
`withDefaults() is unnecessary when using destructure`,
|
||||
).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('should error if destructure reference local vars', () => {
|
||||
|
|
|
@ -250,3 +250,18 @@ test('check when has explicit parse options', () => {
|
|||
)
|
||||
expect(content).toMatch('return { get x() { return x } }')
|
||||
})
|
||||
|
||||
// #11745
|
||||
test('shorthand binding w/ kebab-case', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
<script setup lang="ts">
|
||||
import { fooBar } from "./foo.ts"
|
||||
</script>
|
||||
<template>
|
||||
<div :foo-bar></div>
|
||||
</template>
|
||||
`,
|
||||
)
|
||||
expect(content).toMatch('return { get fooBar() { return fooBar }')
|
||||
})
|
||||
|
|
|
@ -41,6 +41,44 @@ describe('SFC scoped CSS', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('nesting selector', () => {
|
||||
expect(compileScoped(`h1 { color: red; .foo { color: red; } }`)).toMatch(
|
||||
`h1 {\n&[data-v-test] { color: red;\n}\n.foo[data-v-test] { color: red;`,
|
||||
)
|
||||
})
|
||||
|
||||
test('nesting selector with atrule and comment', () => {
|
||||
expect(
|
||||
compileScoped(
|
||||
`h1 {
|
||||
color: red;
|
||||
/*background-color: pink;*/
|
||||
@media only screen and (max-width: 800px) {
|
||||
background-color: green;
|
||||
.bar { color: white }
|
||||
}
|
||||
.foo { color: red; }
|
||||
}`,
|
||||
),
|
||||
).toMatch(
|
||||
`h1 {
|
||||
&[data-v-test] {
|
||||
color: red
|
||||
/*background-color: pink;*/
|
||||
}
|
||||
@media only screen and (max-width: 800px) {
|
||||
&[data-v-test] {
|
||||
background-color: green
|
||||
}
|
||||
.bar[data-v-test] { color: white
|
||||
}
|
||||
}
|
||||
.foo[data-v-test] { color: red;
|
||||
}
|
||||
}`,
|
||||
)
|
||||
})
|
||||
|
||||
test('multiple selectors', () => {
|
||||
expect(compileScoped(`h1 .foo, .bar, .baz { color: red; }`)).toMatch(
|
||||
`h1 .foo[data-v-test], .bar[data-v-test], .baz[data-v-test] { color: red;`,
|
||||
|
@ -95,6 +133,13 @@ describe('SFC scoped CSS', () => {
|
|||
":where(.foo[data-v-test] .bar) { color: red;
|
||||
}"
|
||||
`)
|
||||
expect(compileScoped(`:deep(.foo) { color: red; .bar { color: red; } }`))
|
||||
.toMatchInlineSnapshot(`
|
||||
"[data-v-test] .foo { color: red;
|
||||
.bar { color: red;
|
||||
}
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('::v-slotted', () => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
type SFCParseOptions,
|
||||
type SFCScriptBlock,
|
||||
type SFCScriptCompileOptions,
|
||||
compileScript,
|
||||
parse,
|
||||
|
@ -12,7 +13,7 @@ export function compileSFCScript(
|
|||
src: string,
|
||||
options?: Partial<SFCScriptCompileOptions>,
|
||||
parseOptions?: SFCParseOptions,
|
||||
) {
|
||||
): SFCScriptBlock {
|
||||
const { descriptor, errors } = parse(src, parseOptions)
|
||||
if (errors.length) {
|
||||
console.warn(errors[0])
|
||||
|
@ -23,7 +24,7 @@ export function compileSFCScript(
|
|||
})
|
||||
}
|
||||
|
||||
export function assertCode(code: string) {
|
||||
export function assertCode(code: string): void {
|
||||
// parse the generated code to make sure it is valid
|
||||
try {
|
||||
babelParse(code, {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.5.0-rc.1",
|
||||
"version": "3.5.13",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
@ -49,7 +49,7 @@
|
|||
"@vue/shared": "workspace:*",
|
||||
"estree-walker": "catalog:",
|
||||
"magic-string": "catalog:",
|
||||
"postcss": "^8.4.41",
|
||||
"postcss": "^8.4.49",
|
||||
"source-map-js": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -59,9 +59,9 @@
|
|||
"lru-cache": "10.1.0",
|
||||
"merge-source-map": "^1.1.0",
|
||||
"minimatch": "~9.0.5",
|
||||
"postcss-modules": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.1.2",
|
||||
"postcss-modules": "^6.0.1",
|
||||
"postcss-selector-parser": "^7.0.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.77.8"
|
||||
"sass": "^1.82.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -979,7 +979,7 @@ export function compileScript(
|
|||
(definedOptions ? `\n ...${definedOptions},` : '')
|
||||
ctx.s.prependLeft(
|
||||
startOffset,
|
||||
`\n${genDefaultAs} /*#__PURE__*/${ctx.helper(
|
||||
`\n${genDefaultAs} /*@__PURE__*/${ctx.helper(
|
||||
`defineComponent`,
|
||||
)}({${def}${runtimeOptions}\n ${
|
||||
hasAwait ? `async ` : ``
|
||||
|
@ -992,7 +992,7 @@ export function compileScript(
|
|||
// export default Object.assign(__default__, { ... })
|
||||
ctx.s.prependLeft(
|
||||
startOffset,
|
||||
`\n${genDefaultAs} /*#__PURE__*/Object.assign(${
|
||||
`\n${genDefaultAs} /*@__PURE__*/Object.assign(${
|
||||
defaultExport ? `${normalScriptDefaultVar}, ` : ''
|
||||
}${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
|
||||
`${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
|
||||
|
|
|
@ -18,6 +18,7 @@ import { createCache } from './cache'
|
|||
import type { ImportBinding } from './compileScript'
|
||||
import { isImportUsed } from './script/importUsageCheck'
|
||||
import type { LRUCache } from 'lru-cache'
|
||||
import { genCacheKey } from '@vue/shared'
|
||||
|
||||
export const DEFAULT_FILENAME = 'anonymous.vue'
|
||||
|
||||
|
@ -103,24 +104,14 @@ export const parseCache:
|
|||
| Map<string, SFCParseResult>
|
||||
| LRUCache<string, SFCParseResult> = createCache<SFCParseResult>()
|
||||
|
||||
function genCacheKey(source: string, options: SFCParseOptions): string {
|
||||
return (
|
||||
source +
|
||||
JSON.stringify(
|
||||
{
|
||||
...options,
|
||||
compiler: { parse: options.compiler?.parse },
|
||||
},
|
||||
(_, val) => (typeof val === 'function' ? val.toString() : val),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export function parse(
|
||||
source: string,
|
||||
options: SFCParseOptions = {},
|
||||
): SFCParseResult {
|
||||
const sourceKey = genCacheKey(source, options)
|
||||
const sourceKey = genCacheKey(source, {
|
||||
...options,
|
||||
compiler: { parse: options.compiler?.parse },
|
||||
})
|
||||
const cache = parseCache.get(sourceKey)
|
||||
if (cache) {
|
||||
return cache
|
||||
|
|
|
@ -8,6 +8,7 @@ import type { ModelDecl } from './defineModel'
|
|||
import type { BindingMetadata } from '../../../compiler-core/src'
|
||||
import MagicString from 'magic-string'
|
||||
import type { TypeScope } from './resolveType'
|
||||
import { warn } from '../warn'
|
||||
|
||||
export class ScriptCompileContext {
|
||||
isJS: boolean
|
||||
|
@ -145,20 +146,31 @@ export class ScriptCompileContext {
|
|||
return block.content.slice(node.start!, node.end!)
|
||||
}
|
||||
|
||||
warn(msg: string, node: Node, scope?: TypeScope): void {
|
||||
warn(generateError(msg, node, this, scope))
|
||||
}
|
||||
|
||||
error(msg: string, node: Node, scope?: TypeScope): never {
|
||||
const offset = scope ? scope.offset : this.startOffset!
|
||||
throw new Error(
|
||||
`[@vue/compiler-sfc] ${msg}\n\n${
|
||||
(scope || this.descriptor).filename
|
||||
}\n${generateCodeFrame(
|
||||
(scope || this.descriptor).source,
|
||||
node.start! + offset,
|
||||
node.end! + offset,
|
||||
)}`,
|
||||
`[@vue/compiler-sfc] ${generateError(msg, node, this, scope)}`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function generateError(
|
||||
msg: string,
|
||||
node: Node,
|
||||
ctx: ScriptCompileContext,
|
||||
scope?: TypeScope,
|
||||
) {
|
||||
const offset = scope ? scope.offset : ctx.startOffset!
|
||||
return `${msg}\n\n${(scope || ctx.descriptor).filename}\n${generateCodeFrame(
|
||||
(scope || ctx.descriptor).source,
|
||||
node.start! + offset,
|
||||
node.end! + offset,
|
||||
)}`
|
||||
}
|
||||
|
||||
export function resolveParserPlugins(
|
||||
lang: string,
|
||||
userPlugins?: ParserPlugin[],
|
||||
|
|
|
@ -62,7 +62,7 @@ export function genRuntimeEmits(ctx: ScriptCompileContext): string | undefined {
|
|||
.map(n => JSON.stringify(`update:${n}`))
|
||||
.join(', ')}]`
|
||||
emitsDecl = emitsDecl
|
||||
? `/*#__PURE__*/${ctx.helper(
|
||||
? `/*@__PURE__*/${ctx.helper(
|
||||
'mergeModels',
|
||||
)}(${emitsDecl}, ${modelEmitsDecl})`
|
||||
: modelEmitsDecl
|
||||
|
|
|
@ -48,6 +48,7 @@ export function processDefineProps(
|
|||
ctx: ScriptCompileContext,
|
||||
node: Node,
|
||||
declId?: LVal,
|
||||
isWithDefaults = false,
|
||||
): boolean {
|
||||
if (!isCallOf(node, DEFINE_PROPS)) {
|
||||
return processWithDefaults(ctx, node, declId)
|
||||
|
@ -81,7 +82,7 @@ export function processDefineProps(
|
|||
}
|
||||
|
||||
// handle props destructure
|
||||
if (declId && declId.type === 'ObjectPattern') {
|
||||
if (!isWithDefaults && declId && declId.type === 'ObjectPattern') {
|
||||
processPropsDestructure(ctx, declId)
|
||||
}
|
||||
|
||||
|
@ -99,7 +100,14 @@ function processWithDefaults(
|
|||
if (!isCallOf(node, WITH_DEFAULTS)) {
|
||||
return false
|
||||
}
|
||||
if (!processDefineProps(ctx, node.arguments[0], declId)) {
|
||||
if (
|
||||
!processDefineProps(
|
||||
ctx,
|
||||
node.arguments[0],
|
||||
declId,
|
||||
true /* isWithDefaults */,
|
||||
)
|
||||
) {
|
||||
ctx.error(
|
||||
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
||||
node.arguments[0] || node,
|
||||
|
@ -113,9 +121,10 @@ function processWithDefaults(
|
|||
node,
|
||||
)
|
||||
}
|
||||
if (ctx.propsDestructureDecl) {
|
||||
ctx.error(
|
||||
if (declId && declId.type === 'ObjectPattern') {
|
||||
ctx.warn(
|
||||
`${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +
|
||||
`Reactive destructure will be disabled when using withDefaults().\n` +
|
||||
`Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...). `,
|
||||
node.callee,
|
||||
)
|
||||
|
@ -147,7 +156,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
|||
)
|
||||
}
|
||||
if (defaults.length) {
|
||||
propsDecls = `/*#__PURE__*/${ctx.helper(
|
||||
propsDecls = `/*@__PURE__*/${ctx.helper(
|
||||
`mergeDefaults`,
|
||||
)}(${propsDecls}, {\n ${defaults.join(',\n ')}\n})`
|
||||
}
|
||||
|
@ -159,7 +168,7 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
|
|||
const modelsDecls = genModelProps(ctx)
|
||||
|
||||
if (propsDecls && modelsDecls) {
|
||||
return `/*#__PURE__*/${ctx.helper(
|
||||
return `/*@__PURE__*/${ctx.helper(
|
||||
'mergeModels',
|
||||
)}(${propsDecls}, ${modelsDecls})`
|
||||
} else {
|
||||
|
@ -191,7 +200,7 @@ export function extractRuntimeProps(
|
|||
${propStrings.join(',\n ')}\n }`
|
||||
|
||||
if (ctx.propsRuntimeDefaults && !hasStaticDefaults) {
|
||||
propsDecls = `/*#__PURE__*/${ctx.helper(
|
||||
propsDecls = `/*@__PURE__*/${ctx.helper(
|
||||
'mergeDefaults',
|
||||
)}(${propsDecls}, ${ctx.getString(ctx.propsRuntimeDefaults)})`
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import type {
|
|||
import { walk } from 'estree-walker'
|
||||
import {
|
||||
BindingTypes,
|
||||
TS_NODE_TYPES,
|
||||
extractIdentifiers,
|
||||
isFunctionType,
|
||||
isInDestructureAssignment,
|
||||
|
@ -102,7 +103,7 @@ export function transformDestructuredProps(
|
|||
return
|
||||
}
|
||||
|
||||
const rootScope: Scope = {}
|
||||
const rootScope: Scope = Object.create(null)
|
||||
const scopeStack: Scope[] = [rootScope]
|
||||
let currentScope: Scope = rootScope
|
||||
const excludedIds = new WeakSet<Identifier>()
|
||||
|
@ -240,9 +241,7 @@ export function transformDestructuredProps(
|
|||
if (
|
||||
parent &&
|
||||
parent.type.startsWith('TS') &&
|
||||
parent.type !== 'TSAsExpression' &&
|
||||
parent.type !== 'TSNonNullExpression' &&
|
||||
parent.type !== 'TSTypeAssertion'
|
||||
!TS_NODE_TYPES.includes(parent.type)
|
||||
) {
|
||||
return this.skip()
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ function resolveTemplateUsedIdentifiers(sfc: SFCDescriptor): Set<string> {
|
|||
extractIdentifiers(ids, prop.exp)
|
||||
} else if (prop.name === 'bind' && !prop.exp) {
|
||||
// v-bind shorthand name as identifier
|
||||
ids.add((prop.arg as SimpleExpressionNode).content)
|
||||
ids.add(camelize((prop.arg as SimpleExpressionNode).content))
|
||||
}
|
||||
}
|
||||
if (
|
||||
|
|
|
@ -121,15 +121,3 @@ export const propNameEscapeSymbolsRE: RegExp =
|
|||
export function getEscapedPropName(key: string): string {
|
||||
return propNameEscapeSymbolsRE.test(key) ? JSON.stringify(key) : key
|
||||
}
|
||||
|
||||
export const cssVarNameEscapeSymbolsRE: RegExp =
|
||||
/[ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g
|
||||
|
||||
export function getEscapedCssVarName(
|
||||
key: string,
|
||||
doubleEscape: boolean,
|
||||
): string {
|
||||
return key.replace(cssVarNameEscapeSymbolsRE, s =>
|
||||
doubleEscape ? `\\\\${s}` : `\\${s}`,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -8,9 +8,9 @@ import {
|
|||
processExpression,
|
||||
} from '@vue/compiler-dom'
|
||||
import type { SFCDescriptor } from '../parse'
|
||||
import { getEscapedCssVarName } from '../script/utils'
|
||||
import type { PluginCreator } from 'postcss'
|
||||
import hash from 'hash-sum'
|
||||
import { getEscapedCssVarName } from '@vue/shared'
|
||||
|
||||
export const CSS_VARS_HELPER = `useCssVars`
|
||||
|
||||
|
|
|
@ -1,4 +1,10 @@
|
|||
import type { AtRule, PluginCreator, Rule } from 'postcss'
|
||||
import {
|
||||
type AtRule,
|
||||
type Container,
|
||||
type Document,
|
||||
type PluginCreator,
|
||||
Rule,
|
||||
} from 'postcss'
|
||||
import selectorParser from 'postcss-selector-parser'
|
||||
import { warn } from '../warn'
|
||||
|
||||
|
@ -71,21 +77,32 @@ function processRule(id: string, rule: Rule) {
|
|||
return
|
||||
}
|
||||
processedRules.add(rule)
|
||||
let deep = false
|
||||
let parent: Document | Container | undefined = rule.parent
|
||||
while (parent && parent.type !== 'root') {
|
||||
if ((parent as any).__deep) {
|
||||
deep = true
|
||||
break
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
rule.selector = selectorParser(selectorRoot => {
|
||||
selectorRoot.each(selector => {
|
||||
rewriteSelector(id, selector, selectorRoot)
|
||||
rewriteSelector(id, rule, selector, selectorRoot, deep)
|
||||
})
|
||||
}).processSync(rule.selector)
|
||||
}
|
||||
|
||||
function rewriteSelector(
|
||||
id: string,
|
||||
rule: Rule,
|
||||
selector: selectorParser.Selector,
|
||||
selectorRoot: selectorParser.Root,
|
||||
deep: boolean,
|
||||
slotted = false,
|
||||
) {
|
||||
let node: selectorParser.Node | null = null
|
||||
let shouldInject = true
|
||||
let shouldInject = !deep
|
||||
// find the last child node to insert attribute selector
|
||||
selector.each(n => {
|
||||
// DEPRECATED ">>>" and "/deep/" combinator
|
||||
|
@ -107,6 +124,7 @@ function rewriteSelector(
|
|||
// deep: inject [id] attribute at the node before the ::v-deep
|
||||
// combinator.
|
||||
if (value === ':deep' || value === '::v-deep') {
|
||||
;(rule as any).__deep = true
|
||||
if (n.nodes.length) {
|
||||
// .foo ::v-deep(.bar) -> .foo[xxxxxxx] .bar
|
||||
// replace the current node with ::v-deep's inner selector
|
||||
|
@ -147,7 +165,14 @@ function rewriteSelector(
|
|||
// instead.
|
||||
// ::v-slotted(.foo) -> .foo[xxxxxxx-s]
|
||||
if (value === ':slotted' || value === '::v-slotted') {
|
||||
rewriteSelector(id, n.nodes[0], selectorRoot, true /* slotted */)
|
||||
rewriteSelector(
|
||||
id,
|
||||
rule,
|
||||
n.nodes[0],
|
||||
selectorRoot,
|
||||
deep,
|
||||
true /* slotted */,
|
||||
)
|
||||
let last: selectorParser.Selector['nodes'][0] = n
|
||||
n.nodes[0].each(ss => {
|
||||
selector.insertAfter(last, ss)
|
||||
|
@ -164,8 +189,7 @@ function rewriteSelector(
|
|||
// global: replace with inner selector and do not inject [id].
|
||||
// ::v-global(.foo) -> .foo
|
||||
if (value === ':global' || value === '::v-global') {
|
||||
selectorRoot.insertAfter(selector, n.nodes[0])
|
||||
selectorRoot.removeChild(selector)
|
||||
selector.replaceWith(n.nodes[0])
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
@ -206,11 +230,23 @@ function rewriteSelector(
|
|||
}
|
||||
})
|
||||
|
||||
if (rule.nodes.some(node => node.type === 'rule')) {
|
||||
const deep = (rule as any).__deep
|
||||
if (!deep) {
|
||||
extractAndWrapNodes(rule)
|
||||
const atruleNodes = rule.nodes.filter(node => node.type === 'atrule')
|
||||
for (const atnode of atruleNodes) {
|
||||
extractAndWrapNodes(atnode)
|
||||
}
|
||||
}
|
||||
shouldInject = deep
|
||||
}
|
||||
|
||||
if (node) {
|
||||
const { type, value } = node as selectorParser.Node
|
||||
if (type === 'pseudo' && (value === ':is' || value === ':where')) {
|
||||
;(node as selectorParser.Pseudo).nodes.forEach(value =>
|
||||
rewriteSelector(id, value, selectorRoot, slotted),
|
||||
rewriteSelector(id, rule, value, selectorRoot, deep, slotted),
|
||||
)
|
||||
shouldInject = false
|
||||
}
|
||||
|
@ -245,5 +281,22 @@ function isSpaceCombinator(node: selectorParser.Node) {
|
|||
return node.type === 'combinator' && /^\s+$/.test(node.value)
|
||||
}
|
||||
|
||||
function extractAndWrapNodes(parentNode: Rule | AtRule) {
|
||||
if (!parentNode.nodes) return
|
||||
const nodes = parentNode.nodes.filter(
|
||||
node => node.type === 'decl' || node.type === 'comment',
|
||||
)
|
||||
if (nodes.length) {
|
||||
for (const node of nodes) {
|
||||
parentNode.removeChild(node)
|
||||
}
|
||||
const wrappedRule = new Rule({
|
||||
nodes: nodes,
|
||||
selector: '&',
|
||||
})
|
||||
parentNode.prepend(wrappedRule)
|
||||
}
|
||||
}
|
||||
|
||||
scopedPlugin.postcss = true
|
||||
export default scopedPlugin
|
||||
|
|
|
@ -23,28 +23,48 @@ export interface StylePreprocessorResults {
|
|||
|
||||
// .scss/.sass processor
|
||||
const scss: StylePreprocessor = (source, map, options, load = require) => {
|
||||
const nodeSass = load('sass')
|
||||
const finalOptions = {
|
||||
const nodeSass: typeof import('sass') = load('sass')
|
||||
const { compileString, renderSync } = nodeSass
|
||||
|
||||
const data = getSource(source, options.filename, options.additionalData)
|
||||
let css: string
|
||||
let dependencies: string[]
|
||||
let sourceMap: any
|
||||
|
||||
try {
|
||||
if (compileString) {
|
||||
const { pathToFileURL, fileURLToPath }: typeof import('url') = load('url')
|
||||
|
||||
const result = compileString(data, {
|
||||
...options,
|
||||
data: getSource(source, options.filename, options.additionalData),
|
||||
url: pathToFileURL(options.filename),
|
||||
sourceMap: !!map,
|
||||
})
|
||||
css = result.css
|
||||
dependencies = result.loadedUrls.map(url => fileURLToPath(url))
|
||||
sourceMap = map ? result.sourceMap! : undefined
|
||||
} else {
|
||||
const result = renderSync({
|
||||
...options,
|
||||
data,
|
||||
file: options.filename,
|
||||
outFile: options.filename,
|
||||
sourceMap: !!map,
|
||||
})
|
||||
css = result.css.toString()
|
||||
dependencies = result.stats.includedFiles
|
||||
sourceMap = map ? JSON.parse(result.map!.toString()) : undefined
|
||||
}
|
||||
|
||||
try {
|
||||
const result = nodeSass.renderSync(finalOptions)
|
||||
const dependencies = result.stats.includedFiles
|
||||
if (map) {
|
||||
return {
|
||||
code: result.css.toString(),
|
||||
map: merge(map, JSON.parse(result.map.toString())),
|
||||
code: css,
|
||||
errors: [],
|
||||
dependencies,
|
||||
map: merge(map, sourceMap!),
|
||||
}
|
||||
}
|
||||
|
||||
return { code: result.css.toString(), errors: [], dependencies }
|
||||
return { code: css, errors: [], dependencies }
|
||||
} catch (e: any) {
|
||||
return { code: '', errors: [e], dependencies: [] }
|
||||
}
|
||||
|
|
|
@ -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', () => {
|
||||
expect(getCompiledString(`<div v-bind="x" v-xxx />`))
|
||||
.toMatchInlineSnapshot(`
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('transition-group', () => {
|
|||
})
|
||||
|
||||
// #11514
|
||||
test('with static tag + comment', () => {
|
||||
test('with static tag + v-if comment', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<transition-group tag="ul"><div v-for="i in list"/><div v-if="false"></div></transition-group>`,
|
||||
|
@ -60,6 +60,25 @@ describe('transition-group', () => {
|
|||
`)
|
||||
})
|
||||
|
||||
// #11958
|
||||
test('with static tag + comment', () => {
|
||||
expect(
|
||||
compile(
|
||||
`<transition-group tag="ul"><div v-for="i in list"/><!--test--></transition-group>`,
|
||||
).code,
|
||||
).toMatchInlineSnapshot(`
|
||||
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require("vue/server-renderer")
|
||||
|
||||
return function ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(\`<ul\${_ssrRenderAttrs(_attrs)}>\`)
|
||||
_ssrRenderList(_ctx.list, (i) => {
|
||||
_push(\`<div></div>\`)
|
||||
})
|
||||
_push(\`</ul>\`)
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('with dynamic tag', () => {
|
||||
expect(
|
||||
compile(
|
||||
|
|
|
@ -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(
|
||||
compileWithWrapper(
|
||||
`<select multiple v-model="model"><option value="1" selected></option><option value="2"></option></select>`,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.5.0-rc.1",
|
||||
"version": "3.5.13",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -156,7 +156,7 @@ export function processChildren(
|
|||
context: SSRTransformContext,
|
||||
asFragment = false,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
disableComment = false,
|
||||
): void {
|
||||
if (asFragment) {
|
||||
context.pushStringPart(`<!--[-->`)
|
||||
|
@ -197,7 +197,9 @@ export function processChildren(
|
|||
case NodeTypes.COMMENT:
|
||||
// no need to escape comment here because the AST can only
|
||||
// contain valid comments.
|
||||
if (!disableComment) {
|
||||
context.pushStringPart(`<!--${child.content}-->`)
|
||||
}
|
||||
break
|
||||
case NodeTypes.INTERPOLATION:
|
||||
context.pushStringPart(
|
||||
|
@ -207,12 +209,7 @@ export function processChildren(
|
|||
)
|
||||
break
|
||||
case NodeTypes.IF:
|
||||
ssrProcessIf(
|
||||
child,
|
||||
context,
|
||||
disableNestedFragments,
|
||||
disableCommentAsIfAlternate,
|
||||
)
|
||||
ssrProcessIf(child, context, disableNestedFragments, disableComment)
|
||||
break
|
||||
case NodeTypes.FOR:
|
||||
ssrProcessFor(child, context, disableNestedFragments)
|
||||
|
|
|
@ -28,6 +28,7 @@ import {
|
|||
createSequenceExpression,
|
||||
createSimpleExpression,
|
||||
createTemplateLiteral,
|
||||
findDir,
|
||||
hasDynamicKeyVBind,
|
||||
isStaticArgOf,
|
||||
isStaticExp,
|
||||
|
@ -164,6 +165,9 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||
]
|
||||
}
|
||||
} else if (directives.length && !node.children.length) {
|
||||
// v-text directive has higher priority than the merged props
|
||||
const vText = findDir(node, 'text')
|
||||
if (!vText) {
|
||||
const tempId = `_temp${context.temps++}`
|
||||
propsExp.arguments = [
|
||||
createAssignmentExpression(
|
||||
|
@ -183,6 +187,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
|
|||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (needTagForRuntime) {
|
||||
propsExp.arguments.push(`"${node.tag}"`)
|
||||
|
|
|
@ -27,7 +27,7 @@ export function ssrProcessIf(
|
|||
node: IfNode,
|
||||
context: SSRTransformContext,
|
||||
disableNestedFragments = false,
|
||||
disableCommentAsIfAlternate = false,
|
||||
disableComment = false,
|
||||
): void {
|
||||
const [rootBranch] = node.branches
|
||||
const ifStatement = createIfStatement(
|
||||
|
@ -56,7 +56,7 @@ export function ssrProcessIf(
|
|||
}
|
||||
}
|
||||
|
||||
if (!currentIf.alternate && !disableCommentAsIfAlternate) {
|
||||
if (!currentIf.alternate && !disableComment) {
|
||||
currentIf.alternate = createBlockStatement([
|
||||
createCallExpression(`_push`, ['`<!---->`']),
|
||||
])
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
type ExpressionNode,
|
||||
NodeTypes,
|
||||
type PlainElementNode,
|
||||
type TemplateChildNode,
|
||||
createCallExpression,
|
||||
createConditionalExpression,
|
||||
createDOMCompilerError,
|
||||
|
@ -162,11 +163,18 @@ export const ssrTransformModel: DirectiveTransform = (dir, node, context) => {
|
|||
checkDuplicatedValue()
|
||||
node.children = [createInterpolation(model, model.loc)]
|
||||
} else if (node.tag === 'select') {
|
||||
node.children.forEach(child => {
|
||||
const processChildren = (children: TemplateChildNode[]) => {
|
||||
children.forEach(child => {
|
||||
if (child.type === NodeTypes.ELEMENT) {
|
||||
processOption(child as PlainElementNode)
|
||||
} else if (child.type === NodeTypes.FOR) {
|
||||
processChildren(child.children)
|
||||
} else if (child.type === NodeTypes.IF) {
|
||||
child.branches.forEach(b => processChildren(b.children))
|
||||
}
|
||||
})
|
||||
}
|
||||
processChildren(node.children)
|
||||
} else {
|
||||
context.onError(
|
||||
createDOMCompilerError(
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
/// <reference types="vite/client" />
|
||||
|
||||
// Global compile-time constants
|
||||
declare var __DEV__: boolean
|
||||
declare var __TEST__: boolean
|
||||
|
@ -9,7 +7,6 @@ declare var __ESM_BUNDLER__: boolean
|
|||
declare var __ESM_BROWSER__: boolean
|
||||
declare var __CJS__: boolean
|
||||
declare var __SSR__: boolean
|
||||
declare var __COMMIT__: string
|
||||
declare var __VERSION__: string
|
||||
declare var __COMPAT__: boolean
|
||||
|
||||
|
@ -21,10 +18,6 @@ declare var __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: boolean
|
|||
|
||||
declare module '*.vue' {}
|
||||
|
||||
declare module 'file-saver' {
|
||||
export function saveAs(blob: any, name: any): void
|
||||
}
|
||||
|
||||
declare module 'estree-walker' {
|
||||
export function walk<T>(
|
||||
root: T,
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
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', () => {
|
||||
bench('create computed', () => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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', () => {
|
||||
{
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
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) {
|
||||
{
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
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>) {
|
||||
const map = new Map()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { bench } from 'vitest'
|
||||
import { reactive } from '../src'
|
||||
import { reactive } from '../dist/reactivity.esm-browser.prod'
|
||||
|
||||
bench('create reactive obj', () => {
|
||||
reactive({ a: 1 })
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { bench, describe } from 'vitest'
|
||||
import { ref } from '../src/index'
|
||||
import { ref } from '../dist/reactivity.esm-browser.prod'
|
||||
|
||||
describe('ref', () => {
|
||||
bench('create ref', () => {
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
type TestElement,
|
||||
defineComponent,
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
|
@ -6,6 +8,7 @@ import {
|
|||
onUnmounted,
|
||||
render,
|
||||
serializeInner,
|
||||
triggerEvent,
|
||||
} from '@vue/runtime-test'
|
||||
import {
|
||||
type DebuggerEvent,
|
||||
|
@ -20,6 +23,7 @@ import {
|
|||
ref,
|
||||
shallowRef,
|
||||
toRaw,
|
||||
triggerRef,
|
||||
} from '../src'
|
||||
import { EffectFlags, pauseTracking, resetTracking } from '../src/effect'
|
||||
import type { ComputedRef, ComputedRefImpl } from '../src/computed'
|
||||
|
@ -33,6 +37,20 @@ describe('reactivity/computed', () => {
|
|||
expect(cValue.value).toBe(1)
|
||||
})
|
||||
|
||||
it('pass oldValue to computed getter', () => {
|
||||
const count = ref(0)
|
||||
const oldValue = ref()
|
||||
const curValue = computed(pre => {
|
||||
oldValue.value = pre
|
||||
return count.value
|
||||
})
|
||||
expect(curValue.value).toBe(0)
|
||||
expect(oldValue.value).toBe(undefined)
|
||||
count.value++
|
||||
expect(curValue.value).toBe(1)
|
||||
expect(oldValue.value).toBe(0)
|
||||
})
|
||||
|
||||
it('should compute lazily', () => {
|
||||
const value = reactive<{ foo?: number }>({})
|
||||
const getter = vi.fn(() => value.foo)
|
||||
|
@ -577,7 +595,7 @@ describe('reactivity/computed', () => {
|
|||
|
||||
v.value += ' World'
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe('Hello World World World')
|
||||
expect(serializeInner(root)).toBe('Hello World World World World')
|
||||
// expect(COMPUTED_SIDE_EFFECT_WARN).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
|
@ -875,7 +893,7 @@ describe('reactivity/computed', () => {
|
|||
v.value += ' World'
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
'Hello World World World | Hello World World World',
|
||||
'Hello World World World World | Hello World World World World',
|
||||
)
|
||||
})
|
||||
|
||||
|
@ -944,4 +962,181 @@ describe('reactivity/computed', () => {
|
|||
newValue: 2,
|
||||
})
|
||||
})
|
||||
|
||||
// #11797
|
||||
test('should prevent endless recursion in self-referencing computed getters', async () => {
|
||||
const Comp = defineComponent({
|
||||
data() {
|
||||
return {
|
||||
counter: 0,
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
message(): string {
|
||||
if (this.counter === 0) {
|
||||
this.counter++
|
||||
return this.message
|
||||
} else {
|
||||
return `Step ${this.counter}`
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
render() {
|
||||
return [
|
||||
h(
|
||||
'button',
|
||||
{
|
||||
onClick: () => {
|
||||
this.counter++
|
||||
},
|
||||
},
|
||||
'Step',
|
||||
),
|
||||
h('p', this.message),
|
||||
]
|
||||
},
|
||||
})
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(`<button>Step</button><p>Step 1</p>`)
|
||||
triggerEvent(root.children[1] as TestElement, 'click')
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<button>Step</button><p>Step 2</p>`)
|
||||
})
|
||||
|
||||
test('manual trigger computed', () => {
|
||||
const cValue = computed(() => 1)
|
||||
triggerRef(cValue)
|
||||
expect(cValue.value).toBe(1)
|
||||
})
|
||||
|
||||
test('computed should remain live after losing all subscribers', () => {
|
||||
const state = reactive({ a: 1 })
|
||||
const p = computed(() => state.a + 1)
|
||||
const { effect: e } = effect(() => p.value)
|
||||
e.stop()
|
||||
|
||||
expect(p.value).toBe(2)
|
||||
state.a++
|
||||
expect(p.value).toBe(3)
|
||||
})
|
||||
|
||||
// #11995
|
||||
test('computed dep cleanup should not cause property dep to be deleted', () => {
|
||||
const toggle = ref(true)
|
||||
const state = reactive({ a: 1 })
|
||||
const p = computed(() => {
|
||||
return toggle.value ? state.a : 111
|
||||
})
|
||||
const pp = computed(() => state.a)
|
||||
effect(() => p.value)
|
||||
|
||||
expect(pp.value).toBe(1)
|
||||
toggle.value = false
|
||||
state.a++
|
||||
expect(pp.value).toBe(2)
|
||||
})
|
||||
|
||||
// #12020
|
||||
test('computed value updates correctly after dep cleanup', () => {
|
||||
const obj = reactive({ foo: 1, flag: 1 })
|
||||
const c1 = computed(() => obj.foo)
|
||||
|
||||
let foo
|
||||
effect(() => {
|
||||
foo = obj.flag ? (obj.foo, c1.value) : 0
|
||||
})
|
||||
expect(foo).toBe(1)
|
||||
|
||||
obj.flag = 0
|
||||
expect(foo).toBe(0)
|
||||
|
||||
obj.foo = 2
|
||||
obj.flag = 1
|
||||
expect(foo).toBe(2)
|
||||
})
|
||||
|
||||
// #11928
|
||||
test('should not lead to exponential perf cost with deeply chained computed', () => {
|
||||
const start = {
|
||||
prop1: shallowRef(1),
|
||||
prop2: shallowRef(2),
|
||||
prop3: shallowRef(3),
|
||||
prop4: shallowRef(4),
|
||||
}
|
||||
|
||||
let layer = start
|
||||
|
||||
const LAYERS = 1000
|
||||
|
||||
for (let i = LAYERS; i > 0; i--) {
|
||||
const m = layer
|
||||
const s = {
|
||||
prop1: computed(() => m.prop2.value),
|
||||
prop2: computed(() => m.prop1.value - m.prop3.value),
|
||||
prop3: computed(() => m.prop2.value + m.prop4.value),
|
||||
prop4: computed(() => m.prop3.value),
|
||||
}
|
||||
effect(() => s.prop1.value)
|
||||
effect(() => s.prop2.value)
|
||||
effect(() => s.prop3.value)
|
||||
effect(() => s.prop4.value)
|
||||
|
||||
s.prop1.value
|
||||
s.prop2.value
|
||||
s.prop3.value
|
||||
s.prop4.value
|
||||
|
||||
layer = s
|
||||
}
|
||||
|
||||
const t = performance.now()
|
||||
start.prop1.value = 4
|
||||
start.prop2.value = 3
|
||||
start.prop3.value = 2
|
||||
start.prop4.value = 1
|
||||
expect(performance.now() - t).toBeLessThan(process.env.CI ? 100 : 30)
|
||||
|
||||
const end = layer
|
||||
expect([
|
||||
end.prop1.value,
|
||||
end.prop2.value,
|
||||
end.prop3.value,
|
||||
end.prop4.value,
|
||||
]).toMatchObject([-2, -4, 2, 3])
|
||||
})
|
||||
|
||||
test('performance when removing dependencies from deeply nested computeds', () => {
|
||||
const base = ref(1)
|
||||
const trigger = ref(true)
|
||||
const computeds: ComputedRef<number>[] = []
|
||||
|
||||
const LAYERS = 30
|
||||
|
||||
for (let i = 0; i < LAYERS; i++) {
|
||||
const earlier = [...computeds]
|
||||
|
||||
computeds.push(
|
||||
computed(() => {
|
||||
return base.value + earlier.reduce((sum, c) => sum + c.value, 0)
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
const tail = computed(() =>
|
||||
trigger.value ? computeds[computeds.length - 1].value : 0,
|
||||
)
|
||||
|
||||
const t0 = performance.now()
|
||||
expect(tail.value).toBe(2 ** (LAYERS - 1))
|
||||
const t1 = performance.now()
|
||||
expect(t1 - t0).toBeLessThan(process.env.CI ? 100 : 30)
|
||||
|
||||
trigger.value = false
|
||||
expect(tail.value).toBe(0)
|
||||
const t2 = performance.now()
|
||||
expect(t2 - t1).toBeLessThan(process.env.CI ? 100 : 30)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -176,7 +176,7 @@ describe('reactivity/effect/scope', () => {
|
|||
|
||||
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
|
||||
expect(dummy).toBe(0)
|
||||
|
@ -322,4 +322,44 @@ describe('reactivity/effect/scope', () => {
|
|||
scope.resume()
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from '../src/reactive'
|
||||
import { computed } from '../src/computed'
|
||||
import { effect } from '../src/effect'
|
||||
import { targetMap } from '../src/dep'
|
||||
|
||||
describe('reactivity/reactive', () => {
|
||||
test('Object', () => {
|
||||
|
@ -293,6 +294,13 @@ describe('reactivity/reactive', () => {
|
|||
expect(() => markRaw(obj)).not.toThrowError()
|
||||
})
|
||||
|
||||
test('markRaw should not redefine on an marked object', () => {
|
||||
const obj = markRaw({ foo: 1 })
|
||||
const raw = markRaw(obj)
|
||||
expect(raw).toBe(obj)
|
||||
expect(() => markRaw(obj)).not.toThrowError()
|
||||
})
|
||||
|
||||
test('should not observe non-extensible objects', () => {
|
||||
const obj = reactive({
|
||||
foo: Object.preventExtensions({ a: 1 }),
|
||||
|
@ -391,4 +399,24 @@ describe('reactivity/reactive', () => {
|
|||
a.value++
|
||||
}).not.toThrow()
|
||||
})
|
||||
|
||||
// #11979
|
||||
test('should release property Dep instance if it no longer has subscribers', () => {
|
||||
let obj = { x: 1 }
|
||||
let a = reactive(obj)
|
||||
const e = effect(() => a.x)
|
||||
expect(targetMap.get(obj)?.get('x')).toBeTruthy()
|
||||
e.effect.stop()
|
||||
expect(targetMap.get(obj)?.get('x')).toBeFalsy()
|
||||
})
|
||||
|
||||
test('should trigger reactivity when Map key is undefined', () => {
|
||||
const map = reactive(new Map())
|
||||
const c = computed(() => map.get(void 0))
|
||||
|
||||
expect(c.value).toBe(void 0)
|
||||
|
||||
map.set(void 0, 1)
|
||||
expect(c.value).toBe(1)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -51,6 +51,7 @@ describe('reactivity/reactive/Array', () => {
|
|||
const raw = {}
|
||||
const arr = reactive([{}, {}])
|
||||
arr.push(raw)
|
||||
|
||||
expect(arr.indexOf(raw)).toBe(2)
|
||||
expect(arr.indexOf(raw, 3)).toBe(-1)
|
||||
expect(arr.includes(raw)).toBe(true)
|
||||
|
@ -89,6 +90,84 @@ describe('reactivity/reactive/Array', () => {
|
|||
expect(index).toBe(1)
|
||||
})
|
||||
|
||||
// only non-existent reactive will try to search by using its raw value
|
||||
describe('Array identity methods should not be called more than necessary', () => {
|
||||
const identityMethods = ['includes', 'indexOf', 'lastIndexOf'] as const
|
||||
|
||||
function instrumentArr(rawTarget: any[]) {
|
||||
identityMethods.forEach(key => {
|
||||
const spy = vi.fn(rawTarget[key] as any)
|
||||
rawTarget[key] = spy
|
||||
})
|
||||
}
|
||||
|
||||
function searchValue(target: any[], ...args: unknown[]) {
|
||||
return identityMethods.map(key => (target[key] as any)(...args))
|
||||
}
|
||||
|
||||
function unInstrumentArr(rawTarget: any[]) {
|
||||
identityMethods.forEach(key => {
|
||||
;(rawTarget[key] as any).mockClear()
|
||||
// relink to prototype method
|
||||
rawTarget[key] = Array.prototype[key] as any
|
||||
})
|
||||
}
|
||||
|
||||
function expectHaveBeenCalledTimes(rawTarget: any[], times: number) {
|
||||
identityMethods.forEach(key => {
|
||||
expect(rawTarget[key]).toHaveBeenCalledTimes(times)
|
||||
})
|
||||
}
|
||||
|
||||
test('should be called once with a non-existent raw value', () => {
|
||||
const reactiveArr = reactive([])
|
||||
instrumentArr(toRaw(reactiveArr))
|
||||
const searchResult = searchValue(reactiveArr, {})
|
||||
|
||||
expectHaveBeenCalledTimes(toRaw(reactiveArr), 1)
|
||||
expect(searchResult).toStrictEqual([false, -1, -1])
|
||||
|
||||
unInstrumentArr(toRaw(reactiveArr))
|
||||
})
|
||||
|
||||
test('should be called once with an existent reactive value', () => {
|
||||
const existReactiveValue = reactive({})
|
||||
const reactiveArr = reactive([existReactiveValue, existReactiveValue])
|
||||
|
||||
instrumentArr(toRaw(reactiveArr))
|
||||
const searchResult = searchValue(reactiveArr, existReactiveValue)
|
||||
|
||||
expectHaveBeenCalledTimes(toRaw(reactiveArr), 1)
|
||||
expect(searchResult).toStrictEqual([true, 0, 1])
|
||||
|
||||
unInstrumentArr(toRaw(reactiveArr))
|
||||
})
|
||||
|
||||
test('should be called twice with a non-existent reactive value', () => {
|
||||
const reactiveArr = reactive([])
|
||||
instrumentArr(toRaw(reactiveArr))
|
||||
const searchResult = searchValue(reactiveArr, reactive({}))
|
||||
|
||||
expectHaveBeenCalledTimes(toRaw(reactiveArr), 2)
|
||||
expect(searchResult).toStrictEqual([false, -1, -1])
|
||||
|
||||
unInstrumentArr(toRaw(reactiveArr))
|
||||
})
|
||||
|
||||
test('should be called twice with a non-existent reactive value, but the raw value exists', () => {
|
||||
const existRaw = {}
|
||||
const reactiveArr = reactive([existRaw, existRaw])
|
||||
|
||||
instrumentArr(toRaw(reactiveArr))
|
||||
const searchResult = searchValue(reactiveArr, reactive(existRaw))
|
||||
|
||||
expectHaveBeenCalledTimes(toRaw(reactiveArr), 2)
|
||||
expect(searchResult).toStrictEqual([true, 0, 1])
|
||||
|
||||
unInstrumentArr(toRaw(reactiveArr))
|
||||
})
|
||||
})
|
||||
|
||||
test('delete on Array should not trigger length dependency', () => {
|
||||
const arr = reactive([1, 2, 3])
|
||||
const fn = vi.fn()
|
||||
|
@ -303,19 +382,35 @@ describe('reactivity/reactive/Array', () => {
|
|||
const a2 = reactive([{ val: 3 }])
|
||||
const a3 = [4, 5]
|
||||
|
||||
let result = computed(() => a1.concat(a2, a3))
|
||||
expect(result.value).toStrictEqual([1, { val: 2 }, { val: 3 }, 4, 5])
|
||||
let result = computed(() => a1.concat(a2, a3, 6, { val: 7 }))
|
||||
expect(result.value).toStrictEqual([
|
||||
1,
|
||||
{ val: 2 },
|
||||
{ val: 3 },
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
{ val: 7 },
|
||||
])
|
||||
expect(isReactive(result.value[1])).toBe(false)
|
||||
expect(isReactive(result.value[2])).toBe(true)
|
||||
expect(isReactive(result.value[6])).toBe(false)
|
||||
|
||||
a1.shift()
|
||||
expect(result.value).toStrictEqual([{ val: 2 }, { val: 3 }, 4, 5])
|
||||
expect(result.value).toStrictEqual([
|
||||
{ val: 2 },
|
||||
{ val: 3 },
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
{ val: 7 },
|
||||
])
|
||||
|
||||
a2.pop()
|
||||
expect(result.value).toStrictEqual([{ val: 2 }, 4, 5])
|
||||
expect(result.value).toStrictEqual([{ val: 2 }, 4, 5, 6, { val: 7 }])
|
||||
|
||||
a3.pop()
|
||||
expect(result.value).toStrictEqual([{ val: 2 }, 4, 5])
|
||||
expect(result.value).toStrictEqual([{ val: 2 }, 4, 5, 6, { val: 7 }])
|
||||
})
|
||||
|
||||
test('entries', () => {
|
||||
|
@ -724,6 +819,27 @@ describe('reactivity/reactive/Array', () => {
|
|||
expect(state.things.forEach('foo', 'bar', 'baz')).toBeUndefined()
|
||||
expect(state.things.map('foo', 'bar', 'baz')).toEqual(['1', '2', '3'])
|
||||
expect(state.things.some('foo', 'bar', 'baz')).toBe(true)
|
||||
|
||||
{
|
||||
class Collection extends Array {
|
||||
find(matcher: any) {
|
||||
return super.find(matcher)
|
||||
}
|
||||
}
|
||||
|
||||
const state = reactive({
|
||||
// @ts-expect-error
|
||||
things: new Collection({ foo: '' }),
|
||||
})
|
||||
|
||||
const bar = computed(() => {
|
||||
return state.things.find((obj: any) => obj.foo === 'bar')
|
||||
})
|
||||
bar.value
|
||||
state.things[0].foo = 'bar'
|
||||
|
||||
expect(bar.value).toEqual({ foo: 'bar' })
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -46,26 +46,22 @@ describe('reactivity/ref', () => {
|
|||
it('ref wrapped in reactive should not track internal _value access', () => {
|
||||
const a = ref(1)
|
||||
const b = reactive(a)
|
||||
let calls = 0
|
||||
let dummy
|
||||
|
||||
effect(() => {
|
||||
calls++
|
||||
const fn = vi.fn(() => {
|
||||
dummy = b.value // this will observe both b.value and a.value access
|
||||
})
|
||||
expect(calls).toBe(1)
|
||||
effect(fn)
|
||||
expect(fn).toHaveBeenCalledTimes(1)
|
||||
expect(dummy).toBe(1)
|
||||
|
||||
// mutating a.value should only trigger effect once
|
||||
calls = 0
|
||||
a.value = 3
|
||||
expect(calls).toBe(1)
|
||||
expect(fn).toHaveBeenCalledTimes(2)
|
||||
expect(dummy).toBe(3)
|
||||
|
||||
// mutating b.value should trigger the effect twice. (once for a.value change and once for b.value change)
|
||||
calls = 0
|
||||
b.value = 5
|
||||
expect(calls).toBe(2)
|
||||
expect(fn).toHaveBeenCalledTimes(4)
|
||||
expect(dummy).toBe(5)
|
||||
})
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
WatchErrorCodes,
|
||||
type WatchOptions,
|
||||
type WatchScheduler,
|
||||
computed,
|
||||
onWatcherCleanup,
|
||||
ref,
|
||||
watch,
|
||||
|
@ -13,7 +14,7 @@ const queue: (() => void)[] = []
|
|||
|
||||
// a simple scheduler for testing purposes
|
||||
let isFlushPending = false
|
||||
const resolvedPromise = /*#__PURE__*/ Promise.resolve() as Promise<any>
|
||||
const resolvedPromise = /*@__PURE__*/ Promise.resolve() as Promise<any>
|
||||
const nextTick = (fn?: () => any) =>
|
||||
fn ? resolvedPromise.then(fn) : resolvedPromise
|
||||
|
||||
|
@ -193,4 +194,87 @@ describe('watch', () => {
|
|||
scope.stop()
|
||||
expect(calls).toEqual(['sync 2', 'post 2'])
|
||||
})
|
||||
|
||||
test('once option should be ignored by simple watch', async () => {
|
||||
let dummy: any
|
||||
const source = ref(0)
|
||||
watch(
|
||||
() => {
|
||||
dummy = source.value
|
||||
},
|
||||
null,
|
||||
{ once: true },
|
||||
)
|
||||
expect(dummy).toBe(0)
|
||||
|
||||
source.value++
|
||||
expect(dummy).toBe(1)
|
||||
})
|
||||
|
||||
// #12033
|
||||
test('recursive sync watcher on computed', () => {
|
||||
const r = ref(0)
|
||||
const c = computed(() => r.value)
|
||||
|
||||
watch(c, v => {
|
||||
if (v > 1) {
|
||||
r.value--
|
||||
}
|
||||
})
|
||||
|
||||
expect(r.value).toBe(0)
|
||||
expect(c.value).toBe(0)
|
||||
|
||||
r.value = 10
|
||||
expect(r.value).toBe(1)
|
||||
expect(c.value).toBe(1)
|
||||
})
|
||||
|
||||
// edge case where a nested endBatch() causes an effect to be batched in a
|
||||
// nested batch loop with its .next mutated, causing the outer loop to end
|
||||
// early
|
||||
test('nested batch edge case', () => {
|
||||
// useClamp from VueUse
|
||||
const clamp = (n: number, min: number, max: number) =>
|
||||
Math.min(max, Math.max(min, n))
|
||||
function useClamp(src: Ref<number>, min: number, max: number) {
|
||||
return computed({
|
||||
get() {
|
||||
return (src.value = clamp(src.value, min, max))
|
||||
},
|
||||
set(val) {
|
||||
src.value = clamp(val, min, max)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const src = ref(1)
|
||||
const clamped = useClamp(src, 1, 5)
|
||||
watch(src, val => (clamped.value = val))
|
||||
|
||||
const spy = vi.fn()
|
||||
watch(clamped, spy)
|
||||
|
||||
src.value = 2
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
src.value = 10
|
||||
expect(spy).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test('should ensure correct execution order in batch processing', () => {
|
||||
const dummy: number[] = []
|
||||
const n1 = ref(0)
|
||||
const n2 = ref(0)
|
||||
const sum = computed(() => n1.value + n2.value)
|
||||
watch(n1, () => {
|
||||
dummy.push(1)
|
||||
n2.value++
|
||||
})
|
||||
watch(sum, () => dummy.push(2))
|
||||
watch(n1, () => dummy.push(3))
|
||||
|
||||
n1.value++
|
||||
|
||||
expect(dummy).toEqual([1, 2, 3])
|
||||
})
|
||||
})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue