mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/minor'
This commit is contained in:
commit
cf8be999df
|
|
@ -20,7 +20,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
@ -42,7 +42,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install pnpm
|
||||
uses: pnpm/action-setup@v3.0.0
|
||||
uses: pnpm/action-setup@v4.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v4
|
||||
|
|
@ -36,14 +36,14 @@ jobs:
|
|||
run: pnpm install
|
||||
|
||||
- name: Download Size Data
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
uses: dawidd6/action-download-artifact@v4
|
||||
with:
|
||||
name: size-data
|
||||
run_id: ${{ github.event.workflow_run.id }}
|
||||
path: temp/size
|
||||
|
||||
- name: Download Previous Size Data
|
||||
uses: dawidd6/action-download-artifact@v3
|
||||
uses: dawidd6/action-download-artifact@v4
|
||||
with:
|
||||
branch: main
|
||||
workflow: size-data.yml
|
||||
|
|
|
|||
65
CHANGELOG.md
65
CHANGELOG.md
|
|
@ -1,3 +1,68 @@
|
|||
## [3.4.29](https://github.com/vuejs/core/compare/v3.4.28...v3.4.29) (2024-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **build:** fix accidental inclusion of runtime-core in server-renderer cjs build ([11cc12b](https://github.com/vuejs/core/commit/11cc12b915edfe0e4d3175e57464f73bc2c1cb04)), closes [#11137](https://github.com/vuejs/core/issues/11137)
|
||||
* **compiler-sfc:** fix missing scope for extends error message ([4ec387b](https://github.com/vuejs/core/commit/4ec387b100985b008cdcc4cd883a5b6328c05766))
|
||||
* **compiler-sfc:** fix parsing of mts, d.mts, and mtsx files ([a476692](https://github.com/vuejs/core/commit/a476692ed2d7308f2742d8ff3554cf97a392b0b7))
|
||||
* **compiler-sfc:** support [@vue-ignore](https://github.com/vue-ignore) comment on more type sources ([a23e99b](https://github.com/vuejs/core/commit/a23e99bedf1d65841d162951f10ce35b907a5680))
|
||||
* **custom-element:** support same direct setup function signature in defineCustomElement ([7c8b126](https://github.com/vuejs/core/commit/7c8b12620aad4969b8dc4944d4fc486d16c3033c)), closes [#11116](https://github.com/vuejs/core/issues/11116)
|
||||
* **reactivity:** avoid infinite loop when render access a side effect computed ([#11135](https://github.com/vuejs/core/issues/11135)) ([8296e19](https://github.com/vuejs/core/commit/8296e19855e369a7826f5ea26540a6da01dc7093)), closes [#11121](https://github.com/vuejs/core/issues/11121)
|
||||
|
||||
|
||||
|
||||
## [3.4.28](https://github.com/vuejs/core/compare/v3.4.27...v3.4.28) (2024-06-14)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compat:** correctly transform non-identifier expressions in legacy filter syntax ([#10896](https://github.com/vuejs/core/issues/10896)) ([07b3c4b](https://github.com/vuejs/core/commit/07b3c4b7860009e19446f3d78571556c5737d82a)), closes [#10852](https://github.com/vuejs/core/issues/10852)
|
||||
* **compat:** ensure proper handling of render fuction from SFC using Vue.extend ([#7781](https://github.com/vuejs/core/issues/7781)) ([c73847f](https://github.com/vuejs/core/commit/c73847f2becc20f03cb9c68748eea92455e688ee)), closes [#7766](https://github.com/vuejs/core/issues/7766)
|
||||
* **compat:** only warn ATTR_FALSE_VALUE when enabled ([04729ba](https://github.com/vuejs/core/commit/04729ba2163d840f0ca7866bc964696eb5557804)), closes [#11126](https://github.com/vuejs/core/issues/11126)
|
||||
* **compile-sfc:** register props destructure rest id as setup bindings ([#10888](https://github.com/vuejs/core/issues/10888)) ([b2b5f57](https://github.com/vuejs/core/commit/b2b5f57c2c945edd0eebc1b545ec1b7568e51484)), closes [#10885](https://github.com/vuejs/core/issues/10885)
|
||||
* **compile-sfc:** Support project reference with folder, ([#10908](https://github.com/vuejs/core/issues/10908)) ([bdeac37](https://github.com/vuejs/core/commit/bdeac377c7b85888193b49ac187e927636cc40bc)), closes [#10907](https://github.com/vuejs/core/issues/10907)
|
||||
* **compiler-core:** allow unicode to appear in simple identifiers ([#6765](https://github.com/vuejs/core/issues/6765)) ([3ea9644](https://github.com/vuejs/core/commit/3ea964473d3ac0ba3e7b0b2c22d71f23d0f69123)), closes [#6367](https://github.com/vuejs/core/issues/6367)
|
||||
* **compiler-core:** change v-for key type to match Object.keys ([#10963](https://github.com/vuejs/core/issues/10963)) ([9fead52](https://github.com/vuejs/core/commit/9fead5234320848f8be82275c6b5dd0a290f2cca)), closes [#8819](https://github.com/vuejs/core/issues/8819)
|
||||
* **compiler-core:** emit TS-compatible function declaration when requested ([#9363](https://github.com/vuejs/core/issues/9363)) ([5d25850](https://github.com/vuejs/core/commit/5d258502a0faffc8a451b8701f13a31b2566d068))
|
||||
* **compiler-core:** fix :key shorthand on v-for ([#10942](https://github.com/vuejs/core/issues/10942)) ([29425df](https://github.com/vuejs/core/commit/29425df1acb9e520c6ae894d06bcff73fde90edd)), closes [#10882](https://github.com/vuejs/core/issues/10882) [#10939](https://github.com/vuejs/core/issues/10939)
|
||||
* **compiler-core:** make `ForIteratorExpression`'s `returns` property optional ([#11011](https://github.com/vuejs/core/issues/11011)) ([5b8c1af](https://github.com/vuejs/core/commit/5b8c1afb74e39045fcb53a011420d26e3f67eab4))
|
||||
* **compiler-core:** should set `<math>` tag as block to retain MathML namespace after patching ([#10891](https://github.com/vuejs/core/issues/10891)) ([87c5443](https://github.com/vuejs/core/commit/87c54430448005294c41803f07f517fef848f917))
|
||||
* **compiler-core:** v-for expression missing source with spaces should emit error ([#5821](https://github.com/vuejs/core/issues/5821)) ([b9ca202](https://github.com/vuejs/core/commit/b9ca202f477be595477e182972ee9bae3f2b9f74)), closes [#5819](https://github.com/vuejs/core/issues/5819)
|
||||
* **compiler-sfc:** improve type resolving for the keyof operator ([#10921](https://github.com/vuejs/core/issues/10921)) ([293cf4e](https://github.com/vuejs/core/commit/293cf4e131b6d4606e1de2cd7ea87814e2544952)), closes [#10920](https://github.com/vuejs/core/issues/10920) [#11002](https://github.com/vuejs/core/issues/11002)
|
||||
* **compiler-sfc:** support as keyword with template literal types ([#11100](https://github.com/vuejs/core/issues/11100)) ([2594b1d](https://github.com/vuejs/core/commit/2594b1df57f672ac6621ac2880645e975fea581c)), closes [#10962](https://github.com/vuejs/core/issues/10962)
|
||||
* **compiler-sfc:** support type resolve for keyof for intersection & union types ([#11132](https://github.com/vuejs/core/issues/11132)) ([495263a](https://github.com/vuejs/core/commit/495263a9cb356861e58a4364f2570608265486b5)), closes [#11129](https://github.com/vuejs/core/issues/11129)
|
||||
* **compiler-sfc:** throw error when import macro as alias ([#11041](https://github.com/vuejs/core/issues/11041)) ([34a97ed](https://github.com/vuejs/core/commit/34a97edd2c8273c213599c44770accdb0846da8e))
|
||||
* correct the type of `<details>`'s `onToggle` event handler ([#10938](https://github.com/vuejs/core/issues/10938)) ([fd18ce7](https://github.com/vuejs/core/commit/fd18ce70b1a260a2485c9cd7faa30193da4b79f5)), closes [#10928](https://github.com/vuejs/core/issues/10928)
|
||||
* **custom-element:** disconnect MutationObserver in nextTick in case that custom elements are moved ([#10613](https://github.com/vuejs/core/issues/10613)) ([bbb5be2](https://github.com/vuejs/core/commit/bbb5be299b500a00e60c757118c846c3b5ddd8e0)), closes [#10610](https://github.com/vuejs/core/issues/10610)
|
||||
* **custom-elements:** compatibility of createElement in older versions of Chrome ([#9615](https://github.com/vuejs/core/issues/9615)) ([a88295d](https://github.com/vuejs/core/commit/a88295dc076ee867939d8b0ee2225e63c5ffb0ca)), closes [#9614](https://github.com/vuejs/core/issues/9614)
|
||||
* **hmr:** avoid infinite recursion when reloading hmr components ([#6936](https://github.com/vuejs/core/issues/6936)) ([36bd9b0](https://github.com/vuejs/core/commit/36bd9b0a1fb83e61731fb80d66e265dccbedcfa8)), closes [#6930](https://github.com/vuejs/core/issues/6930)
|
||||
* **hydration:** log hydration error even when using async components ([#9403](https://github.com/vuejs/core/issues/9403)) ([5afc76c](https://github.com/vuejs/core/commit/5afc76c229f9ad30eef07f34c7b65e8fe427e637)), closes [#9369](https://github.com/vuejs/core/issues/9369)
|
||||
* **KeepAlive:** properly cache nested Suspense subtree ([#10912](https://github.com/vuejs/core/issues/10912)) ([07764fe](https://github.com/vuejs/core/commit/07764fe330692fadf0fc9fb9e92cb5b111df33be))
|
||||
* **npm:** explicitly add `@vue/reactivity` as dependency of `@vue/runtime-dom` ([#10468](https://github.com/vuejs/core/issues/10468)) ([ec424f6](https://github.com/vuejs/core/commit/ec424f6cd96b7e6ba74fc244c484c00fa5590aac))
|
||||
* **reactivity:** pass oldValue in debug info when triggering refs ([#8210](https://github.com/vuejs/core/issues/8210)) ([3b0a56a](https://github.com/vuejs/core/commit/3b0a56a9c4d162ec3bd725a4f2dfd776b045e727)), closes [vuejs/pinia#2061](https://github.com/vuejs/pinia/issues/2061)
|
||||
* **runtime-core:** avoid traversing static children for vnodes w/ PatchFlags.BAIL ([#11115](https://github.com/vuejs/core/issues/11115)) ([b557d3f](https://github.com/vuejs/core/commit/b557d3fb8ae1e4e926c4ad0fbb2fa7abe50fd661)), closes [#10547](https://github.com/vuejs/core/issues/10547)
|
||||
* **runtime-core:** do not fire mount/activated hooks if unmounted before mounted ([#9370](https://github.com/vuejs/core/issues/9370)) ([aa156ed](https://github.com/vuejs/core/commit/aa156ed5c4dc0d33ff37e201a7e89d5e0e29160e)), closes [#8898](https://github.com/vuejs/core/issues/8898) [#9264](https://github.com/vuejs/core/issues/9264) [#9617](https://github.com/vuejs/core/issues/9617)
|
||||
* **runtime-core:** ensure suspense creates dep component's render effect with correct optimized flag ([#7689](https://github.com/vuejs/core/issues/7689)) ([c521f95](https://github.com/vuejs/core/commit/c521f956e1697cda36a7f1b913599e5e2004f7ba)), closes [#7688](https://github.com/vuejs/core/issues/7688)
|
||||
* **runtime-core:** fix missed updates when passing text vnode to `<component :is>` ([#8304](https://github.com/vuejs/core/issues/8304)) ([b310ec3](https://github.com/vuejs/core/commit/b310ec389d9738247e5b0f01711186216eb49955)), closes [#8298](https://github.com/vuejs/core/issues/8298)
|
||||
* **runtime-core:** fix stale v-memo after v-if toggle ([#6606](https://github.com/vuejs/core/issues/6606)) ([edf2638](https://github.com/vuejs/core/commit/edf263847eddc910f4d2de68287d84b8c66c3860)), closes [#6593](https://github.com/vuejs/core/issues/6593)
|
||||
* **runtime-core:** fix Transition for components with root-level v-if ([#7678](https://github.com/vuejs/core/issues/7678)) ([ef2e737](https://github.com/vuejs/core/commit/ef2e737577de42ea38771403f8a4dee8c892daa5)), closes [#7649](https://github.com/vuejs/core/issues/7649)
|
||||
* **runtime-dom:** also set attribute for form element state ([537a571](https://github.com/vuejs/core/commit/537a571f8cf09dfe0a020e9e8891ecdd351fc3e4)), closes [#6007](https://github.com/vuejs/core/issues/6007) [#6012](https://github.com/vuejs/core/issues/6012)
|
||||
* **runtime-dom:** support Symbol for input value bindings ([#10608](https://github.com/vuejs/core/issues/10608)) ([188f3ae](https://github.com/vuejs/core/commit/188f3ae533fd340603068a516a8fecc5d57426c5)), closes [#10597](https://github.com/vuejs/core/issues/10597)
|
||||
* **shared:** ensure invokeArrayFns handles undefined arguments ([#10869](https://github.com/vuejs/core/issues/10869)) ([9b40d0f](https://github.com/vuejs/core/commit/9b40d0f25da868a83b0d6bf99dbbdb3ca68bb700)), closes [#10863](https://github.com/vuejs/core/issues/10863)
|
||||
* **ssr:** directive binding.instance should respect exposed during ssr ([df686ab](https://github.com/vuejs/core/commit/df686abb4f0ac9d898e4fd93751e860f8cbbdbea)), closes [#7499](https://github.com/vuejs/core/issues/7499) [#7502](https://github.com/vuejs/core/issues/7502)
|
||||
* **ssr:** fix hydration for node with empty text node ([#7216](https://github.com/vuejs/core/issues/7216)) ([d1011c0](https://github.com/vuejs/core/commit/d1011c07a957d858cb37725b13bc8e4d7a395490))
|
||||
* **ssr:** fix the bug that multi slot scope id does not work on component ([#6100](https://github.com/vuejs/core/issues/6100)) ([4c74302](https://github.com/vuejs/core/commit/4c74302aae64c118752db7fc2a2c229a11ebaead)), closes [#6093](https://github.com/vuejs/core/issues/6093)
|
||||
* **teleport:** do not throw target warning when teleport is disabled ([#9818](https://github.com/vuejs/core/issues/9818)) ([15ee43f](https://github.com/vuejs/core/commit/15ee43f66ad2485ac212b02b444345d867b3c060))
|
||||
* **transition:** ensure Transition enterHooks are updated after clone ([#11066](https://github.com/vuejs/core/issues/11066)) ([671cf29](https://github.com/vuejs/core/commit/671cf297a550d15b19fa3fecce1b30e26cad8154)), closes [#11061](https://github.com/vuejs/core/issues/11061)
|
||||
* **types/apiWatch:** correct type inference for reactive array ([#11036](https://github.com/vuejs/core/issues/11036)) ([aae2d78](https://github.com/vuejs/core/commit/aae2d78875daa476280a45e71c2f38292964efae)), closes [#9416](https://github.com/vuejs/core/issues/9416)
|
||||
* **types:** improve `app.provide` type checking ([#10603](https://github.com/vuejs/core/issues/10603)) ([612bbf0](https://github.com/vuejs/core/commit/612bbf0507cbe39d701acc5dff11824802078063)), closes [#10602](https://github.com/vuejs/core/issues/10602)
|
||||
* **types:** support generic argument in setup context expose method ([#8507](https://github.com/vuejs/core/issues/8507)) ([635a59b](https://github.com/vuejs/core/commit/635a59b96fe6be445525c6595ca27da7ef7c1feb))
|
||||
* **v-model:** fix the lazy modifier is not reset by other modifications ([#8547](https://github.com/vuejs/core/issues/8547)) ([a52a02f](https://github.com/vuejs/core/commit/a52a02f43fdf73d8aaad99c9cafed07f12ee422a)), closes [#8546](https://github.com/vuejs/core/issues/8546) [#6564](https://github.com/vuejs/core/issues/6564) [#6773](https://github.com/vuejs/core/issues/6773)
|
||||
* **watch:** support traversing symbol properties in deep watcher ([#10969](https://github.com/vuejs/core/issues/10969)) ([a3e8aaf](https://github.com/vuejs/core/commit/a3e8aafbcc82003a66caded61143eb64c4ef02cd)), closes [#402](https://github.com/vuejs/core/issues/402)
|
||||
|
||||
|
||||
|
||||
## [3.4.27](https://github.com/vuejs/core/compare/v3.4.26...v3.4.27) (2024-05-06)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,3 +12,4 @@ We would like to thank the following security researchers for responsibly disclo
|
|||
|
||||
- Jeet Pal - [@jeetpal2007](https://github.com/jeetpal2007) | [Email](jeetpal2007@gmail.com) | [LinkedIn](https://in.linkedin.com/in/jeet-pal-22601a290 )
|
||||
- Mix - [@mnixry](https://github.com/mnixry)
|
||||
- Aviv Keller - [@RedYetiDev](https://github.com/redyetidev) | [LinkedIn](https://www.linkedin.com/in/redyetidev) <redyetidev@gmail.com>
|
||||
|
|
|
|||
26
package.json
26
package.json
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "3.0.0-vapor",
|
||||
"packageManager": "pnpm@9.1.2",
|
||||
"packageManager": "pnpm@9.2.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js vue vue-vapor",
|
||||
|
|
@ -59,8 +59,8 @@
|
|||
"node": ">=18.12.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/parser": "^7.24.6",
|
||||
"@babel/types": "^7.24.6",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@babel/types": "^7.24.7",
|
||||
"@codspeed/vitest-plugin": "^3.1.0",
|
||||
"@rollup/plugin-alias": "^5.1.0",
|
||||
"@rollup/plugin-commonjs": "^25.0.8",
|
||||
|
|
@ -70,20 +70,20 @@
|
|||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/node": "^20.14.2",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@vitest/coverage-istanbul": "^1.5.2",
|
||||
"@vitest/ui": "^1.5.2",
|
||||
"@vue/consolidate": "1.0.0",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.21.4",
|
||||
"esbuild": "^0.21.5",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^9.3.0",
|
||||
"eslint": "^9.4.0",
|
||||
"eslint-plugin-import-x": "^0.5.1",
|
||||
"eslint-plugin-vitest": "^0.5.4",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^8.0.1",
|
||||
"execa": "^9.2.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"lint-staged": "^15.2.5",
|
||||
"lodash": "^4.17.21",
|
||||
|
|
@ -93,7 +93,7 @@
|
|||
"minimist": "^1.2.8",
|
||||
"npm-run-all2": "^6.2.0",
|
||||
"picocolors": "^1.0.1",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier": "^3.3.1",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.3",
|
||||
"puppeteer": "~22.7.1",
|
||||
|
|
@ -105,13 +105,13 @@
|
|||
"semver": "^7.6.2",
|
||||
"serve": "^14.2.3",
|
||||
"simple-git-hooks": "^2.11.1",
|
||||
"terser": "^5.31.0",
|
||||
"terser": "^5.31.1",
|
||||
"todomvc-app-css": "^2.4.3",
|
||||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.11.0",
|
||||
"tslib": "^2.6.3",
|
||||
"tsx": "^4.15.1",
|
||||
"typescript": "~5.4.5",
|
||||
"typescript-eslint": "^7.10.0",
|
||||
"vite": "^5.2.11",
|
||||
"typescript-eslint": "^7.12.0",
|
||||
"vite": "^5.2.13",
|
||||
"vitest": "^1.5.2"
|
||||
},
|
||||
"pnpm": {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
exports[`compiler: parse > Edge Cases > invalid html 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -86,7 +86,7 @@ exports[`compiler: parse > Edge Cases > invalid html 1`] = `
|
|||
|
||||
exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -280,7 +280,7 @@ exports[`compiler: parse > Edge Cases > self closing multiple tag 1`] = `
|
|||
|
||||
exports[`compiler: parse > Edge Cases > valid html 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -498,7 +498,7 @@ exports[`compiler: parse > Edge Cases > valid html 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><![CDATA[cdata]]></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -550,7 +550,7 @@ exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><![CDATA[c
|
|||
|
||||
exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><svg><![CDATA[cdata]]></svg></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -643,7 +643,7 @@ exports[`compiler: parse > Errors > CDATA_IN_HTML_CONTENT > <template><svg><![CD
|
|||
|
||||
exports[`compiler: parse > Errors > DUPLICATE_ATTRIBUTE > <template><div id="" id=""></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -813,7 +813,7 @@ exports[`compiler: parse > Errors > DUPLICATE_ATTRIBUTE > <template><div id="" i
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template>< 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -883,7 +883,7 @@ exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template>< 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template></ 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -953,7 +953,7 @@ exports[`compiler: parse > Errors > EOF_BEFORE_TAG_NAME > <template></ 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[ 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -1028,7 +1028,7 @@ exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[ 1`]
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[cdata 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -1121,7 +1121,7 @@ exports[`compiler: parse > Errors > EOF_IN_CDATA > <template><svg><![CDATA[cdata
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!-- 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1173,7 +1173,7 @@ exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!-- 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!--comment 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -1243,7 +1243,7 @@ exports[`compiler: parse > Errors > EOF_IN_COMMENT > <template><!--comment 1`] =
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <div></div 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1295,7 +1295,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <div></div 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1347,7 +1347,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1399,7 +1399,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1451,7 +1451,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id = 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1503,7 +1503,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id = 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1555,7 +1555,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1607,7 +1607,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc" 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1659,7 +1659,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc" 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc"/ 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -1729,7 +1729,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id="abc"/ 1`] =
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1781,7 +1781,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc' 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -1833,7 +1833,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc' 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc'/ 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -1903,7 +1903,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id='abc'/ 1`] =
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc / 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -1973,7 +1973,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc / 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -2025,7 +2025,7 @@ exports[`compiler: parse > Errors > EOF_IN_TAG > <template><div id=abc 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id= /></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -2148,7 +2148,7 @@ exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=
|
|||
|
||||
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id= ></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -2271,7 +2271,7 @@ exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=
|
|||
|
||||
exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -2394,7 +2394,7 @@ exports[`compiler: parse > Errors > MISSING_ATTRIBUTE_VALUE > <template><div id=
|
|||
|
||||
exports[`compiler: parse > Errors > MISSING_END_TAG_NAME > <template></></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -2446,7 +2446,7 @@ exports[`compiler: parse > Errors > MISSING_END_TAG_NAME > <template></></templa
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a"bc=''></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -2569,7 +2569,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <te
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a'bc=''></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -2692,7 +2692,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <te
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <template><div a<bc=''></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -2815,7 +2815,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_ATTRIBUTE_NAME > <te
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar"></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -2938,7 +2938,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar'></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3061,7 +3061,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar<div></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3184,7 +3184,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar=baz></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3307,7 +3307,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_VALUE > <template><div foo=bar\`></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3430,7 +3430,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_CHARACTER_IN_UNQUOTED_ATTRIBUTE_V
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME > <template><div =></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3537,7 +3537,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME > <template><div =foo=bar></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3660,7 +3660,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_EQUALS_SIGN_BEFORE_ATTRIBUTE_NAME
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME > <template><?xml?></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -3712,7 +3712,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_QUESTION_MARK_INSTEAD_OF_TAG_NAME
|
|||
|
||||
exports[`compiler: parse > Errors > UNEXPECTED_SOLIDUS_IN_TAG > <template><div a/b></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3850,7 +3850,7 @@ exports[`compiler: parse > Errors > UNEXPECTED_SOLIDUS_IN_TAG > <template><div a
|
|||
|
||||
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><![CDATA[</div>]]></svg> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3920,7 +3920,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><![CDATA[</div>]]><
|
|||
|
||||
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><!--</div>--></svg> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -3990,7 +3990,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <svg><!--</div>--></svg>
|
|||
|
||||
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -4042,7 +4042,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></div></
|
|||
|
||||
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -4094,7 +4094,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template></div></templa
|
|||
|
||||
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>{{'</div>'}}</template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -4182,7 +4182,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>{{'</div>'}}</
|
|||
|
||||
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>a </ b</template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -4252,7 +4252,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <template>a </ b</templa
|
|||
|
||||
exports[`compiler: parse > Errors > X_INVALID_END_TAG > <textarea></div></textarea> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -4322,7 +4322,7 @@ exports[`compiler: parse > Errors > X_INVALID_END_TAG > <textarea></div></textar
|
|||
|
||||
exports[`compiler: parse > Errors > X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END > <div v-foo:[sef fsef] /> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [],
|
||||
|
|
@ -4446,7 +4446,7 @@ exports[`compiler: parse > Errors > X_MISSING_DYNAMIC_DIRECTIVE_ARGUMENT_END > <
|
|||
|
||||
exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -4521,7 +4521,7 @@ exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div> 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div></template> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -4596,7 +4596,7 @@ exports[`compiler: parse > Errors > X_MISSING_END_TAG > <template><div></templat
|
|||
|
||||
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > <div>{{ foo</div> 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"children": [
|
||||
|
|
@ -4666,7 +4666,7 @@ exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > <div>{{ foo</d
|
|||
|
||||
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"content": "{{",
|
||||
|
|
@ -4713,7 +4713,7 @@ exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ foo 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"content": "{{ foo",
|
||||
|
|
@ -4760,7 +4760,7 @@ exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{ foo 1`] = `
|
|||
|
||||
exports[`compiler: parse > Errors > X_MISSING_INTERPOLATION_END > {{}} 1`] = `
|
||||
{
|
||||
"cached": 0,
|
||||
"cached": [],
|
||||
"children": [
|
||||
{
|
||||
"content": {
|
||||
|
|
|
|||
|
|
@ -1,21 +1,5 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`scopeId compiler support > should push scopeId for hoisted nodes 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, toDisplayString as _toDisplayString, createTextVNode as _createTextVNode, openBlock as _openBlock, createElementBlock as _createElementBlock, pushScopeId as _pushScopeId, popScopeId as _popScopeId } from "vue"
|
||||
|
||||
const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)
|
||||
const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", -1 /* HOISTED */))
|
||||
const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", -1 /* HOISTED */))
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_hoisted_1,
|
||||
_createTextVNode(_toDisplayString(_ctx.foo), 1 /* TEXT */),
|
||||
_hoisted_2
|
||||
]))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`scopeId compiler support > should wrap default slot 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, resolveComponent as _resolveComponent, withCtx as _withCtx, openBlock as _openBlock, createBlock as _createBlock } from "vue"
|
||||
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ function createRoot(options: Partial<RootNode> = {}): RootNode {
|
|||
directives: [],
|
||||
imports: [],
|
||||
hoists: [],
|
||||
cached: 0,
|
||||
cached: [],
|
||||
temps: 0,
|
||||
codegenNode: createSimpleExpression(`null`, false),
|
||||
loc: locStub,
|
||||
|
|
@ -422,7 +422,7 @@ describe('compiler: codegen', () => {
|
|||
test('CacheExpression', () => {
|
||||
const { code } = generate(
|
||||
createRoot({
|
||||
cached: 1,
|
||||
cached: [],
|
||||
codegenNode: createCacheExpression(
|
||||
1,
|
||||
createSimpleExpression(`foo`, false),
|
||||
|
|
@ -440,7 +440,7 @@ describe('compiler: codegen', () => {
|
|||
test('CacheExpression w/ isVNode: true', () => {
|
||||
const { code } = generate(
|
||||
createRoot({
|
||||
cached: 1,
|
||||
cached: [],
|
||||
codegenNode: createCacheExpression(
|
||||
1,
|
||||
createSimpleExpression(`foo`, false),
|
||||
|
|
|
|||
|
|
@ -1,7 +1,4 @@
|
|||
import { baseCompile } from '../src/compile'
|
||||
import { POP_SCOPE_ID, PUSH_SCOPE_ID } from '../src/runtimeHelpers'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { genFlagText } from './testUtils'
|
||||
|
||||
/**
|
||||
* Ensure all slot functions are wrapped with _withCtx
|
||||
|
|
@ -57,28 +54,4 @@ describe('scopeId compiler support', () => {
|
|||
expect(code).toMatch(/name: i,\s+fn: _withCtx\(/)
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should push scopeId for hoisted nodes', () => {
|
||||
const { ast, code } = baseCompile(
|
||||
`<div><div>hello</div>{{ foo }}<div>world</div></div>`,
|
||||
{
|
||||
mode: 'module',
|
||||
scopeId: 'test',
|
||||
hoistStatic: true,
|
||||
},
|
||||
)
|
||||
expect(ast.helpers).toContain(PUSH_SCOPE_ID)
|
||||
expect(ast.helpers).toContain(POP_SCOPE_ID)
|
||||
expect(ast.hoists.length).toBe(2)
|
||||
;[
|
||||
`const _withScopeId = n => (_pushScopeId("test"),n=n(),_popScopeId(),n)`,
|
||||
`const _hoisted_1 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "hello", ${genFlagText(
|
||||
PatchFlags.HOISTED,
|
||||
)}))`,
|
||||
`const _hoisted_2 = /*#__PURE__*/ _withScopeId(() => /*#__PURE__*/_createElementVNode("div", null, "world", ${genFlagText(
|
||||
PatchFlags.HOISTED,
|
||||
)}))`,
|
||||
].forEach(c => expect(code).toMatch(c))
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,103 +1,87 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist element with static key 1`] = `
|
||||
exports[`compiler: cacheStatic transform > cache element with static key 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", { key: "foo" }, null, -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", { key: "foo" }, null, -1 /* CACHED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist nested static tree 1`] = `
|
||||
exports[`compiler: cacheStatic transform > cache nested children array 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("p", null, [
|
||||
/*#__PURE__*/_createElementVNode("span"),
|
||||
/*#__PURE__*/_createElementVNode("span")
|
||||
], -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */),
|
||||
_createElementVNode("p", null, [
|
||||
_createElementVNode("span"),
|
||||
_createElementVNode("span")
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist nested static tree with comments 1`] = `
|
||||
exports[`compiler: cacheStatic transform > cache nested static tree with comments 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
|
||||
/*#__PURE__*/_createCommentVNode("comment")
|
||||
], -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createCommentVNode: _createCommentVNode, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createCommentVNode("comment")
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist siblings with common non-hoistable parent 1`] = `
|
||||
exports[`compiler: cacheStatic transform > cache siblings including text with common non-hoistable parent 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
|
||||
const _hoisted_2 = /*#__PURE__*/_createElementVNode("div", null, null, -1 /* HOISTED */)
|
||||
const _hoisted_3 = [
|
||||
_hoisted_1,
|
||||
_hoisted_2
|
||||
]
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, createTextVNode: _createTextVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */),
|
||||
_createTextVNode("foo"),
|
||||
_createElementVNode("div", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: cacheStatic transform > cache single children array 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_3))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "inline" }, "hello", -1 /* CACHED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist simple element 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { class: "inline" }, "hello", -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist static props for elements with directives 1`] = `
|
||||
exports[`compiler: cacheStatic transform > hoist static props for elements with directives 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
|
|
@ -118,7 +102,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist static props for elements with dynamic text children 1`] = `
|
||||
exports[`compiler: cacheStatic transform > hoist static props for elements with dynamic text children 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
|
|
@ -135,7 +119,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > hoist static props for elements with unhoistable children 1`] = `
|
||||
exports[`compiler: cacheStatic transform > hoist static props for elements with unhoistable children 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createVNode: _createVNode, createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
|
|
@ -156,7 +140,53 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist class with static object value 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static interpolation 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, "foo " + _toDisplayString(1) + " " + _toDisplayString(true), -1 /* CACHED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > cache nested static tree with static prop value 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { foo: 0 }, _toDisplayString(1), -1 /* CACHED */)
|
||||
])))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > clone hoisted array children in v-for + HMR mode 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(1, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", null, [...(_cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", { class: "hi" }, null, -1 /* CACHED */)
|
||||
]))]))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > hoist class with static object value 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
|
|
@ -175,50 +205,8 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static interpolation 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache SVG with directives 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "foo " + /*#__PURE__*/_toDisplayString(1) + " " + /*#__PURE__*/_toDisplayString(true), -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > hoist nested static tree with static prop value 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", { foo: 0 }, /*#__PURE__*/_toDisplayString(1), -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { toDisplayString: _toDisplayString, createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist SVG with directives 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
|
|
@ -227,7 +215,9 @@ return function render(_ctx, _cache) {
|
|||
const _directive_foo = _resolveDirective("foo")
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _hoisted_2)), [
|
||||
_withDirectives((_openBlock(), _createElementBlock("svg", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("path", { d: "M2,3H5.5L12" }, null, -1 /* CACHED */)
|
||||
]))), [
|
||||
[_directive_foo]
|
||||
])
|
||||
]))
|
||||
|
|
@ -235,7 +225,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers + other bindings 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers + other bindings 1`] = `
|
||||
"import { normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
|
|
@ -250,7 +240,7 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist elements with cached handlers 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache elements with cached handlers 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
|
|
@ -264,7 +254,7 @@ export function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (2) 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (2) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
@ -282,7 +272,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables (v-slot) 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables (v-slot) 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
@ -301,7 +291,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist expressions that refer scope variables 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache expressions that refer scope variables 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
@ -319,7 +309,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > prefixIdentifiers > should NOT hoist keyed template v-for with plain element child 1`] = `
|
||||
exports[`compiler: cacheStatic transform > prefixIdentifiers > should NOT cache keyed template v-for with plain element child 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
@ -335,7 +325,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > should NOT hoist components 1`] = `
|
||||
exports[`compiler: cacheStatic transform > should NOT cache components 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
@ -351,7 +341,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic key 1`] = `
|
||||
exports[`compiler: cacheStatic transform > should NOT cache element with dynamic key 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
@ -365,7 +355,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic props (but hoist the props list) 1`] = `
|
||||
exports[`compiler: cacheStatic transform > should NOT cache element with dynamic props (but hoist the props list) 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
|
|
@ -382,7 +372,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > should NOT hoist element with dynamic ref 1`] = `
|
||||
exports[`compiler: cacheStatic transform > should NOT cache element with dynamic ref 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
|
|
@ -396,42 +386,7 @@ return function render(_ctx, _cache) {
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > should NOT hoist root node 1`] = `
|
||||
"const _Vue = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { openBlock: _openBlock, createElementBlock: _createElementBlock } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div"))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > should hoist v-for children if static 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = { id: "foo" }
|
||||
const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
|
||||
const _hoisted_3 = [
|
||||
_hoisted_2
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: hoistStatic transform > should hoist v-if props/children if static 1`] = `
|
||||
exports[`compiler: cacheStatic transform > should cache v-if props/children if static 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
|
||||
|
||||
|
|
@ -439,10 +394,6 @@ const _hoisted_1 = {
|
|||
key: 0,
|
||||
id: "foo"
|
||||
}
|
||||
const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, null, -1 /* HOISTED */)
|
||||
const _hoisted_3 = [
|
||||
_hoisted_2
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
|
|
@ -450,9 +401,32 @@ return function render(_ctx, _cache) {
|
|||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
ok
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _hoisted_3))
|
||||
? (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
: _createCommentVNode("v-if", true)
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`compiler: cacheStatic transform > should hoist v-for children if static 1`] = `
|
||||
"const _Vue = Vue
|
||||
const { createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
const _hoisted_1 = { id: "foo" }
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
with (_ctx) {
|
||||
const { renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createElementVNode: _createElementVNode } = _Vue
|
||||
|
||||
return (_openBlock(), _createElementBlock("div", null, [
|
||||
(_openBlock(true), _createElementBlock(_Fragment, null, _renderList(list, (i) => {
|
||||
return (_openBlock(), _createElementBlock("div", _hoisted_1, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("span", null, null, -1 /* CACHED */)
|
||||
])))
|
||||
}), 256 /* UNKEYED_FRAGMENT */))
|
||||
]))
|
||||
}
|
||||
}"
|
||||
`;
|
||||
|
|
@ -25,18 +25,33 @@ import { createObjectMatcher, genFlagText } from '../testUtils'
|
|||
import { transformText } from '../../src/transforms/transformText'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
|
||||
const hoistedChildrenArrayMatcher = (startIndex = 1, length = 1) => ({
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: new Array(length).fill(0).map((_, i) => ({
|
||||
type: NodeTypes.ELEMENT,
|
||||
codegenNode: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_${startIndex + i}`,
|
||||
},
|
||||
})),
|
||||
const cachedChildrenArrayMatcher = (
|
||||
tags: string[],
|
||||
needArraySpread = false,
|
||||
) => ({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
needArraySpread,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: tags.map(tag => {
|
||||
if (tag === '') {
|
||||
return {
|
||||
type: NodeTypes.TEXT_CALL,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: NodeTypes.ELEMENT,
|
||||
codegenNode: {
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: JSON.stringify(tag),
|
||||
},
|
||||
}
|
||||
}
|
||||
}),
|
||||
},
|
||||
})
|
||||
|
||||
function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
||||
function transformWithCache(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template)
|
||||
transform(ast, {
|
||||
hoistStatic: true,
|
||||
|
|
@ -60,101 +75,253 @@ function transformWithHoist(template: string, options: CompilerOptions = {}) {
|
|||
return ast
|
||||
}
|
||||
|
||||
describe('compiler: hoistStatic transform', () => {
|
||||
test('should NOT hoist root node', () => {
|
||||
describe('compiler: cacheStatic transform', () => {
|
||||
test('should NOT cache root node', () => {
|
||||
// if the whole tree is static, the root still needs to be a block
|
||||
// so that it's patched in optimized mode to skip children
|
||||
const root = transformWithHoist(`<div/>`)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
const root = transformWithCache(`<div/>`)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
expect(root.cached.length).toBe(0)
|
||||
})
|
||||
|
||||
test('hoist simple element', () => {
|
||||
const root = transformWithHoist(
|
||||
test('cache root node children', () => {
|
||||
// we don't have access to the root codegenNode during the transform
|
||||
// so we only cache each child individually
|
||||
const root = transformWithCache(
|
||||
`<span class="inline">hello</span><span class="inline">hello</span>`,
|
||||
)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
children: [
|
||||
{ codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
|
||||
{ codegenNode: { type: NodeTypes.JS_CACHE_EXPRESSION } },
|
||||
],
|
||||
})
|
||||
expect(root.cached.length).toBe(2)
|
||||
})
|
||||
|
||||
test('cache single children array', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><span class="inline">hello</span></div>`,
|
||||
)
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"span"`,
|
||||
props: createObjectMatcher({ class: 'inline' }),
|
||||
children: {
|
||||
type: NodeTypes.TEXT,
|
||||
content: `hello`,
|
||||
},
|
||||
},
|
||||
hoistedChildrenArrayMatcher(),
|
||||
])
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
tag: `"div"`,
|
||||
props: undefined,
|
||||
children: { content: `_hoisted_2` },
|
||||
children: cachedChildrenArrayMatcher(['span']),
|
||||
})
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist nested static tree', () => {
|
||||
const root = transformWithHoist(`<div><p><span/><span/></p></div>`)
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"p"`,
|
||||
props: undefined,
|
||||
children: [
|
||||
{ type: NodeTypes.ELEMENT, tag: `span` },
|
||||
{ type: NodeTypes.ELEMENT, tag: `span` },
|
||||
],
|
||||
},
|
||||
hoistedChildrenArrayMatcher(),
|
||||
])
|
||||
test('cache nested children array', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><p><span/><span/></p><p><span/><span/></p></div>`,
|
||||
)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject(
|
||||
cachedChildrenArrayMatcher(['p', 'p']),
|
||||
)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('cache nested static tree with comments', () => {
|
||||
const root = transformWithCache(`<div><div><!--comment--></div></div>`)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject(
|
||||
cachedChildrenArrayMatcher(['div']),
|
||||
)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('cache siblings including text with common non-hoistable parent', () => {
|
||||
const root = transformWithCache(`<div><span/>foo<div/></div>`)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject(
|
||||
cachedChildrenArrayMatcher(['span', '', 'div']),
|
||||
)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('cache inside default slot', () => {
|
||||
const root = transformWithCache(`<Foo>{{x}}<span/></Foo>`)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject({
|
||||
content: '_hoisted_2',
|
||||
properties: [
|
||||
{
|
||||
key: { content: 'default' },
|
||||
value: {
|
||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||
returns: [
|
||||
{
|
||||
type: NodeTypes.TEXT_CALL,
|
||||
},
|
||||
// first slot child cached
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
codegenNode: {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist nested static tree with comments', () => {
|
||||
const root = transformWithHoist(`<div><div><!--comment--></div></div>`)
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
props: undefined,
|
||||
children: [{ type: NodeTypes.COMMENT, content: `comment` }],
|
||||
},
|
||||
hoistedChildrenArrayMatcher(),
|
||||
])
|
||||
test('cache default slot as a whole', () => {
|
||||
const root = transformWithCache(`<Foo><span/><span/></Foo>`)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject({
|
||||
content: `_hoisted_2`,
|
||||
properties: [
|
||||
{
|
||||
key: { content: 'default' },
|
||||
value: {
|
||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||
returns: {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: [
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist siblings with common non-hoistable parent', () => {
|
||||
const root = transformWithHoist(`<div><span/><div/></div>`)
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"span"`,
|
||||
},
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
},
|
||||
hoistedChildrenArrayMatcher(1, 2),
|
||||
])
|
||||
test('cache inside named slot', () => {
|
||||
const root = transformWithCache(
|
||||
`<Foo><template #foo>{{x}}<span/></template></Foo>`,
|
||||
)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject({
|
||||
content: '_hoisted_3',
|
||||
properties: [
|
||||
{
|
||||
key: { content: 'foo' },
|
||||
value: {
|
||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||
returns: [
|
||||
{
|
||||
type: NodeTypes.TEXT_CALL,
|
||||
},
|
||||
// first slot child cached
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
codegenNode: {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist components', () => {
|
||||
const root = transformWithHoist(`<div><Comp/></div>`)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
test('cache named slot as a whole', () => {
|
||||
const root = transformWithCache(
|
||||
`<Foo><template #foo><span/><span/></template></Foo>`,
|
||||
)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject({
|
||||
properties: [
|
||||
{
|
||||
key: { content: 'foo' },
|
||||
value: {
|
||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||
returns: {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: [
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
test('cache dynamically named slot as a whole', () => {
|
||||
const root = transformWithCache(
|
||||
`<Foo><template #[foo]><span/><span/></template></Foo>`,
|
||||
)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject({
|
||||
properties: [
|
||||
{
|
||||
key: { content: 'foo', isStatic: false },
|
||||
value: {
|
||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||
returns: {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: [
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
test('cache dynamically named (expression) slot as a whole', () => {
|
||||
const root = transformWithCache(
|
||||
`<Foo><template #[foo+1]><span/><span/></template></Foo>`,
|
||||
{ prefixIdentifiers: true },
|
||||
)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject({
|
||||
properties: [
|
||||
{
|
||||
key: { type: NodeTypes.COMPOUND_EXPRESSION },
|
||||
value: {
|
||||
type: NodeTypes.JS_FUNCTION_EXPRESSION,
|
||||
returns: {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: [
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
{ type: NodeTypes.ELEMENT },
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
/* _ slot flag */
|
||||
},
|
||||
],
|
||||
})
|
||||
})
|
||||
|
||||
test('should NOT cache components', () => {
|
||||
const root = transformWithCache(`<div><Comp/></div>`)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
|
|
@ -164,11 +331,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
},
|
||||
])
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist element with dynamic props (but hoist the props list)', () => {
|
||||
const root = transformWithHoist(`<div><div :id="foo"/></div>`)
|
||||
test('should NOT cache element with dynamic props (but hoist the props list)', () => {
|
||||
const root = transformWithCache(`<div><div :id="foo"/></div>`)
|
||||
expect(root.hoists.length).toBe(1)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||
{
|
||||
|
|
@ -189,31 +357,23 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
},
|
||||
])
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist element with static key', () => {
|
||||
const root = transformWithHoist(`<div><div key="foo"/></div>`)
|
||||
expect(root.hoists.length).toBe(2)
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
props: createObjectMatcher({ key: 'foo' }),
|
||||
},
|
||||
hoistedChildrenArrayMatcher(),
|
||||
])
|
||||
test('cache element with static key', () => {
|
||||
const root = transformWithCache(`<div><div key="foo"/></div>`)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
tag: `"div"`,
|
||||
props: undefined,
|
||||
children: { content: `_hoisted_2` },
|
||||
children: cachedChildrenArrayMatcher(['div']),
|
||||
})
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist element with dynamic key', () => {
|
||||
const root = transformWithHoist(`<div><div :key="foo"/></div>`)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
test('should NOT cache element with dynamic key', () => {
|
||||
const root = transformWithCache(`<div><div :key="foo"/></div>`)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
|
|
@ -226,12 +386,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
},
|
||||
])
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist element with dynamic ref', () => {
|
||||
const root = transformWithHoist(`<div><div :ref="foo"/></div>`)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
test('should NOT cache element with dynamic ref', () => {
|
||||
const root = transformWithCache(`<div><div :ref="foo"/></div>`)
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.ELEMENT,
|
||||
|
|
@ -246,11 +406,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
},
|
||||
])
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist static props for elements with directives', () => {
|
||||
const root = transformWithHoist(`<div><div id="foo" v-foo/></div>`)
|
||||
const root = transformWithCache(`<div><div id="foo" v-foo/></div>`)
|
||||
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||
{
|
||||
|
|
@ -270,11 +431,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
},
|
||||
])
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist static props for elements with dynamic text children', () => {
|
||||
const root = transformWithHoist(
|
||||
const root = transformWithCache(
|
||||
`<div><div id="foo">{{ hello }}</div></div>`,
|
||||
)
|
||||
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
||||
|
|
@ -290,11 +452,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
},
|
||||
])
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist static props for elements with unhoistable children', () => {
|
||||
const root = transformWithHoist(`<div><div id="foo"><Comp/></div></div>`)
|
||||
const root = transformWithCache(`<div><div id="foo"><Comp/></div></div>`)
|
||||
expect(root.hoists).toMatchObject([createObjectMatcher({ id: 'foo' })])
|
||||
expect((root.codegenNode as VNodeCall).children).toMatchObject([
|
||||
{
|
||||
|
|
@ -307,11 +470,12 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
},
|
||||
])
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should hoist v-if props/children if static', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should cache v-if props/children if static', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><div v-if="ok" id="foo"><span/></div></div>`,
|
||||
)
|
||||
expect(root.hoists).toMatchObject([
|
||||
|
|
@ -319,40 +483,31 @@ describe('compiler: hoistStatic transform', () => {
|
|||
key: `[0]`, // key injected by v-if branch
|
||||
id: 'foo',
|
||||
}),
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"span"`,
|
||||
},
|
||||
hoistedChildrenArrayMatcher(2),
|
||||
])
|
||||
expect(
|
||||
((root.children[0] as ElementNode).children[0] as IfNode).codegenNode,
|
||||
).toMatchObject({
|
||||
type: NodeTypes.JS_CONDITIONAL_EXPRESSION,
|
||||
consequent: {
|
||||
// blocks should NOT be hoisted
|
||||
// blocks should NOT be cached
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
props: { content: `_hoisted_1` },
|
||||
children: { content: `_hoisted_3` },
|
||||
children: cachedChildrenArrayMatcher(['span']),
|
||||
},
|
||||
})
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should hoist v-for children if static', () => {
|
||||
const root = transformWithHoist(
|
||||
const root = transformWithCache(
|
||||
`<div><div v-for="i in list" id="foo"><span/></div></div>`,
|
||||
)
|
||||
expect(root.hoists).toMatchObject([
|
||||
createObjectMatcher({
|
||||
id: 'foo',
|
||||
}),
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"span"`,
|
||||
},
|
||||
hoistedChildrenArrayMatcher(2),
|
||||
])
|
||||
const forBlockCodegen = (
|
||||
(root.children[0] as ElementNode).children[0] as ForNode
|
||||
|
|
@ -372,78 +527,47 @@ describe('compiler: hoistStatic transform', () => {
|
|||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
props: { content: `_hoisted_1` },
|
||||
children: { content: `_hoisted_3` },
|
||||
children: cachedChildrenArrayMatcher(['span']),
|
||||
})
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
describe('prefixIdentifiers', () => {
|
||||
test('hoist nested static tree with static interpolation', () => {
|
||||
const root = transformWithHoist(
|
||||
test('cache nested static tree with static interpolation', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><span>foo {{ 1 }} {{ true }}</span></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
},
|
||||
)
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"span"`,
|
||||
props: undefined,
|
||||
children: {
|
||||
type: NodeTypes.COMPOUND_EXPRESSION,
|
||||
},
|
||||
},
|
||||
hoistedChildrenArrayMatcher(),
|
||||
])
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
tag: `"div"`,
|
||||
props: undefined,
|
||||
children: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_2`,
|
||||
},
|
||||
children: cachedChildrenArrayMatcher(['span']),
|
||||
})
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist nested static tree with static prop value', () => {
|
||||
const root = transformWithHoist(
|
||||
test('cache nested static tree with static prop value', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><span :foo="0">{{ 1 }}</span></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
},
|
||||
)
|
||||
|
||||
expect(root.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"span"`,
|
||||
props: createObjectMatcher({ foo: `[0]` }),
|
||||
children: {
|
||||
type: NodeTypes.INTERPOLATION,
|
||||
content: {
|
||||
content: `1`,
|
||||
isStatic: false,
|
||||
constType: ConstantTypes.CAN_STRINGIFY,
|
||||
},
|
||||
},
|
||||
},
|
||||
hoistedChildrenArrayMatcher(),
|
||||
])
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
tag: `"div"`,
|
||||
props: undefined,
|
||||
children: {
|
||||
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||
content: `_hoisted_2`,
|
||||
},
|
||||
children: cachedChildrenArrayMatcher(['span']),
|
||||
})
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('hoist class with static object value', () => {
|
||||
const root = transformWithHoist(
|
||||
const root = transformWithCache(
|
||||
`<div><span :class="{ foo: true }">{{ bar }}</span></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
|
|
@ -504,44 +628,44 @@ describe('compiler: hoistStatic transform', () => {
|
|||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist expressions that refer scope variables', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should NOT cache expressions that refer scope variables', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><p v-for="o in list"><span>{{ o }}</span></p></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
},
|
||||
)
|
||||
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist expressions that refer scope variables (2)', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should NOT cache expressions that refer scope variables (2)', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><p v-for="o in list"><span>{{ o + 'foo' }}</span></p></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
},
|
||||
)
|
||||
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist expressions that refer scope variables (v-slot)', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should NOT cache expressions that refer scope variables (v-slot)', () => {
|
||||
const root = transformWithCache(
|
||||
`<Comp v-slot="{ foo }">{{ foo }}</Comp>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
},
|
||||
)
|
||||
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(root.cached.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist elements with cached handlers', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should NOT cache elements with cached handlers', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><div><div @click="foo"/></div></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
|
|
@ -549,7 +673,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
)
|
||||
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(
|
||||
generate(root, {
|
||||
|
|
@ -559,8 +683,8 @@ describe('compiler: hoistStatic transform', () => {
|
|||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist elements with cached handlers + other bindings', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should NOT cache elements with cached handlers + other bindings', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><div><div :class="{}" @click="foo"/></div></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
|
|
@ -568,7 +692,7 @@ describe('compiler: hoistStatic transform', () => {
|
|||
},
|
||||
)
|
||||
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(
|
||||
generate(root, {
|
||||
|
|
@ -578,32 +702,66 @@ describe('compiler: hoistStatic transform', () => {
|
|||
).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist keyed template v-for with plain element child', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should NOT cache keyed template v-for with plain element child', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><template v-for="item in items" :key="item"><span/></template></div>`,
|
||||
)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should NOT hoist SVG with directives', () => {
|
||||
const root = transformWithHoist(
|
||||
test('should NOT cache SVG with directives', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`,
|
||||
)
|
||||
expect(root.hoists.length).toBe(2)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
children: [
|
||||
{
|
||||
tag: 'svg',
|
||||
// only cache the children, not the svg tag itself
|
||||
codegenNode: {
|
||||
children: {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('clone hoisted array children in HMR mode', () => {
|
||||
const root = transformWithHoist(`<div><span class="hi"></span></div>`, {
|
||||
hmr: true,
|
||||
})
|
||||
expect(root.hoists.length).toBe(2)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
children: {
|
||||
content: '[..._hoisted_2]',
|
||||
test('clone hoisted array children in v-for + HMR mode', () => {
|
||||
const root = transformWithCache(
|
||||
`<div><div v-for="i in 1"><span class="hi"></span></div></div>`,
|
||||
{
|
||||
hmr: true,
|
||||
},
|
||||
)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const forBlockCodegen = (
|
||||
(root.children[0] as ElementNode).children[0] as ForNode
|
||||
).codegenNode
|
||||
expect(forBlockCodegen).toMatchObject({
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: FRAGMENT,
|
||||
props: undefined,
|
||||
children: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: RENDER_LIST,
|
||||
},
|
||||
patchFlag: genFlagText(PatchFlags.UNKEYED_FRAGMENT),
|
||||
})
|
||||
const innerBlockCodegen = forBlockCodegen!.children.arguments[1]
|
||||
expect(innerBlockCodegen.returns).toMatchObject({
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
children: cachedChildrenArrayMatcher(
|
||||
['span'],
|
||||
true /* needArraySpread */,
|
||||
),
|
||||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { ErrorCodes } from '../../src/errors'
|
||||
import { type CompilerOptions, generate } from '../../src'
|
||||
import { FRAGMENT, RENDER_LIST, RENDER_SLOT } from '../../src/runtimeHelpers'
|
||||
import { PatchFlags } from '@vue/shared'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { createObjectMatcher, genFlagText } from '../testUtils'
|
||||
|
||||
export function parseWithForTransform(
|
||||
|
|
@ -202,6 +202,18 @@ describe('compiler: v-for', () => {
|
|||
expect(forNode.valueAlias).toBeUndefined()
|
||||
expect((forNode.source as SimpleExpressionNode).content).toBe('items')
|
||||
})
|
||||
|
||||
test('source containing string expression with spaces', () => {
|
||||
const { node: forNode } = parseWithForTransform(
|
||||
`<span v-for="item in state ['my items']" />`,
|
||||
)
|
||||
expect(forNode.keyAlias).toBeUndefined()
|
||||
expect(forNode.objectIndexAlias).toBeUndefined()
|
||||
expect((forNode.valueAlias as SimpleExpressionNode).content).toBe('item')
|
||||
expect((forNode.source as SimpleExpressionNode).content).toBe(
|
||||
"state ['my items']",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('errors', () => {
|
||||
|
|
@ -253,6 +265,18 @@ describe('compiler: v-for', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('missing source and have multiple spaces with', () => {
|
||||
const onError = vi.fn()
|
||||
parseWithForTransform('<span v-for="item in " />', { onError })
|
||||
|
||||
expect(onError).toHaveBeenCalledTimes(1)
|
||||
expect(onError).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
code: ErrorCodes.X_V_FOR_MALFORMED_EXPRESSION,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test('missing value', () => {
|
||||
const onError = vi.fn()
|
||||
parseWithForTransform('<span v-for="in items" />', { onError })
|
||||
|
|
@ -1019,5 +1043,33 @@ describe('compiler: v-for', () => {
|
|||
})
|
||||
expect(generate(root).code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('template v-for key w/ :key shorthand on div', () => {
|
||||
const {
|
||||
node: { codegenNode },
|
||||
} = parseWithForTransform('<div v-for="key in keys" :key>test</div>')
|
||||
expect(codegenNode.patchFlag).toBe(
|
||||
`${PatchFlags.KEYED_FRAGMENT} /* ${PatchFlagNames[PatchFlags.KEYED_FRAGMENT]} */`,
|
||||
)
|
||||
})
|
||||
|
||||
test('template v-for key w/ :key shorthand on template injected to the child', () => {
|
||||
const {
|
||||
node: { codegenNode },
|
||||
} = parseWithForTransform(
|
||||
'<template v-for="key in keys" :key><div>test</div></template>',
|
||||
)
|
||||
expect(assertSharedCodegen(codegenNode, true)).toMatchObject({
|
||||
source: { content: `keys` },
|
||||
params: [{ content: `key` }],
|
||||
innerVNodeCall: {
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
tag: `"div"`,
|
||||
props: createObjectMatcher({
|
||||
key: '[key]',
|
||||
}),
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -399,7 +399,7 @@ describe('compiler: transform v-model', () => {
|
|||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const codegen = (root.children[0] as PlainElementNode)
|
||||
.codegenNode as VNodeCall
|
||||
// should not list cached prop in dynamicProps
|
||||
|
|
@ -417,7 +417,7 @@ describe('compiler: transform v-model', () => {
|
|||
cacheHandlers: true,
|
||||
},
|
||||
)
|
||||
expect(root.cached).toBe(0)
|
||||
expect(root.cached.length).toBe(0)
|
||||
const codegen = (
|
||||
(root.children[0] as ForNode).children[0] as PlainElementNode
|
||||
).codegenNode as VNodeCall
|
||||
|
|
@ -433,7 +433,7 @@ describe('compiler: transform v-model', () => {
|
|||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).not.toBe(2)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
})
|
||||
|
||||
test('should mark update handler dynamic if it refers slot scope variables', () => {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {
|
|||
baseParse as parse,
|
||||
transform,
|
||||
} from '../../src'
|
||||
import { transformFor } from '../../src/transforms/vFor'
|
||||
import { transformOn } from '../../src/transforms/vOn'
|
||||
import { transformElement } from '../../src/transforms/transformElement'
|
||||
import { transformExpression } from '../../src/transforms/transformExpression'
|
||||
|
|
@ -17,7 +18,7 @@ import { transformExpression } from '../../src/transforms/transformExpression'
|
|||
function parseWithVOn(template: string, options: CompilerOptions = {}) {
|
||||
const ast = parse(template, options)
|
||||
transform(ast, {
|
||||
nodeTransforms: [transformExpression, transformElement],
|
||||
nodeTransforms: [transformExpression, transformElement, transformFor],
|
||||
directiveTransforms: {
|
||||
on: transformOn,
|
||||
},
|
||||
|
|
@ -504,7 +505,7 @@ describe('compiler: transform v-on', () => {
|
|||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
@ -525,7 +526,7 @@ describe('compiler: transform v-on', () => {
|
|||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
@ -550,7 +551,7 @@ describe('compiler: transform v-on', () => {
|
|||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
@ -587,7 +588,7 @@ describe('compiler: transform v-on', () => {
|
|||
cacheHandlers: true,
|
||||
isNativeTag: tag => tag === 'div',
|
||||
})
|
||||
expect(root.cached).toBe(0)
|
||||
expect(root.cached.length).toBe(0)
|
||||
})
|
||||
|
||||
test('should not be cached inside v-once', () => {
|
||||
|
|
@ -598,8 +599,19 @@ describe('compiler: transform v-on', () => {
|
|||
cacheHandlers: true,
|
||||
},
|
||||
)
|
||||
expect(root.cached).not.toBe(2)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).not.toBe(2)
|
||||
expect(root.cached.length).toBe(1)
|
||||
})
|
||||
|
||||
test('unicode identifier should not be cached (v-for)', () => {
|
||||
const { root } = parseWithVOn(
|
||||
`<div v-for="项 in items" :key="value"><div v-on:click="foo(项)"/></div>`,
|
||||
{
|
||||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
},
|
||||
)
|
||||
expect(root.cached.length).toBe(0)
|
||||
})
|
||||
|
||||
test('inline function expression handler', () => {
|
||||
|
|
@ -607,7 +619,7 @@ describe('compiler: transform v-on', () => {
|
|||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
@ -631,7 +643,7 @@ describe('compiler: transform v-on', () => {
|
|||
cacheHandlers: true,
|
||||
},
|
||||
)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
@ -656,7 +668,7 @@ describe('compiler: transform v-on', () => {
|
|||
},
|
||||
)
|
||||
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
@ -688,7 +700,7 @@ describe('compiler: transform v-on', () => {
|
|||
cacheHandlers: true,
|
||||
},
|
||||
)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
@ -713,8 +725,8 @@ describe('compiler: transform v-on', () => {
|
|||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
const vnodeCall = node.codegenNode as VNodeCall
|
||||
// should not treat cached handler as dynamicProp, so no flags
|
||||
expect(vnodeCall.patchFlag).toBeUndefined()
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ function transformWithOnce(template: string, options: CompilerOptions = {}) {
|
|||
describe('compiler: v-once transform', () => {
|
||||
test('as root node', () => {
|
||||
const root = transformWithOnce(`<div :id="foo" v-once />`)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect(root.codegenNode).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
|
|
@ -37,7 +37,7 @@ describe('compiler: v-once transform', () => {
|
|||
|
||||
test('on nested plain element', () => {
|
||||
const root = transformWithOnce(`<div><div :id="foo" v-once /></div>`)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
|
|
@ -52,7 +52,7 @@ describe('compiler: v-once transform', () => {
|
|||
|
||||
test('on component', () => {
|
||||
const root = transformWithOnce(`<div><Comp :id="foo" v-once /></div>`)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
|
|
@ -67,7 +67,7 @@ describe('compiler: v-once transform', () => {
|
|||
|
||||
test('on slot outlet', () => {
|
||||
const root = transformWithOnce(`<div><slot v-once /></div>`)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
|
|
@ -84,7 +84,7 @@ describe('compiler: v-once transform', () => {
|
|||
test('inside v-once', () => {
|
||||
const root = transformWithOnce(`<div v-once><div v-once/></div>`)
|
||||
expect(root.cached).not.toBe(2)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
})
|
||||
|
||||
// cached nodes should be ignored by hoistStatic transform
|
||||
|
|
@ -92,7 +92,7 @@ describe('compiler: v-once transform', () => {
|
|||
const root = transformWithOnce(`<div><div v-once /></div>`, {
|
||||
hoistStatic: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect(root.hoists.length).toBe(0)
|
||||
expect((root.children[0] as any).children[0].codegenNode).toMatchObject({
|
||||
|
|
@ -108,7 +108,7 @@ describe('compiler: v-once transform', () => {
|
|||
|
||||
test('with v-if/else', () => {
|
||||
const root = transformWithOnce(`<div v-if="BOOLEAN" v-once /><p v-else/>`)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect(root.children[0]).toMatchObject({
|
||||
type: NodeTypes.IF,
|
||||
|
|
@ -132,7 +132,7 @@ describe('compiler: v-once transform', () => {
|
|||
|
||||
test('with v-for', () => {
|
||||
const root = transformWithOnce(`<div v-for="i in list" v-once />`)
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
expect(root.helpers).toContain(SET_BLOCK_TRACKING)
|
||||
expect(root.children[0]).toMatchObject({
|
||||
type: NodeTypes.FOR,
|
||||
|
|
|
|||
|
|
@ -46,13 +46,13 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-core#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.6",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@vue/shared": "workspace:*",
|
||||
"entities": "^4.5.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.6"
|
||||
"@babel/types": "^7.24.7"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ export interface RootNode extends Node {
|
|||
directives: string[]
|
||||
hoists: (JSChildNode | null)[]
|
||||
imports: ImportItem[]
|
||||
cached: number
|
||||
cached: (CacheExpression | null)[]
|
||||
temps: number
|
||||
ssrHelpers?: symbol[]
|
||||
codegenNode?: TemplateChildNode | JSChildNode | BlockStatement
|
||||
|
|
@ -225,7 +225,7 @@ export interface DirectiveNode extends Node {
|
|||
export enum ConstantTypes {
|
||||
NOT_CONSTANT = 0,
|
||||
CAN_SKIP_PATCH,
|
||||
CAN_HOIST,
|
||||
CAN_CACHE,
|
||||
CAN_STRINGIFY,
|
||||
}
|
||||
|
||||
|
|
@ -337,6 +337,7 @@ export interface VNodeCall extends Node {
|
|||
| SlotsExpression // component slots
|
||||
| ForRenderListExpression // v-for fragment call
|
||||
| SimpleExpressionNode // hoisted
|
||||
| CacheExpression // cached
|
||||
| undefined
|
||||
patchFlag: string | undefined
|
||||
dynamicProps: string | SimpleExpressionNode | undefined
|
||||
|
|
@ -423,7 +424,8 @@ export interface CacheExpression extends Node {
|
|||
type: NodeTypes.JS_CACHE_EXPRESSION
|
||||
index: number
|
||||
value: JSChildNode
|
||||
isVNode: boolean
|
||||
needPauseTracking: boolean
|
||||
needArraySpread: boolean
|
||||
}
|
||||
|
||||
export interface MemoExpression extends CallExpression {
|
||||
|
|
@ -518,7 +520,7 @@ export interface SlotsObjectProperty extends Property {
|
|||
}
|
||||
|
||||
export interface SlotFunctionExpression extends FunctionExpression {
|
||||
returns: TemplateChildNode[]
|
||||
returns: TemplateChildNode[] | CacheExpression
|
||||
}
|
||||
|
||||
// createSlots({ ... }, [
|
||||
|
|
@ -605,7 +607,7 @@ export function createRoot(
|
|||
directives: [],
|
||||
hoists: [],
|
||||
imports: [],
|
||||
cached: 0,
|
||||
cached: [],
|
||||
temps: 0,
|
||||
codegenNode: undefined,
|
||||
loc: locStub,
|
||||
|
|
@ -778,13 +780,14 @@ export function createConditionalExpression(
|
|||
export function createCacheExpression(
|
||||
index: number,
|
||||
value: JSChildNode,
|
||||
isVNode: boolean = false,
|
||||
needPauseTracking: boolean = false,
|
||||
): CacheExpression {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
index,
|
||||
value,
|
||||
isVNode,
|
||||
needPauseTracking: needPauseTracking,
|
||||
needArraySpread: false,
|
||||
loc: locStub,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,8 +43,6 @@ import {
|
|||
CREATE_TEXT,
|
||||
CREATE_VNODE,
|
||||
OPEN_BLOCK,
|
||||
POP_SCOPE_ID,
|
||||
PUSH_SCOPE_ID,
|
||||
RESOLVE_COMPONENT,
|
||||
RESOLVE_DIRECTIVE,
|
||||
RESOLVE_FILTER,
|
||||
|
|
@ -490,11 +488,6 @@ function genModulePreamble(
|
|||
ssrRuntimeModuleName,
|
||||
} = context
|
||||
|
||||
if (genScopeId && ast.hoists.length) {
|
||||
ast.helpers.add(PUSH_SCOPE_ID)
|
||||
ast.helpers.add(POP_SCOPE_ID)
|
||||
}
|
||||
|
||||
// generate import statements for helpers
|
||||
if (ast.helpers.size) {
|
||||
const helpers = Array.from(ast.helpers)
|
||||
|
|
@ -583,33 +576,14 @@ function genHoists(hoists: (JSChildNode | null)[], context: CodegenContext) {
|
|||
return
|
||||
}
|
||||
context.pure = true
|
||||
const { push, newline, helper, scopeId, mode } = context
|
||||
const genScopeId = !__BROWSER__ && scopeId != null && mode !== 'function'
|
||||
const { push, newline } = context
|
||||
newline()
|
||||
|
||||
// generate inlined withScopeId helper
|
||||
if (genScopeId) {
|
||||
push(
|
||||
`const _withScopeId = n => (${helper(
|
||||
PUSH_SCOPE_ID,
|
||||
)}("${scopeId}"),n=n(),${helper(POP_SCOPE_ID)}(),n)`,
|
||||
)
|
||||
newline()
|
||||
}
|
||||
|
||||
for (let i = 0; i < hoists.length; i++) {
|
||||
const exp = hoists[i]
|
||||
if (exp) {
|
||||
const needScopeIdWrapper = genScopeId && exp.type === NodeTypes.VNODE_CALL
|
||||
push(
|
||||
`const _hoisted_${i + 1} = ${
|
||||
needScopeIdWrapper ? `${PURE_ANNOTATION} _withScopeId(() => ` : ``
|
||||
}`,
|
||||
)
|
||||
push(`const _hoisted_${i + 1} = `)
|
||||
genNode(exp, context)
|
||||
if (needScopeIdWrapper) {
|
||||
push(`)`)
|
||||
}
|
||||
newline()
|
||||
}
|
||||
}
|
||||
|
|
@ -1024,15 +998,19 @@ function genConditionalExpression(
|
|||
|
||||
function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
||||
const { push, helper, indent, deindent, newline } = context
|
||||
const { needPauseTracking, needArraySpread } = node
|
||||
if (needArraySpread) {
|
||||
push(`[...(`)
|
||||
}
|
||||
push(`_cache[${node.index}] || (`)
|
||||
if (node.isVNode) {
|
||||
if (needPauseTracking) {
|
||||
indent()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(-1),`)
|
||||
newline()
|
||||
}
|
||||
push(`_cache[${node.index}] = `)
|
||||
genNode(node.value, context)
|
||||
if (node.isVNode) {
|
||||
if (needPauseTracking) {
|
||||
push(`,`)
|
||||
newline()
|
||||
push(`${helper(SET_BLOCK_TRACKING)}(1),`)
|
||||
|
|
@ -1041,6 +1019,9 @@ function genCacheExpression(node: CacheExpression, context: CodegenContext) {
|
|||
deindent()
|
||||
}
|
||||
push(`)`)
|
||||
if (needArraySpread) {
|
||||
push(`)]`)
|
||||
}
|
||||
}
|
||||
|
||||
function genTemplateLiteral(node: TemplateLiteral, context: CodegenContext) {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export {
|
|||
type PropsExpression,
|
||||
} from './transforms/transformElement'
|
||||
export { processSlotOutlet } from './transforms/transformSlotOutlet'
|
||||
export { getConstantType } from './transforms/hoistStatic'
|
||||
export { getConstantType } from './transforms/cacheStatic'
|
||||
export { generateCodeFrame } from '@vue/shared'
|
||||
|
||||
// v2 compat only
|
||||
|
|
|
|||
|
|
@ -247,7 +247,16 @@ export interface TransformOptions
|
|||
*/
|
||||
isCustomElement?: (tag: string) => boolean | void
|
||||
/**
|
||||
* Hoist static VNodes and props objects to `_hoisted_x` constants
|
||||
* Transform expressions like {{ foo }} to `_ctx.foo`.
|
||||
* If this option is false, the generated code will be wrapped in a
|
||||
* `with (this) { ... }` block.
|
||||
* - This is force-enabled in module mode, since modules are by default strict
|
||||
* and cannot use `with`
|
||||
* @default mode === 'module'
|
||||
*/
|
||||
prefixIdentifiers?: boolean
|
||||
/**
|
||||
* Cache static VNodes and props objects to `_hoisted_x` constants
|
||||
* @default false
|
||||
*/
|
||||
hoistStatic?: boolean
|
||||
|
|
|
|||
|
|
@ -32,7 +32,14 @@ export const CAMELIZE = Symbol(__DEV__ ? `camelize` : ``)
|
|||
export const CAPITALIZE = Symbol(__DEV__ ? `capitalize` : ``)
|
||||
export const TO_HANDLER_KEY = Symbol(__DEV__ ? `toHandlerKey` : ``)
|
||||
export const SET_BLOCK_TRACKING = Symbol(__DEV__ ? `setBlockTracking` : ``)
|
||||
/**
|
||||
* @deprecated no longer needed in 3.5+ because we no longer hoist element nodes
|
||||
* but kept for backwards compat
|
||||
*/
|
||||
export const PUSH_SCOPE_ID = Symbol(__DEV__ ? `pushScopeId` : ``)
|
||||
/**
|
||||
* @deprecated kept for backwards compat
|
||||
*/
|
||||
export const POP_SCOPE_ID = Symbol(__DEV__ ? `popScopeId` : ``)
|
||||
export const WITH_CTX = Symbol(__DEV__ ? `withCtx` : ``)
|
||||
export const UNREF = Symbol(__DEV__ ? `unref` : ``)
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ import {
|
|||
helperNameMap,
|
||||
} from './runtimeHelpers'
|
||||
import { isVSlot } from './utils'
|
||||
import { hoistStatic, isSingleElementRoot } from './transforms/hoistStatic'
|
||||
import { cacheStatic, isSingleElementRoot } from './transforms/cacheStatic'
|
||||
import type { CompilerCompatOptions } from './compat/compatConfig'
|
||||
|
||||
// There are two types of transforms:
|
||||
|
|
@ -93,7 +93,7 @@ export interface TransformContext
|
|||
hoists: (JSChildNode | null)[]
|
||||
imports: ImportItem[]
|
||||
temps: number
|
||||
cached: number
|
||||
cached: (CacheExpression | null)[]
|
||||
identifiers: { [name: string]: number | undefined }
|
||||
scopes: {
|
||||
vFor: number
|
||||
|
|
@ -117,7 +117,7 @@ export interface TransformContext
|
|||
addIdentifiers(exp: ExpressionNode | string): void
|
||||
removeIdentifiers(exp: ExpressionNode | string): void
|
||||
hoist(exp: string | JSChildNode | ArrayExpression): SimpleExpressionNode
|
||||
cache<T extends JSChildNode>(exp: T, isVNode?: boolean): CacheExpression | T
|
||||
cache(exp: JSChildNode, isVNode?: boolean): CacheExpression
|
||||
constantCache: WeakMap<TemplateChildNode, ConstantTypes>
|
||||
|
||||
// 2.x Compat only
|
||||
|
|
@ -185,9 +185,9 @@ export function createTransformContext(
|
|||
directives: new Set(),
|
||||
hoists: [],
|
||||
imports: [],
|
||||
cached: [],
|
||||
constantCache: new WeakMap(),
|
||||
temps: 0,
|
||||
cached: 0,
|
||||
identifiers: Object.create(null),
|
||||
scopes: {
|
||||
vFor: 0,
|
||||
|
|
@ -291,13 +291,19 @@ export function createTransformContext(
|
|||
`_hoisted_${context.hoists.length}`,
|
||||
false,
|
||||
exp.loc,
|
||||
ConstantTypes.CAN_HOIST,
|
||||
ConstantTypes.CAN_CACHE,
|
||||
)
|
||||
identifier.hoisted = exp
|
||||
return identifier
|
||||
},
|
||||
cache(exp, isVNode = false) {
|
||||
return createCacheExpression(context.cached++, exp, isVNode)
|
||||
const cacheExp = createCacheExpression(
|
||||
context.cached.length,
|
||||
exp,
|
||||
isVNode,
|
||||
)
|
||||
context.cached.push(cacheExp)
|
||||
return cacheExp
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -324,7 +330,7 @@ export function transform(root: RootNode, options: TransformOptions) {
|
|||
const context = createTransformContext(root, options)
|
||||
traverseNode(root, context)
|
||||
if (options.hoistStatic) {
|
||||
hoistStatic(root, context)
|
||||
cacheStatic(root, context)
|
||||
}
|
||||
if (!options.ssr) {
|
||||
createRootCodegen(root, context)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,20 @@
|
|||
import {
|
||||
type CacheExpression,
|
||||
type CallExpression,
|
||||
type ComponentNode,
|
||||
ConstantTypes,
|
||||
ElementTypes,
|
||||
type ExpressionNode,
|
||||
type JSChildNode,
|
||||
NodeTypes,
|
||||
type ParentNode,
|
||||
type PlainElementNode,
|
||||
type RootNode,
|
||||
type SimpleExpressionNode,
|
||||
type SlotFunctionExpression,
|
||||
type TemplateChildNode,
|
||||
type TemplateNode,
|
||||
type TextCallNode,
|
||||
type VNodeCall,
|
||||
createArrayExpression,
|
||||
getVNodeBlockHelper,
|
||||
|
|
@ -18,7 +22,7 @@ import {
|
|||
} from '../ast'
|
||||
import type { TransformContext } from '../transform'
|
||||
import { PatchFlags, isArray, isString, isSymbol } from '@vue/shared'
|
||||
import { isSlotOutlet } from '../utils'
|
||||
import { findDir, isSlotOutlet } from '../utils'
|
||||
import {
|
||||
GUARD_REACTIVE_PROPS,
|
||||
NORMALIZE_CLASS,
|
||||
|
|
@ -27,9 +31,10 @@ import {
|
|||
OPEN_BLOCK,
|
||||
} from '../runtimeHelpers'
|
||||
|
||||
export function hoistStatic(root: RootNode, context: TransformContext) {
|
||||
export function cacheStatic(root: RootNode, context: TransformContext) {
|
||||
walk(
|
||||
root,
|
||||
undefined,
|
||||
context,
|
||||
// Root node is unfortunately non-hoistable due to potential parent
|
||||
// fallthrough attributes.
|
||||
|
|
@ -51,16 +56,16 @@ export function isSingleElementRoot(
|
|||
|
||||
function walk(
|
||||
node: ParentNode,
|
||||
parent: ParentNode | undefined,
|
||||
context: TransformContext,
|
||||
doNotHoistNode: boolean = false,
|
||||
inFor = false,
|
||||
) {
|
||||
const { children } = node
|
||||
const originalCount = children.length
|
||||
let hoistedCount = 0
|
||||
|
||||
const toCache: (PlainElementNode | TextCallNode)[] = []
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
// only plain elements & text calls are eligible for hoisting.
|
||||
// only plain elements & text calls are eligible for caching.
|
||||
if (
|
||||
child.type === NodeTypes.ELEMENT &&
|
||||
child.tagType === ElementTypes.ELEMENT
|
||||
|
|
@ -69,11 +74,10 @@ function walk(
|
|||
? ConstantTypes.NOT_CONSTANT
|
||||
: getConstantType(child, context)
|
||||
if (constantType > ConstantTypes.NOT_CONSTANT) {
|
||||
if (constantType >= ConstantTypes.CAN_HOIST) {
|
||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
||||
;(child.codegenNode as VNodeCall).patchFlag =
|
||||
PatchFlags.HOISTED + (__DEV__ ? ` /* HOISTED */` : ``)
|
||||
child.codegenNode = context.hoist(child.codegenNode!)
|
||||
hoistedCount++
|
||||
PatchFlags.CACHED + (__DEV__ ? ` /* CACHED */` : ``)
|
||||
toCache.push(child)
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
|
|
@ -87,7 +91,7 @@ function walk(
|
|||
flag === PatchFlags.NEED_PATCH ||
|
||||
flag === PatchFlags.TEXT) &&
|
||||
getGeneratedPropsConstantType(child, context) >=
|
||||
ConstantTypes.CAN_HOIST
|
||||
ConstantTypes.CAN_CACHE
|
||||
) {
|
||||
const props = getNodeProps(child)
|
||||
if (props) {
|
||||
|
|
@ -99,6 +103,14 @@ function walk(
|
|||
}
|
||||
}
|
||||
}
|
||||
} else if (child.type === NodeTypes.TEXT_CALL) {
|
||||
const constantType = doNotHoistNode
|
||||
? ConstantTypes.NOT_CONSTANT
|
||||
: getConstantType(child, context)
|
||||
if (constantType >= ConstantTypes.CAN_CACHE) {
|
||||
toCache.push(child)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// walk further
|
||||
|
|
@ -107,54 +119,122 @@ function walk(
|
|||
if (isComponent) {
|
||||
context.scopes.vSlot++
|
||||
}
|
||||
walk(child, context)
|
||||
walk(child, node, context, false, inFor)
|
||||
if (isComponent) {
|
||||
context.scopes.vSlot--
|
||||
}
|
||||
} else if (child.type === NodeTypes.FOR) {
|
||||
// Do not hoist v-for single child because it has to be a block
|
||||
walk(child, context, child.children.length === 1)
|
||||
walk(child, node, context, child.children.length === 1, true)
|
||||
} else if (child.type === NodeTypes.IF) {
|
||||
for (let i = 0; i < child.branches.length; i++) {
|
||||
// Do not hoist v-if single child because it has to be a block
|
||||
walk(
|
||||
child.branches[i],
|
||||
node,
|
||||
context,
|
||||
child.branches[i].children.length === 1,
|
||||
inFor,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hoistedCount && context.transformHoist) {
|
||||
context.transformHoist(children, context, node)
|
||||
let cachedAsArray = false
|
||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||
if (
|
||||
node.tagType === ElementTypes.ELEMENT &&
|
||||
node.codegenNode &&
|
||||
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
isArray(node.codegenNode.children)
|
||||
) {
|
||||
// all children were hoisted - the entire children array is cacheable.
|
||||
node.codegenNode.children = getCacheExpression(
|
||||
createArrayExpression(node.codegenNode.children),
|
||||
)
|
||||
cachedAsArray = true
|
||||
} else if (
|
||||
node.tagType === ElementTypes.COMPONENT &&
|
||||
node.codegenNode &&
|
||||
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
node.codegenNode.children &&
|
||||
!isArray(node.codegenNode.children) &&
|
||||
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||
) {
|
||||
// default slot
|
||||
const slot = getSlotNode(node.codegenNode, 'default')
|
||||
if (slot) {
|
||||
slot.returns = getCacheExpression(
|
||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||
)
|
||||
cachedAsArray = true
|
||||
}
|
||||
} else if (
|
||||
node.tagType === ElementTypes.TEMPLATE &&
|
||||
parent &&
|
||||
parent.type === NodeTypes.ELEMENT &&
|
||||
parent.tagType === ElementTypes.COMPONENT &&
|
||||
parent.codegenNode &&
|
||||
parent.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
parent.codegenNode.children &&
|
||||
!isArray(parent.codegenNode.children) &&
|
||||
parent.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||
) {
|
||||
// named <template> slot
|
||||
const slotName = findDir(node, 'slot', true)
|
||||
const slot =
|
||||
slotName &&
|
||||
slotName.arg &&
|
||||
getSlotNode(parent.codegenNode, slotName.arg)
|
||||
if (slot) {
|
||||
slot.returns = getCacheExpression(
|
||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||
)
|
||||
cachedAsArray = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// all children were hoisted - the entire children array is hoistable.
|
||||
if (
|
||||
hoistedCount &&
|
||||
hoistedCount === originalCount &&
|
||||
node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.ELEMENT &&
|
||||
node.codegenNode &&
|
||||
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
isArray(node.codegenNode.children)
|
||||
) {
|
||||
const hoisted = context.hoist(
|
||||
createArrayExpression(node.codegenNode.children),
|
||||
)
|
||||
// #6978, #7138, #7114
|
||||
// a hoisted children array inside v-for can caused HMR errors since
|
||||
// it might be mutated when mounting the v-for list
|
||||
if (context.hmr) {
|
||||
hoisted.content = `[...${hoisted.content}]`
|
||||
if (!cachedAsArray) {
|
||||
for (const child of toCache) {
|
||||
child.codegenNode = context.cache(child.codegenNode!)
|
||||
}
|
||||
node.codegenNode.children = hoisted
|
||||
}
|
||||
|
||||
function getCacheExpression(value: JSChildNode): CacheExpression {
|
||||
const exp = context.cache(value)
|
||||
// #6978, #7138, #7114
|
||||
// a cached children array inside v-for can caused HMR errors since
|
||||
// it might be mutated when mounting the first item
|
||||
if (inFor && context.hmr) {
|
||||
exp.needArraySpread = true
|
||||
}
|
||||
return exp
|
||||
}
|
||||
|
||||
function getSlotNode(
|
||||
node: VNodeCall,
|
||||
name: string | ExpressionNode,
|
||||
): SlotFunctionExpression | undefined {
|
||||
if (
|
||||
node.children &&
|
||||
!isArray(node.children) &&
|
||||
node.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||
) {
|
||||
const slot = node.children.properties.find(
|
||||
p => p.key === name || (p.key as SimpleExpressionNode).content === name,
|
||||
)
|
||||
return slot && slot.value
|
||||
}
|
||||
}
|
||||
|
||||
if (toCache.length && context.transformHoist) {
|
||||
context.transformHoist(children, context, node)
|
||||
}
|
||||
}
|
||||
|
||||
export function getConstantType(
|
||||
node: TemplateChildNode | SimpleExpressionNode,
|
||||
node: TemplateChildNode | SimpleExpressionNode | CacheExpression,
|
||||
context: TransformContext,
|
||||
): ConstantTypes {
|
||||
const { constantCache } = context
|
||||
|
|
@ -284,6 +364,8 @@ export function getConstantType(
|
|||
}
|
||||
}
|
||||
return returnType
|
||||
case NodeTypes.JS_CACHE_EXPRESSION:
|
||||
return ConstantTypes.CAN_CACHE
|
||||
default:
|
||||
if (__DEV__) {
|
||||
const exhaustiveCheck: never = node
|
||||
|
|
@ -57,7 +57,7 @@ import {
|
|||
toValidAssetId,
|
||||
} from '../utils'
|
||||
import { buildSlots } from './vSlot'
|
||||
import { getConstantType } from './hoistStatic'
|
||||
import { getConstantType } from './cacheStatic'
|
||||
import { BindingTypes } from '../options'
|
||||
import {
|
||||
CompilerDeprecationTypes,
|
||||
|
|
|
|||
|
|
@ -252,7 +252,7 @@ export function processExpression(
|
|||
if (isLiteral) {
|
||||
node.constType = ConstantTypes.CAN_STRINGIFY
|
||||
} else {
|
||||
node.constType = ConstantTypes.CAN_HOIST
|
||||
node.constType = ConstantTypes.CAN_CACHE
|
||||
}
|
||||
}
|
||||
return node
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {
|
|||
import { isText } from '../utils'
|
||||
import { CREATE_TEXT } from '../runtimeHelpers'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { getConstantType } from './hoistStatic'
|
||||
import { getConstantType } from './cacheStatic'
|
||||
|
||||
// Merge adjacent text nodes and expressions into a single expression
|
||||
// e.g. <div>abc {{ d }} {{ e }}</div> should have a single expression node as child.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import type { DirectiveTransform } from '../transform'
|
||||
import type { DirectiveTransform, TransformContext } from '../transform'
|
||||
import {
|
||||
type DirectiveNode,
|
||||
type ExpressionNode,
|
||||
NodeTypes,
|
||||
type SimpleExpressionNode,
|
||||
|
|
@ -56,11 +57,8 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||
exp = dir.exp = createSimpleExpression(propName, false, arg.loc)
|
||||
if (!__BROWSER__) {
|
||||
exp = dir.exp = processExpression(exp, context)
|
||||
}
|
||||
transformBindShorthand(dir, context)
|
||||
exp = dir.exp!
|
||||
}
|
||||
|
||||
if (arg.type !== NodeTypes.SIMPLE_EXPRESSION) {
|
||||
|
|
@ -98,6 +96,19 @@ export const transformBind: DirectiveTransform = (dir, _node, context) => {
|
|||
}
|
||||
}
|
||||
|
||||
export const transformBindShorthand = (
|
||||
dir: DirectiveNode,
|
||||
context: TransformContext,
|
||||
) => {
|
||||
const arg = dir.arg!
|
||||
|
||||
const propName = camelize((arg as SimpleExpressionNode).content)
|
||||
dir.exp = createSimpleExpression(propName, false, arg.loc)
|
||||
if (!__BROWSER__) {
|
||||
dir.exp = processExpression(dir.exp, context)
|
||||
}
|
||||
}
|
||||
|
||||
const injectPrefix = (arg: ExpressionNode, prefix: string) => {
|
||||
if (arg.type === NodeTypes.SIMPLE_EXPRESSION) {
|
||||
if (arg.isStatic) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ import {
|
|||
import { processExpression } from './transformExpression'
|
||||
import { validateBrowserExpression } from '../validateExpression'
|
||||
import { PatchFlagNames, PatchFlags } from '@vue/shared'
|
||||
import { transformBindShorthand } from './vBind'
|
||||
|
||||
export const transformFor = createStructuralDirectiveTransform(
|
||||
'for',
|
||||
|
|
@ -60,13 +61,20 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
]) as ForRenderListExpression
|
||||
const isTemplate = isTemplateNode(node)
|
||||
const memo = findDir(node, 'memo')
|
||||
const keyProp = findProp(node, `key`)
|
||||
const keyProp = findProp(node, `key`, false, true)
|
||||
if (keyProp && keyProp.type === NodeTypes.DIRECTIVE && !keyProp.exp) {
|
||||
// resolve :key shorthand #10882
|
||||
transformBindShorthand(keyProp, context)
|
||||
}
|
||||
const keyExp =
|
||||
keyProp &&
|
||||
(keyProp.type === NodeTypes.ATTRIBUTE
|
||||
? createSimpleExpression(keyProp.value!.content, true)
|
||||
: keyProp.exp!)
|
||||
const keyProperty = keyProp ? createObjectProperty(`key`, keyExp!) : null
|
||||
? keyProp.value
|
||||
? createSimpleExpression(keyProp.value.content, true)
|
||||
: undefined
|
||||
: keyProp.exp)
|
||||
const keyProperty =
|
||||
keyProp && keyExp ? createObjectProperty(`key`, keyExp) : null
|
||||
|
||||
if (!__BROWSER__ && isTemplate) {
|
||||
// #2085 / #5288 process :key and v-memo expressions need to be
|
||||
|
|
@ -224,8 +232,10 @@ export const transformFor = createStructuralDirectiveTransform(
|
|||
renderExp.arguments.push(
|
||||
loop as ForIteratorExpression,
|
||||
createSimpleExpression(`_cache`),
|
||||
createSimpleExpression(String(context.cached++)),
|
||||
createSimpleExpression(String(context.cached.length)),
|
||||
)
|
||||
// increment cache count
|
||||
context.cached.push(null)
|
||||
} else {
|
||||
renderExp.arguments.push(
|
||||
createFunctionExpression(
|
||||
|
|
|
|||
|
|
@ -248,7 +248,7 @@ function createChildrenCodegenNode(
|
|||
`${keyIndex}`,
|
||||
false,
|
||||
locStub,
|
||||
ConstantTypes.CAN_HOIST,
|
||||
ConstantTypes.CAN_CACHE,
|
||||
),
|
||||
)
|
||||
const { children } = branch
|
||||
|
|
|
|||
|
|
@ -33,8 +33,10 @@ export const transformMemo: NodeTransform = (node, context) => {
|
|||
dir.exp!,
|
||||
createFunctionExpression(undefined, codegenNode),
|
||||
`_cache`,
|
||||
String(context.cached++),
|
||||
String(context.cached.length),
|
||||
]) as MemoExpression
|
||||
// increment cache count
|
||||
context.cached.push(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ export const transformModel: DirectiveTransform = (dir, node, context) => {
|
|||
`{ ${modifiers} }`,
|
||||
false,
|
||||
dir.loc,
|
||||
ConstantTypes.CAN_HOIST,
|
||||
ConstantTypes.CAN_CACHE,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import {
|
||||
type BlockCodegenNode,
|
||||
type CacheExpression,
|
||||
type CallExpression,
|
||||
type DirectiveNode,
|
||||
type ElementNode,
|
||||
|
|
@ -62,7 +63,7 @@ export function isCoreComponent(tag: string): symbol | void {
|
|||
}
|
||||
}
|
||||
|
||||
const nonIdentifierRE = /^\d|[^\$\w]/
|
||||
const nonIdentifierRE = /^\d|[^\$\w\xA0-\uFFFF]/
|
||||
export const isSimpleIdentifier = (name: string): boolean =>
|
||||
!nonIdentifierRE.test(name)
|
||||
|
||||
|
|
@ -445,7 +446,12 @@ export function toValidAssetId(
|
|||
|
||||
// Check if a node contains expressions that reference current context scope ids
|
||||
export function hasScopeRef(
|
||||
node: TemplateChildNode | IfBranchNode | ExpressionNode | undefined,
|
||||
node:
|
||||
| TemplateChildNode
|
||||
| IfBranchNode
|
||||
| ExpressionNode
|
||||
| CacheExpression
|
||||
| undefined,
|
||||
ids: TransformContext['identifiers'],
|
||||
): boolean {
|
||||
if (!node || Object.keys(ids).length === 0) {
|
||||
|
|
@ -488,6 +494,7 @@ export function hasScopeRef(
|
|||
return hasScopeRef(node.content, ids)
|
||||
case NodeTypes.TEXT:
|
||||
case NodeTypes.COMMENT:
|
||||
case NodeTypes.JS_CACHE_EXPRESSION:
|
||||
return false
|
||||
default:
|
||||
if (__DEV__) {
|
||||
|
|
@ -506,4 +513,4 @@ export function getMemoedVNodeCall(node: BlockCodegenNode | MemoExpression) {
|
|||
}
|
||||
}
|
||||
|
||||
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
|
||||
export const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+(\S[\s\S]*)/
|
||||
|
|
|
|||
|
|
@ -1,96 +1,128 @@
|
|||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`stringify static html > escape 1`] = `
|
||||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span><span class=\\"foo>ar\\">1 + <</span><span>&</span></div>", 1)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > serializing constant bindings 1`] = `
|
||||
"const { toDisplayString: _toDisplayString, normalizeClass: _normalizeClass, createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_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 number values 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("select", null, [
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 }),
|
||||
/*#__PURE__*/_createElementVNode("option", { value: 1 })
|
||||
], -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("select", null, [
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 }),
|
||||
_createElementVNode("option", { value: 1 })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should bail on bindings that are hoisted but not stringifiable 1`] = `
|
||||
exports[`stringify static html > should bail on bindings that are cached but not stringifiable 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, [
|
||||
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
/*#__PURE__*/_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
/*#__PURE__*/_createElementVNode("img", { src: _imports_0_ })
|
||||
], -1 /* HOISTED */)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createElementVNode("div", null, [
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("span", { class: "foo" }, "foo"),
|
||||
_createElementVNode("img", { src: _imports_0_ })
|
||||
], -1 /* CACHED */)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should work for <option> elements with string values 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<select><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option><option value=\\"1\\"></option></select>", 1)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should work for multiple adjacent nodes 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span>", 5)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should work on eligible content (elements > 20) 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("<div><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></div>", 1)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should work on eligible content (elements with binding > 5) 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("<div><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span><span class=\\"foo\\"></span></div>", 1)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > should work with bindings that are non-static but stringifiable 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode, openBlock: _openBlock, createElementBlock: _createElementBlock } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
||||
const _hoisted_2 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_2))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<div><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><span class=\\"foo\\">foo</span><img src=\\"" + _imports_0_ + "\\"></div>", 1)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > stringify v-html 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2)
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return _hoisted_1
|
||||
return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > stringify v-text 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2)
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return _hoisted_1
|
||||
return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code><span>show-it </span></code></pre><div class><span class>1</span><span class>2</span></div>", 2))
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`stringify static html > stringify v-text with escape 1`] = `
|
||||
"const { createElementVNode: _createElementVNode, createStaticVNode: _createStaticVNode } = Vue
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<pre data-type=\\"js\\"><code>text1</code></pre><div class><span class>1</span><span class>2</span></div>", 2)
|
||||
|
||||
return function render(_ctx, _cache) {
|
||||
return _hoisted_1
|
||||
return _cache[0] || (_cache[0] = _createStaticVNode("<pre data-type=\\"js\\"><code>text1</code></pre><div class><span class>1</span><span class>2</span></div>", 2))
|
||||
}"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -23,134 +23,147 @@ describe('stringify static html', () => {
|
|||
return code.repeat(n)
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert cached node NOT stringified
|
||||
*/
|
||||
function cachedArrayBailedMatcher(n = 1) {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: new Array(n).fill(0).map(() => ({
|
||||
// should remain VNODE_CALL instead of JS_CALL_EXPRESSION
|
||||
codegenNode: { type: NodeTypes.VNODE_CALL },
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert cached node is stringified (no content check)
|
||||
*/
|
||||
function cachedArraySuccessMatcher(n = 1) {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: new Array(n).fill(0).map(() => ({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
})),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Assert cached node stringified with desired content and node count
|
||||
*/
|
||||
function cachedArrayStaticNodeMatcher(content: string, count: number) {
|
||||
return {
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
elements: [
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [JSON.stringify(content), String(count)],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
test('should bail on non-eligible static trees', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div><div>hello</div><div>hello</div></div></div>`,
|
||||
)
|
||||
// should be a normal vnode call
|
||||
expect(ast.hoists[0]!.type).toBe(NodeTypes.VNODE_CALL)
|
||||
// should be cached children array
|
||||
expect(ast.cached[0]!.value.type).toBe(NodeTypes.JS_ARRAY_EXPRESSION)
|
||||
})
|
||||
|
||||
test('should work on eligible content (elements with binding > 5)', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
const { code, ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span class="foo"/>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div></div>`,
|
||||
)
|
||||
|
||||
// should be optimized now
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span class="foo"></span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div>`,
|
||||
),
|
||||
'1',
|
||||
],
|
||||
}, // the children array is hoisted as well
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
`<div>${repeat(
|
||||
`<span class="foo"></span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div>`,
|
||||
1,
|
||||
),
|
||||
])
|
||||
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should work on eligible content (elements > 20)', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
const { code, ast } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span/>`,
|
||||
StringifyThresholds.NODE_COUNT,
|
||||
)}</div></div>`,
|
||||
)
|
||||
// should be optimized now
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span></span>`,
|
||||
StringifyThresholds.NODE_COUNT,
|
||||
)}</div>`,
|
||||
),
|
||||
'1',
|
||||
],
|
||||
},
|
||||
// the children array is hoisted as well
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
`<div>${repeat(`<span></span>`, StringifyThresholds.NODE_COUNT)}</div>`,
|
||||
1,
|
||||
),
|
||||
])
|
||||
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should work for multiple adjacent nodes', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
const { ast, code } = compileWithStringify(
|
||||
`<div>${repeat(
|
||||
`<span class="foo"/>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div>`,
|
||||
)
|
||||
// should have 6 hoisted nodes (including the entire array),
|
||||
// but 2~5 should be null because they are merged into 1
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
repeat(
|
||||
`<span class="foo"></span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
),
|
||||
),
|
||||
'5',
|
||||
],
|
||||
},
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
repeat(
|
||||
`<span class="foo"></span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
),
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
),
|
||||
])
|
||||
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('serializing constant bindings', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
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.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div style="color:red;">${repeat(
|
||||
`<span class="foo bar">1 + false</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div>`,
|
||||
),
|
||||
'1',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
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 } = compileWithStringify(
|
||||
const { ast, code } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span :class="'foo' + '>ar'">{{ 1 }} + {{ '<' }}</span>` +
|
||||
`<span>&</span>`,
|
||||
|
|
@ -158,27 +171,19 @@ describe('stringify static html', () => {
|
|||
)}</div></div>`,
|
||||
)
|
||||
// should be optimized now
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<div>${repeat(
|
||||
`<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div>`,
|
||||
),
|
||||
'1',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
`<div>${repeat(
|
||||
`<span class="foo>ar">1 + <</span>` + `<span>&</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div>`,
|
||||
1,
|
||||
),
|
||||
])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should bail on bindings that are hoisted but not stringifiable', () => {
|
||||
test('should bail on bindings that are cached but not stringifiable', () => {
|
||||
const { ast, code } = compile(
|
||||
`<div><div>${repeat(
|
||||
`<span class="foo">foo</span>`,
|
||||
|
|
@ -195,7 +200,7 @@ describe('stringify static html', () => {
|
|||
'_imports_0_',
|
||||
false,
|
||||
node.loc,
|
||||
ConstantTypes.CAN_HOIST,
|
||||
ConstantTypes.CAN_CACHE,
|
||||
)
|
||||
node.props[0] = {
|
||||
type: NodeTypes.DIRECTIVE,
|
||||
|
|
@ -210,17 +215,7 @@ describe('stringify static html', () => {
|
|||
],
|
||||
},
|
||||
)
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
// the expression and the tree are still hoistable
|
||||
// but should stay NodeTypes.VNODE_CALL
|
||||
// if it's stringified it will be NodeTypes.JS_CALL_EXPRESSION
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
|
|
@ -258,35 +253,19 @@ describe('stringify static html', () => {
|
|||
],
|
||||
},
|
||||
)
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
// the hoisted node should be NodeTypes.JS_CALL_EXPRESSION
|
||||
// of `createStaticVNode()` instead of dynamic NodeTypes.VNODE_CALL
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(ast.cached).toMatchObject([cachedArraySuccessMatcher()])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
||||
// #1128
|
||||
test('should bail on non attribute bindings', () => {
|
||||
test('should bail on non-attribute bindings', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
`<div><div><input indeterminate>${repeat(
|
||||
`<span class="foo">foo</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div></div>`,
|
||||
)
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
|
||||
const { ast: ast2 } = compileWithStringify(
|
||||
`<div><div><input :indeterminate="true">${repeat(
|
||||
|
|
@ -294,46 +273,23 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div></div>`,
|
||||
)
|
||||
expect(ast2.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
})
|
||||
expect(ast2.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
|
||||
test('should bail on non attribute bindings', () => {
|
||||
const { ast } = compileWithStringify(
|
||||
const { ast: ast3 } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span class="foo">foo</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}<input indeterminate></div></div>`,
|
||||
)
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(ast3.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
|
||||
const { ast: ast2 } = compileWithStringify(
|
||||
const { ast: ast4 } = compileWithStringify(
|
||||
`<div><div>${repeat(
|
||||
`<span class="foo">foo</span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}<input :indeterminate="true"></div></div>`,
|
||||
)
|
||||
expect(ast2.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(ast4.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
})
|
||||
|
||||
test('should bail on tags that has placement constraints (eg.tables related tags)', () => {
|
||||
|
|
@ -343,14 +299,7 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</tbody></table>`,
|
||||
)
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
})
|
||||
|
||||
test('should bail inside slots', () => {
|
||||
|
|
@ -360,14 +309,9 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</foo>`,
|
||||
)
|
||||
expect(ast.hoists.length).toBe(
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)
|
||||
ast.hoists.forEach(node => {
|
||||
expect(node).toMatchObject({
|
||||
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
|
||||
})
|
||||
})
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
|
||||
])
|
||||
|
||||
const { ast: ast2 } = compileWithStringify(
|
||||
`<foo><template #foo>${repeat(
|
||||
|
|
@ -375,14 +319,9 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</template></foo>`,
|
||||
)
|
||||
expect(ast2.hoists.length).toBe(
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)
|
||||
ast2.hoists.forEach(node => {
|
||||
expect(node).toMatchObject({
|
||||
type: NodeTypes.VNODE_CALL, // not CALL_EXPRESSION
|
||||
})
|
||||
})
|
||||
expect(ast2.cached).toMatchObject([
|
||||
cachedArrayBailedMatcher(StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
|
||||
])
|
||||
})
|
||||
|
||||
test('should remove attribute for `null`', () => {
|
||||
|
|
@ -392,19 +331,13 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</div>`,
|
||||
)
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`${repeat(
|
||||
`<span></span>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}`,
|
||||
),
|
||||
'5',
|
||||
],
|
||||
})
|
||||
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
repeat(`<span></span>`, StringifyThresholds.ELEMENT_WITH_BINDING_COUNT),
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
// #6617
|
||||
|
|
@ -415,19 +348,24 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.NODE_COUNT,
|
||||
)}`,
|
||||
)
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<button>enable</button>${repeat(
|
||||
`<div></div>`,
|
||||
StringifyThresholds.NODE_COUNT,
|
||||
)}`,
|
||||
),
|
||||
'21',
|
||||
],
|
||||
})
|
||||
expect(ast.cached).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CACHE_EXPRESSION,
|
||||
value: {
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<button>enable</button>${repeat(
|
||||
`<div></div>`,
|
||||
StringifyThresholds.NODE_COUNT,
|
||||
)}`,
|
||||
),
|
||||
'21',
|
||||
],
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
test('should stringify svg', () => {
|
||||
|
|
@ -439,19 +377,16 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</svg></div>`,
|
||||
)
|
||||
expect(ast.hoists[0]).toMatchObject({
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`${svg}${repeat(
|
||||
repeated,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</svg>`,
|
||||
),
|
||||
'1',
|
||||
],
|
||||
})
|
||||
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
`${svg}${repeat(
|
||||
repeated,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</svg>`,
|
||||
1,
|
||||
),
|
||||
])
|
||||
})
|
||||
|
||||
// #5439
|
||||
|
|
@ -494,23 +429,14 @@ describe('stringify static html', () => {
|
|||
)}</select></div>`,
|
||||
)
|
||||
// should be optimized now
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.JS_CALL_EXPRESSION,
|
||||
callee: CREATE_STATIC,
|
||||
arguments: [
|
||||
JSON.stringify(
|
||||
`<select>${repeat(
|
||||
`<option value="1"></option>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</select>`,
|
||||
),
|
||||
'1',
|
||||
],
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
expect(ast.cached).toMatchObject([
|
||||
cachedArrayStaticNodeMatcher(
|
||||
`<select>${repeat(
|
||||
`<option value="1"></option>`,
|
||||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</select>`,
|
||||
1,
|
||||
),
|
||||
])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
|
|
@ -522,14 +448,7 @@ describe('stringify static html', () => {
|
|||
StringifyThresholds.ELEMENT_WITH_BINDING_COUNT,
|
||||
)}</select></div>`,
|
||||
)
|
||||
expect(ast.hoists).toMatchObject([
|
||||
{
|
||||
type: NodeTypes.VNODE_CALL,
|
||||
},
|
||||
{
|
||||
type: NodeTypes.JS_ARRAY_EXPRESSION,
|
||||
},
|
||||
])
|
||||
expect(ast.cached).toMatchObject([cachedArrayBailedMatcher()])
|
||||
expect(code).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ describe('compiler-dom: transform v-on', () => {
|
|||
prefixIdentifiers: true,
|
||||
cacheHandlers: true,
|
||||
})
|
||||
expect(root.cached).toBe(1)
|
||||
expect(root.cached.length).toBe(1)
|
||||
// should not treat cached handler as dynamicProp, so it should have no
|
||||
// dynamicProps flags and only the hydration flag
|
||||
expect((root as any).children[0].codegenNode.patchFlag).toBe(
|
||||
|
|
|
|||
|
|
@ -3,12 +3,12 @@
|
|||
*/
|
||||
import {
|
||||
CREATE_STATIC,
|
||||
type CacheExpression,
|
||||
ConstantTypes,
|
||||
type ElementNode,
|
||||
ElementTypes,
|
||||
type ExpressionNode,
|
||||
type HoistTransform,
|
||||
type JSChildNode,
|
||||
Namespaces,
|
||||
NodeTypes,
|
||||
type PlainElementNode,
|
||||
|
|
@ -16,11 +16,14 @@ import {
|
|||
type TemplateChildNode,
|
||||
type TextCallNode,
|
||||
type TransformContext,
|
||||
type VNodeCall,
|
||||
createArrayExpression,
|
||||
createCallExpression,
|
||||
isStaticArgOf,
|
||||
} from '@vue/compiler-core'
|
||||
import {
|
||||
escapeHtml,
|
||||
isArray,
|
||||
isBooleanAttr,
|
||||
isKnownHtmlAttr,
|
||||
isKnownSvgAttr,
|
||||
|
|
@ -76,6 +79,14 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
|
|||
return
|
||||
}
|
||||
|
||||
const isParentCached =
|
||||
parent.type === NodeTypes.ELEMENT &&
|
||||
parent.codegenNode &&
|
||||
parent.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||
parent.codegenNode.children &&
|
||||
!isArray(parent.codegenNode.children) &&
|
||||
parent.codegenNode.children.type === NodeTypes.JS_CACHE_EXPRESSION
|
||||
|
||||
let nc = 0 // current node count
|
||||
let ec = 0 // current element with binding count
|
||||
const currentChunk: StringifiableNode[] = []
|
||||
|
|
@ -94,19 +105,31 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
|
|||
// will insert / hydrate
|
||||
String(currentChunk.length),
|
||||
])
|
||||
// replace the first node's hoisted expression with the static vnode call
|
||||
replaceHoist(currentChunk[0], staticCall, context)
|
||||
|
||||
if (currentChunk.length > 1) {
|
||||
for (let i = 1; i < currentChunk.length; i++) {
|
||||
// for the merged nodes, set their hoisted expression to null
|
||||
replaceHoist(currentChunk[i], null, context)
|
||||
if (isParentCached) {
|
||||
;((parent.codegenNode as VNodeCall).children as CacheExpression).value =
|
||||
createArrayExpression([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(
|
||||
currentChunk[currentChunk.length - 1]
|
||||
.codegenNode as CacheExpression,
|
||||
)
|
||||
if (cacheIndex > -1) {
|
||||
for (let i = cacheIndex; i < context.cached.length; i++) {
|
||||
const c = context.cached[i]
|
||||
if (c) c.index -= deleteCount
|
||||
}
|
||||
context.cached.splice(cacheIndex - deleteCount + 1, deleteCount)
|
||||
}
|
||||
return deleteCount
|
||||
}
|
||||
|
||||
// also remove merged nodes from children
|
||||
const deleteCount = currentChunk.length - 1
|
||||
children.splice(currentIndex - currentChunk.length + 1, deleteCount)
|
||||
return deleteCount
|
||||
}
|
||||
}
|
||||
return 0
|
||||
|
|
@ -115,16 +138,15 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
|
|||
let i = 0
|
||||
for (; i < children.length; i++) {
|
||||
const child = children[i]
|
||||
const hoisted = getHoistedNode(child)
|
||||
if (hoisted) {
|
||||
// presence of hoisted means child must be a stringifiable node
|
||||
const node = child as StringifiableNode
|
||||
const result = analyzeNode(node)
|
||||
const isCached = isParentCached || getCachedNode(child)
|
||||
if (isCached) {
|
||||
// presence of cached means child must be a stringifiable node
|
||||
const result = analyzeNode(child as StringifiableNode)
|
||||
if (result) {
|
||||
// node is stringifiable, record state
|
||||
nc += result[0]
|
||||
ec += result[1]
|
||||
currentChunk.push(node)
|
||||
currentChunk.push(child as StringifiableNode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
|
@ -141,12 +163,19 @@ export const stringifyStatic: HoistTransform = (children, context, parent) => {
|
|||
stringifyCurrentChunk(i)
|
||||
}
|
||||
|
||||
const getHoistedNode = (node: TemplateChildNode) =>
|
||||
((node.type === NodeTypes.ELEMENT && node.tagType === ElementTypes.ELEMENT) ||
|
||||
node.type == NodeTypes.TEXT_CALL) &&
|
||||
node.codegenNode &&
|
||||
node.codegenNode.type === NodeTypes.SIMPLE_EXPRESSION &&
|
||||
node.codegenNode.hoisted
|
||||
const getCachedNode = (
|
||||
node: TemplateChildNode,
|
||||
): CacheExpression | undefined => {
|
||||
if (
|
||||
((node.type === NodeTypes.ELEMENT &&
|
||||
node.tagType === ElementTypes.ELEMENT) ||
|
||||
node.type === NodeTypes.TEXT_CALL) &&
|
||||
node.codegenNode &&
|
||||
node.codegenNode.type === NodeTypes.JS_CACHE_EXPRESSION
|
||||
) {
|
||||
return node.codegenNode
|
||||
}
|
||||
}
|
||||
|
||||
const dataAriaRE = /^(data|aria)-/
|
||||
const isStringifiableAttr = (name: string, ns: Namespaces) => {
|
||||
|
|
@ -159,21 +188,12 @@ const isStringifiableAttr = (name: string, ns: Namespaces) => {
|
|||
)
|
||||
}
|
||||
|
||||
const replaceHoist = (
|
||||
node: StringifiableNode,
|
||||
replacement: JSChildNode | null,
|
||||
context: TransformContext,
|
||||
) => {
|
||||
const hoistToReplace = (node.codegenNode as SimpleExpressionNode).hoisted!
|
||||
context.hoists[context.hoists.indexOf(hoistToReplace)] = replacement
|
||||
}
|
||||
|
||||
const isNonStringifiable = /*#__PURE__*/ makeMap(
|
||||
`caption,thead,tr,th,tbody,td,tfoot,colgroup,col`,
|
||||
)
|
||||
|
||||
/**
|
||||
* for a hoisted node, analyze it and return:
|
||||
* for a cached node, analyze it and return:
|
||||
* - false: bailed (contains non-stringifiable props or runtime constant)
|
||||
* - [nc, ec] where
|
||||
* - nc is the number of nodes inside
|
||||
|
|
@ -381,7 +401,7 @@ function evaluateConstant(exp: ExpressionNode): string {
|
|||
} else if (c.type === NodeTypes.INTERPOLATION) {
|
||||
res += toDisplayString(evaluateConstant(c.content))
|
||||
} else {
|
||||
res += evaluateConstant(c)
|
||||
res += evaluateConstant(c as ExpressionNode)
|
||||
}
|
||||
})
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -851,8 +851,6 @@ return (_ctx, _cache) => {
|
|||
exports[`SFC compile <script setup> > inlineTemplate mode > should work 1`] = `
|
||||
"import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("div", null, "static", -1 /* HOISTED */)
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
export default {
|
||||
|
|
@ -863,7 +861,7 @@ export default {
|
|||
return (_ctx, _cache) => {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_createElementVNode("div", null, _toDisplayString(count.value), 1 /* TEXT */),
|
||||
_hoisted_1
|
||||
_cache[0] || (_cache[0] = _createElementVNode("div", null, "static", -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,13 +38,11 @@ import _imports_0 from '@svg/file.svg'
|
|||
|
||||
|
||||
const _hoisted_1 = _imports_0 + '#fragment'
|
||||
const _hoisted_2 = /*#__PURE__*/_createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)
|
||||
const _hoisted_3 = /*#__PURE__*/_createElementVNode("use", { href: _hoisted_1 }, null, -1 /* HOISTED */)
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_hoisted_2,
|
||||
_hoisted_3
|
||||
_cache[0] || (_cache[0] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("use", { href: _hoisted_1 }, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -82,13 +80,10 @@ import _imports_0 from './bar.png'
|
|||
import _imports_1 from '/bar.png'
|
||||
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
||||
const _hoisted_6 = [
|
||||
_hoisted_1
|
||||
]
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_6))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<img src=\\"" + _imports_0 + "\\"><img src=\\"" + _imports_1 + "\\"><img src=\\"https://foo.bar/baz.png\\"><img src=\\"//foo.bar/baz.png\\"><img src=\\"" + _imports_0 + "\\">", 5)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,13 +7,11 @@ import _imports_0 from '@/logo.png'
|
|||
|
||||
const _hoisted_1 = _imports_0 + ', ' + _imports_0 + ' 2x'
|
||||
const _hoisted_2 = _imports_0 + ' 1x, ' + "/foo/logo.png" + ' 2x'
|
||||
const _hoisted_3 = /*#__PURE__*/_createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* HOISTED */)
|
||||
const _hoisted_4 = /*#__PURE__*/_createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* HOISTED */)
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_hoisted_3,
|
||||
_hoisted_4
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", { srcset: _hoisted_1 }, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", { srcset: _hoisted_2 }, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -31,69 +29,57 @@ const _hoisted_5 = _imports_0 + ' 2x, ' + _imports_0
|
|||
const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
|
||||
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
|
||||
const _hoisted_8 = "/logo.png" + ', ' + _imports_0 + ' 2x'
|
||||
const _hoisted_9 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_13 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_14 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_15 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_16 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_17 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_18 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_19 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_20 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_hoisted_9,
|
||||
_hoisted_10,
|
||||
_hoisted_11,
|
||||
_hoisted_12,
|
||||
_hoisted_13,
|
||||
_hoisted_14,
|
||||
_hoisted_15,
|
||||
_hoisted_16,
|
||||
_hoisted_17,
|
||||
_hoisted_18,
|
||||
_hoisted_19,
|
||||
_hoisted_20
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -101,69 +87,56 @@ export function render(_ctx, _cache) {
|
|||
exports[`compiler sfc: transform srcset > transform srcset w/ base 1`] = `
|
||||
"import { createElementVNode as _createElementVNode, Fragment as _Fragment, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
|
||||
|
||||
const _hoisted_1 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_2 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_3 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_4 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_5 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_6 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_7 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_8 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_9 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_hoisted_1,
|
||||
_hoisted_2,
|
||||
_hoisted_3,
|
||||
_hoisted_4,
|
||||
_hoisted_5,
|
||||
_hoisted_6,
|
||||
_hoisted_7,
|
||||
_hoisted_8,
|
||||
_hoisted_9,
|
||||
_hoisted_10,
|
||||
_hoisted_11,
|
||||
_hoisted_12
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: "/foo/logo.png, /foo/logo.png 2x, /foo/logo.png 3x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: "/logo.png, /foo/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -183,69 +156,57 @@ const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
|
|||
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
|
||||
const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
|
||||
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
||||
const _hoisted_10 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_11 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_12 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_13 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_14 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_15 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_16 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_17 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_18 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_19 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_20 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_9
|
||||
}, null, -1 /* HOISTED */)
|
||||
const _hoisted_21 = /*#__PURE__*/_createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* HOISTED */)
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock(_Fragment, null, [
|
||||
_hoisted_10,
|
||||
_hoisted_11,
|
||||
_hoisted_12,
|
||||
_hoisted_13,
|
||||
_hoisted_14,
|
||||
_hoisted_15,
|
||||
_hoisted_16,
|
||||
_hoisted_17,
|
||||
_hoisted_18,
|
||||
_hoisted_19,
|
||||
_hoisted_20,
|
||||
_hoisted_21
|
||||
_cache[0] || (_cache[0] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: ""
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[1] || (_cache[1] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_1
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[2] || (_cache[2] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_2
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[3] || (_cache[3] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_3
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[4] || (_cache[4] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_4
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[5] || (_cache[5] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_5
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[6] || (_cache[6] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_6
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[7] || (_cache[7] = _createElementVNode("img", {
|
||||
src: "./logo.png",
|
||||
srcset: _hoisted_7
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[8] || (_cache[8] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_8
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[9] || (_cache[9] = _createElementVNode("img", {
|
||||
src: "https://example.com/logo.png",
|
||||
srcset: "https://example.com/logo.png, https://example.com/logo.png 2x"
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[10] || (_cache[10] = _createElementVNode("img", {
|
||||
src: "/logo.png",
|
||||
srcset: _hoisted_9
|
||||
}, null, -1 /* CACHED */)),
|
||||
_cache[11] || (_cache[11] = _createElementVNode("img", {
|
||||
src: "data:image/png;base64,i",
|
||||
srcset: "data:image/png;base64,i 1x, data:image/png;base64,i 2x"
|
||||
}, null, -1 /* CACHED */))
|
||||
], 64 /* STABLE_FRAGMENT */))
|
||||
}"
|
||||
`;
|
||||
|
|
@ -265,12 +226,10 @@ const _hoisted_6 = _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
|
|||
const _hoisted_7 = _imports_0 + ', ' + _imports_0 + ' 2x, ' + _imports_0 + ' 3x'
|
||||
const _hoisted_8 = _imports_1 + ', ' + _imports_1 + ' 2x'
|
||||
const _hoisted_9 = _imports_1 + ', ' + _imports_0 + ' 2x'
|
||||
const _hoisted_10 = /*#__PURE__*/_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
||||
const _hoisted_22 = [
|
||||
_hoisted_10
|
||||
]
|
||||
|
||||
export function render(_ctx, _cache) {
|
||||
return (_openBlock(), _createElementBlock("div", null, _hoisted_22))
|
||||
return (_openBlock(), _createElementBlock("div", null, _cache[0] || (_cache[0] = [
|
||||
_createStaticVNode("<img src=\\"./logo.png\\" srcset=\\"\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_1 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_2 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_3 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_4 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_5 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_6 + "\\"><img src=\\"./logo.png\\" srcset=\\"" + _hoisted_7 + "\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_8 + "\\"><img src=\\"https://example.com/logo.png\\" srcset=\\"https://example.com/logo.png, https://example.com/logo.png 2x\\"><img src=\\"/logo.png\\" srcset=\\"" + _hoisted_9 + "\\"><img src=\\"data:image/png;base64,i\\" srcset=\\"data:image/png;base64,i 1x, data:image/png;base64,i 2x\\">", 12)
|
||||
])))
|
||||
}"
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -9,8 +9,9 @@ import {
|
|||
registerTS,
|
||||
resolveTypeElements,
|
||||
} from '../../src/script/resolveType'
|
||||
|
||||
import { UNKNOWN_TYPE } from '../../src/script/utils'
|
||||
import ts from 'typescript'
|
||||
|
||||
registerTS(() => ts)
|
||||
|
||||
describe('resolveType', () => {
|
||||
|
|
@ -128,7 +129,7 @@ describe('resolveType', () => {
|
|||
defineProps<{ self: any } & Foo & Bar & Baz>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
self: ['Unknown'],
|
||||
self: [UNKNOWN_TYPE],
|
||||
foo: ['Number'],
|
||||
// both Bar & Baz has 'bar', but Baz['bar] is wider so it should be
|
||||
// preferred
|
||||
|
|
@ -136,6 +137,18 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('intersection type with ignore', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type Foo = { foo: number }
|
||||
type Bar = { bar: string }
|
||||
defineProps<Foo & /* @vue-ignore */ Bar>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
foo: ['Number'],
|
||||
})
|
||||
})
|
||||
|
||||
// #7553
|
||||
test('union type', () => {
|
||||
expect(
|
||||
|
|
@ -455,13 +468,13 @@ describe('resolveType', () => {
|
|||
const { props } = resolve(
|
||||
`
|
||||
import { IMP } from './foo'
|
||||
interface Foo { foo: 1, ${1}: 1 }
|
||||
interface Foo { foo: 1, ${1}: 1 }
|
||||
type Bar = { bar: 1 }
|
||||
declare const obj: Bar
|
||||
declare const set: Set<any>
|
||||
declare const arr: Array<any>
|
||||
|
||||
defineProps<{
|
||||
defineProps<{
|
||||
imp: keyof IMP,
|
||||
foo: keyof Foo,
|
||||
bar: keyof Bar,
|
||||
|
|
@ -483,6 +496,106 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('keyof: index signature', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
declare const num: number;
|
||||
interface Foo {
|
||||
[key: symbol]: 1
|
||||
[key: string]: 1
|
||||
[key: typeof num]: 1,
|
||||
}
|
||||
|
||||
type Test<T> = T
|
||||
type Bar = {
|
||||
[key: string]: 1
|
||||
[key: Test<number>]: 1
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
foo: keyof Foo
|
||||
bar: keyof Bar
|
||||
}>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['Symbol', 'String', 'Number'],
|
||||
bar: [UNKNOWN_TYPE],
|
||||
})
|
||||
})
|
||||
|
||||
// #11129
|
||||
test('keyof: intersection type', () => {
|
||||
const { props } = resolve(`
|
||||
type A = { name: string }
|
||||
type B = A & { [key: number]: string }
|
||||
defineProps<{
|
||||
foo: keyof B
|
||||
}>()`)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: union type', () => {
|
||||
const { props } = resolve(`
|
||||
type A = { name: string }
|
||||
type B = A | { [key: number]: string }
|
||||
defineProps<{
|
||||
foo: keyof B
|
||||
}>()`)
|
||||
expect(props).toStrictEqual({
|
||||
foo: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: utility type', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
type Foo = Record<symbol | string, any>
|
||||
type Bar = { [key: string]: any }
|
||||
type AnyRecord = Record<keyof any, any>
|
||||
type Baz = { a: 1, ${1}: 2, b: 3}
|
||||
|
||||
defineProps<{
|
||||
record: keyof Foo,
|
||||
anyRecord: keyof AnyRecord
|
||||
partial: keyof Partial<Bar>,
|
||||
required: keyof Required<Bar>,
|
||||
readonly: keyof Readonly<Bar>,
|
||||
pick: keyof Pick<Baz, 'a' | 1>
|
||||
extract: keyof Extract<keyof Baz, 'a' | 1>
|
||||
}>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
record: ['Symbol', 'String'],
|
||||
anyRecord: ['String', 'Number', 'Symbol'],
|
||||
partial: ['String'],
|
||||
required: ['String'],
|
||||
readonly: ['String'],
|
||||
pick: ['String', 'Number'],
|
||||
extract: ['String', 'Number'],
|
||||
})
|
||||
})
|
||||
|
||||
test('keyof: fallback to Unknown', () => {
|
||||
const { props } = resolve(
|
||||
`
|
||||
interface Barr {}
|
||||
interface Bar extends Barr {}
|
||||
type Foo = keyof Bar
|
||||
defineProps<{ foo: Foo }>()
|
||||
`,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
foo: [UNKNOWN_TYPE],
|
||||
})
|
||||
})
|
||||
|
||||
test('ExtractPropTypes (element-plus)', () => {
|
||||
const { props, raw } = resolve(
|
||||
`
|
||||
|
|
@ -946,6 +1059,53 @@ describe('resolveType', () => {
|
|||
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
|
||||
})
|
||||
|
||||
test('ts module resolve w/ project reference folder', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
references: [
|
||||
{
|
||||
path: './web',
|
||||
},
|
||||
{
|
||||
path: './empty',
|
||||
},
|
||||
{
|
||||
path: './noexists-should-ignore',
|
||||
},
|
||||
],
|
||||
}),
|
||||
'/web/tsconfig.json': JSON.stringify({
|
||||
include: ['../**/*.ts', '../**/*.vue'],
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
paths: {
|
||||
bar: ['../user.ts'],
|
||||
},
|
||||
},
|
||||
}),
|
||||
// tsconfig with no include / paths defined, should match nothing
|
||||
'/empty/tsconfig.json': JSON.stringify({
|
||||
compilerOptions: {
|
||||
composite: true,
|
||||
},
|
||||
}),
|
||||
'/user.ts': 'export type User = { bar: string }',
|
||||
}
|
||||
|
||||
const { props, deps } = resolve(
|
||||
`
|
||||
import { User } from 'bar'
|
||||
defineProps<User>()
|
||||
`,
|
||||
files,
|
||||
)
|
||||
|
||||
expect(props).toStrictEqual({
|
||||
bar: ['String'],
|
||||
})
|
||||
expect(deps && [...deps]).toStrictEqual(['/user.ts'])
|
||||
})
|
||||
|
||||
test('ts module resolve w/ path aliased vue file', () => {
|
||||
const files = {
|
||||
'/tsconfig.json': JSON.stringify({
|
||||
|
|
@ -1122,6 +1282,37 @@ describe('resolveType', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('template literals', () => {
|
||||
test('mapped types with string type', () => {
|
||||
expect(
|
||||
resolve(`
|
||||
type X = 'a' | 'b'
|
||||
defineProps<{[K in X as \`\${K}_foo\`]: string}>()
|
||||
`).props,
|
||||
).toStrictEqual({
|
||||
a_foo: ['String'],
|
||||
b_foo: ['String'],
|
||||
})
|
||||
})
|
||||
|
||||
// #10962
|
||||
test('mapped types with generic parameters', () => {
|
||||
const { props } = resolve(`
|
||||
type Breakpoints = 'sm' | 'md' | 'lg'
|
||||
type BreakpointFactory<T extends string, V> = {
|
||||
[K in Breakpoints as \`\${T}\${Capitalize<K>}\`]: V
|
||||
}
|
||||
type ColsBreakpoints = BreakpointFactory<'cols', number>
|
||||
defineProps<ColsBreakpoints>()
|
||||
`)
|
||||
expect(props).toStrictEqual({
|
||||
colsSm: ['Number'],
|
||||
colsMd: ['Number'],
|
||||
colsLg: ['Number'],
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
function resolve(
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@
|
|||
},
|
||||
"homepage": "https://github.com/vuejs/core-vapor/tree/main/packages/compiler-sfc#readme",
|
||||
"dependencies": {
|
||||
"@babel/parser": "^7.24.6",
|
||||
"@babel/parser": "^7.24.7",
|
||||
"@vue/compiler-core": "workspace:*",
|
||||
"@vue/compiler-dom": "workspace:*",
|
||||
"@vue/compiler-ssr": "workspace:*",
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
"source-map-js": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/types": "^7.24.6",
|
||||
"@babel/types": "^7.24.7",
|
||||
"@vue/consolidate": "^1.0.0",
|
||||
"hash-sum": "^2.0.0",
|
||||
"lru-cache": "10.1.0",
|
||||
|
|
@ -63,6 +63,6 @@
|
|||
"postcss-modules": "^6.0.0",
|
||||
"postcss-selector-parser": "^6.1.0",
|
||||
"pug": "^3.0.3",
|
||||
"sass": "^1.77.2"
|
||||
"sass": "^1.77.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ import {
|
|||
} from './script/defineEmits'
|
||||
import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
|
||||
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
||||
import { processDefineSlots } from './script/defineSlots'
|
||||
import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
|
||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
||||
import { getImportedName, isCallOf, isLiteralNode } from './script/utils'
|
||||
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||
|
|
@ -140,6 +140,16 @@ export interface ImportBinding {
|
|||
isUsedInTemplate: boolean
|
||||
}
|
||||
|
||||
const MACROS = [
|
||||
DEFINE_PROPS,
|
||||
DEFINE_EMITS,
|
||||
DEFINE_EXPOSE,
|
||||
DEFINE_OPTIONS,
|
||||
DEFINE_SLOTS,
|
||||
DEFINE_MODEL,
|
||||
WITH_DEFAULTS,
|
||||
]
|
||||
|
||||
/**
|
||||
* Compile `<script setup>`
|
||||
* It requires the whole SFC descriptor because we need to handle and merge
|
||||
|
|
@ -323,15 +333,18 @@ export function compileScript(
|
|||
const imported = getImportedName(specifier)
|
||||
const source = node.source.value
|
||||
const existing = ctx.userImports[local]
|
||||
if (
|
||||
source === 'vue' &&
|
||||
(imported === DEFINE_PROPS ||
|
||||
imported === DEFINE_EMITS ||
|
||||
imported === DEFINE_EXPOSE)
|
||||
) {
|
||||
warnOnce(
|
||||
`\`${imported}\` is a compiler macro and no longer needs to be imported.`,
|
||||
)
|
||||
if (source === 'vue' && MACROS.includes(imported)) {
|
||||
if (local === imported) {
|
||||
warnOnce(
|
||||
`\`${imported}\` is a compiler macro and no longer needs to be imported.`,
|
||||
)
|
||||
} else {
|
||||
ctx.error(
|
||||
`\`${imported}\` is a compiler macro and cannot be aliased to ` +
|
||||
`a different name.`,
|
||||
specifier,
|
||||
)
|
||||
}
|
||||
removeSpecifier(i)
|
||||
} else if (existing) {
|
||||
if (existing.source === source && existing.imported === imported) {
|
||||
|
|
@ -1072,13 +1085,16 @@ function walkDeclaration(
|
|||
// export const foo = ...
|
||||
for (const { id, init: _init } of node.declarations) {
|
||||
const init = _init && unwrapTSNode(_init)
|
||||
const isDefineCall = !!(
|
||||
const isConstMacroCall =
|
||||
isConst &&
|
||||
isCallOf(
|
||||
init,
|
||||
c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS,
|
||||
c =>
|
||||
c === DEFINE_PROPS ||
|
||||
c === DEFINE_EMITS ||
|
||||
c === WITH_DEFAULTS ||
|
||||
c === DEFINE_SLOTS,
|
||||
)
|
||||
)
|
||||
if (id.type === 'Identifier') {
|
||||
let bindingType
|
||||
const userReactiveBinding = userImportAliases['reactive']
|
||||
|
|
@ -1095,7 +1111,7 @@ function walkDeclaration(
|
|||
} else if (
|
||||
// if a declaration is a const literal, we can mark it so that
|
||||
// the generated render fn code doesn't need to unref() it
|
||||
isDefineCall ||
|
||||
isConstMacroCall ||
|
||||
(isConst && canNeverBeRef(init!, userReactiveBinding))
|
||||
) {
|
||||
bindingType = isCallOf(init, DEFINE_PROPS)
|
||||
|
|
@ -1127,9 +1143,9 @@ function walkDeclaration(
|
|||
continue
|
||||
}
|
||||
if (id.type === 'ObjectPattern') {
|
||||
walkObjectPattern(id, bindings, isConst, isDefineCall)
|
||||
walkObjectPattern(id, bindings, isConst, isConstMacroCall)
|
||||
} else if (id.type === 'ArrayPattern') {
|
||||
walkArrayPattern(id, bindings, isConst, isDefineCall)
|
||||
walkArrayPattern(id, bindings, isConst, isConstMacroCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -198,11 +198,11 @@ function doCompileTemplate({
|
|||
if (ssr && !ssrCssVars) {
|
||||
warnOnce(
|
||||
`compileTemplate is called with \`ssr: true\` but no ` +
|
||||
`corresponding \`cssVars\` option.\`.`,
|
||||
`corresponding \`cssVars\` option.`,
|
||||
)
|
||||
}
|
||||
if (!id) {
|
||||
warnOnce(`compileTemplate now requires the \`id\` option.\`.`)
|
||||
warnOnce(`compileTemplate now requires the \`id\` option.`)
|
||||
id = ''
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -176,14 +176,14 @@ export function resolveParserPlugins(
|
|||
) {
|
||||
plugins.push('importAttributes')
|
||||
}
|
||||
if (lang === 'jsx' || lang === 'tsx') {
|
||||
if (lang === 'jsx' || lang === 'tsx' || lang === 'mtsx') {
|
||||
plugins.push('jsx')
|
||||
} else if (userPlugins) {
|
||||
// If don't match the case of adding jsx
|
||||
// should remove the jsx from user options
|
||||
userPlugins = userPlugins.filter(p => p !== 'jsx')
|
||||
}
|
||||
if (lang === 'ts' || lang === 'tsx') {
|
||||
if (lang === 'ts' || lang === 'mts' || lang === 'tsx' || lang === 'mtsx') {
|
||||
plugins.push(['typescript', { dts }], 'explicitResourceManagement')
|
||||
if (!userPlugins || !userPlugins.includes('decorators')) {
|
||||
plugins.push('decorators-legacy')
|
||||
|
|
|
|||
|
|
@ -165,6 +165,12 @@ function innerResolveTypeElements(
|
|||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>,
|
||||
): ResolvedElements {
|
||||
if (
|
||||
node.leadingComments &&
|
||||
node.leadingComments.some(c => c.value.includes('@vue-ignore'))
|
||||
) {
|
||||
return { props: {} }
|
||||
}
|
||||
switch (node.type) {
|
||||
case 'TSTypeLiteral':
|
||||
return typeElementsToMap(ctx, node.members, scope, typeParameters)
|
||||
|
|
@ -188,7 +194,7 @@ function innerResolveTypeElements(
|
|||
node.type,
|
||||
)
|
||||
case 'TSMappedType':
|
||||
return resolveMappedType(ctx, node, scope)
|
||||
return resolveMappedType(ctx, node, scope, typeParameters)
|
||||
case 'TSIndexedAccessType': {
|
||||
const types = resolveIndexType(ctx, node, scope)
|
||||
return mergeElements(
|
||||
|
|
@ -414,12 +420,6 @@ function resolveInterfaceMembers(
|
|||
)
|
||||
if (node.extends) {
|
||||
for (const ext of node.extends) {
|
||||
if (
|
||||
ext.leadingComments &&
|
||||
ext.leadingComments.some(c => c.value.includes('@vue-ignore'))
|
||||
) {
|
||||
continue
|
||||
}
|
||||
try {
|
||||
const { props, calls } = resolveTypeElements(ctx, ext, scope)
|
||||
for (const key in props) {
|
||||
|
|
@ -439,6 +439,7 @@ function resolveInterfaceMembers(
|
|||
`Note: both in 3.2 or with the ignore, the properties in the base ` +
|
||||
`type are treated as fallthrough attrs at runtime.`,
|
||||
ext,
|
||||
scope,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -450,9 +451,18 @@ function resolveMappedType(
|
|||
ctx: TypeResolveContext,
|
||||
node: TSMappedType,
|
||||
scope: TypeScope,
|
||||
typeParameters?: Record<string, Node>,
|
||||
): ResolvedElements {
|
||||
const res: ResolvedElements = { props: {} }
|
||||
const keys = resolveStringType(ctx, node.typeParameter.constraint!, scope)
|
||||
let keys: string[]
|
||||
if (node.nameType) {
|
||||
const { name, constraint } = node.typeParameter
|
||||
scope = createChildScope(scope)
|
||||
Object.assign(scope.types, { ...typeParameters, [name]: constraint })
|
||||
keys = resolveStringType(ctx, node.nameType, scope)
|
||||
} else {
|
||||
keys = resolveStringType(ctx, node.typeParameter.constraint!, scope)
|
||||
}
|
||||
for (const key of keys) {
|
||||
res.props[key] = createProperty(
|
||||
{
|
||||
|
|
@ -903,7 +913,7 @@ function importSourceToScope(
|
|||
|
||||
const filename = osSpecificJoinFn(dirname(scope.filename), source)
|
||||
resolved = resolveExt(filename, fs)
|
||||
} else if (source.startsWith('.')) {
|
||||
} else if (source[0] === '.') {
|
||||
// relative import - fast path
|
||||
const filename = joinPaths(dirname(scope.filename), source)
|
||||
resolved = resolveExt(filename, fs)
|
||||
|
|
@ -1005,11 +1015,11 @@ function resolveWithTS(
|
|||
(c.config.options.pathsBasePath as string) ||
|
||||
dirname(c.config.options.configFilePath as string),
|
||||
)
|
||||
const included: string[] = c.config.raw?.include
|
||||
const excluded: string[] = c.config.raw?.exclude
|
||||
const included: string[] | undefined = c.config.raw?.include
|
||||
const excluded: string[] | undefined = c.config.raw?.exclude
|
||||
if (
|
||||
(!included && (!base || containingFile.startsWith(base))) ||
|
||||
included.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
included?.some(p => isMatch(containingFile, joinPaths(base, p)))
|
||||
) {
|
||||
if (
|
||||
excluded &&
|
||||
|
|
@ -1080,8 +1090,12 @@ function loadTSConfig(
|
|||
const res = [config]
|
||||
if (config.projectReferences) {
|
||||
for (const ref of config.projectReferences) {
|
||||
tsConfigRefMap.set(ref.path, configPath)
|
||||
res.unshift(...loadTSConfig(ref.path, ts, fs))
|
||||
const refPath = ts.resolveProjectReferencePath(ref)
|
||||
if (!fs.fileExists(refPath)) {
|
||||
continue
|
||||
}
|
||||
tsConfigRefMap.set(refPath, configPath)
|
||||
res.unshift(...loadTSConfig(refPath, ts, fs))
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
|
@ -1125,12 +1139,12 @@ function parseFile(
|
|||
parserPlugins?: SFCScriptCompileOptions['babelParserPlugins'],
|
||||
): Statement[] {
|
||||
const ext = extname(filename)
|
||||
if (ext === '.ts' || ext === '.tsx') {
|
||||
if (ext === '.ts' || ext === '.mts' || ext === '.tsx' || ext === '.mtsx') {
|
||||
return babelParse(content, {
|
||||
plugins: resolveParserPlugins(
|
||||
ext.slice(1),
|
||||
parserPlugins,
|
||||
filename.endsWith('.d.ts'),
|
||||
/\.d\.m?ts$/.test(filename),
|
||||
),
|
||||
sourceType: 'module',
|
||||
}).program.body
|
||||
|
|
@ -1476,6 +1490,17 @@ export function inferRuntimeType(
|
|||
m.key.type === 'NumericLiteral'
|
||||
) {
|
||||
types.add('Number')
|
||||
} else if (m.type === 'TSIndexSignature') {
|
||||
const annotation = m.parameters[0].typeAnnotation
|
||||
if (annotation && annotation.type !== 'Noop') {
|
||||
const type = inferRuntimeType(
|
||||
ctx,
|
||||
annotation.typeAnnotation,
|
||||
scope,
|
||||
)[0]
|
||||
if (type === UNKNOWN_TYPE) return [UNKNOWN_TYPE]
|
||||
types.add(type)
|
||||
}
|
||||
} else {
|
||||
types.add('String')
|
||||
}
|
||||
|
|
@ -1489,7 +1514,9 @@ export function inferRuntimeType(
|
|||
}
|
||||
}
|
||||
|
||||
return types.size ? Array.from(types) : ['Object']
|
||||
return types.size
|
||||
? Array.from(types)
|
||||
: [isKeyOf ? UNKNOWN_TYPE : 'Object']
|
||||
}
|
||||
case 'TSPropertySignature':
|
||||
if (node.typeAnnotation) {
|
||||
|
|
@ -1533,81 +1560,123 @@ export function inferRuntimeType(
|
|||
case 'String':
|
||||
case 'Array':
|
||||
case 'ArrayLike':
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['String', 'Number']
|
||||
default:
|
||||
|
||||
// TS built-in utility types
|
||||
case 'Record':
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
true,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Pick':
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
case 'ReadonlyMap':
|
||||
case 'ReadonlySet':
|
||||
return ['String']
|
||||
}
|
||||
}
|
||||
} else {
|
||||
switch (node.typeName.name) {
|
||||
case 'Array':
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
|
||||
switch (node.typeName.name) {
|
||||
case 'Array':
|
||||
case 'Function':
|
||||
case 'Object':
|
||||
case 'Set':
|
||||
case 'Map':
|
||||
case 'WeakSet':
|
||||
case 'WeakMap':
|
||||
case 'Date':
|
||||
case 'Promise':
|
||||
case 'Error':
|
||||
return [node.typeName.name]
|
||||
// TS built-in utility types
|
||||
// https://www.typescriptlang.org/docs/handbook/utility-types.html
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
case 'Record':
|
||||
case 'Pick':
|
||||
case 'Omit':
|
||||
case 'InstanceType':
|
||||
return ['Object']
|
||||
|
||||
// TS built-in utility types
|
||||
// https://www.typescriptlang.org/docs/handbook/utility-types.html
|
||||
case 'Partial':
|
||||
case 'Required':
|
||||
case 'Readonly':
|
||||
case 'Record':
|
||||
case 'Pick':
|
||||
case 'Omit':
|
||||
case 'InstanceType':
|
||||
return ['Object']
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
return ['String']
|
||||
|
||||
case 'Uppercase':
|
||||
case 'Lowercase':
|
||||
case 'Capitalize':
|
||||
case 'Uncapitalize':
|
||||
return ['String']
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['Array']
|
||||
|
||||
case 'Parameters':
|
||||
case 'ConstructorParameters':
|
||||
case 'ReadonlyArray':
|
||||
return ['Array']
|
||||
case 'ReadonlyMap':
|
||||
return ['Map']
|
||||
case 'ReadonlySet':
|
||||
return ['Set']
|
||||
|
||||
case 'ReadonlyMap':
|
||||
return ['Map']
|
||||
case 'ReadonlySet':
|
||||
return ['Set']
|
||||
|
||||
case 'NonNullable':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
).filter(t => t !== 'null')
|
||||
}
|
||||
break
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Exclude':
|
||||
case 'OmitThisParameter':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'NonNullable':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
).filter(t => t !== 'null')
|
||||
}
|
||||
break
|
||||
case 'Extract':
|
||||
if (node.typeParameters && node.typeParameters.params[1]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[1],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'Exclude':
|
||||
case 'OmitThisParameter':
|
||||
if (node.typeParameters && node.typeParameters.params[0]) {
|
||||
return inferRuntimeType(
|
||||
ctx,
|
||||
node.typeParameters.params[0],
|
||||
scope,
|
||||
)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
// cannot infer, fallback to UNKNOWN: ThisParameterType
|
||||
|
|
@ -1618,9 +1687,9 @@ export function inferRuntimeType(
|
|||
return inferRuntimeType(ctx, node.typeAnnotation, scope)
|
||||
|
||||
case 'TSUnionType':
|
||||
return flattenTypes(ctx, node.types, scope)
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf)
|
||||
case 'TSIntersectionType': {
|
||||
return flattenTypes(ctx, node.types, scope).filter(
|
||||
return flattenTypes(ctx, node.types, scope, isKeyOf).filter(
|
||||
t => t !== UNKNOWN_TYPE,
|
||||
)
|
||||
}
|
||||
|
|
@ -1674,6 +1743,13 @@ export function inferRuntimeType(
|
|||
node.operator === 'keyof',
|
||||
)
|
||||
}
|
||||
|
||||
case 'TSAnyKeyword': {
|
||||
if (isKeyOf) {
|
||||
return ['String', 'Number', 'Symbol']
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// always soft fail on failed runtime type inference
|
||||
|
|
@ -1685,14 +1761,15 @@ function flattenTypes(
|
|||
ctx: TypeResolveContext,
|
||||
types: TSType[],
|
||||
scope: TypeScope,
|
||||
isKeyOf: boolean = false,
|
||||
): string[] {
|
||||
if (types.length === 1) {
|
||||
return inferRuntimeType(ctx, types[0], scope)
|
||||
return inferRuntimeType(ctx, types[0], scope, isKeyOf)
|
||||
}
|
||||
return [
|
||||
...new Set(
|
||||
([] as string[]).concat(
|
||||
...types.map(t => inferRuntimeType(ctx, t, scope)),
|
||||
...types.map(t => inferRuntimeType(ctx, t, scope, isKeyOf)),
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1972,3 +1972,24 @@ createApp({}).component(
|
|||
},
|
||||
}),
|
||||
)
|
||||
|
||||
const Comp = defineComponent({
|
||||
props: {
|
||||
actionText: {
|
||||
type: {} as PropType<string>,
|
||||
default: 'Become a sponsor',
|
||||
},
|
||||
},
|
||||
__typeProps: {} as {
|
||||
actionText?: string
|
||||
},
|
||||
})
|
||||
|
||||
const instance = new Comp()
|
||||
function expectString(s: string) {}
|
||||
// instance prop with default should be non-null
|
||||
expectString(instance.actionText)
|
||||
|
||||
// public prop on $props should be optional
|
||||
// @ts-expect-error
|
||||
expectString(instance.$props.actionText)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,11 @@
|
|||
import { type InjectionKey, type Ref, inject, provide, ref } from 'vue'
|
||||
import {
|
||||
type InjectionKey,
|
||||
type Ref,
|
||||
createApp,
|
||||
inject,
|
||||
provide,
|
||||
ref,
|
||||
} from 'vue'
|
||||
import { expectType } from './utils'
|
||||
|
||||
// non-symbol keys
|
||||
|
|
@ -40,3 +47,8 @@ provide<Cube>(injectionKeyRef, { size: 123 })
|
|||
provide<Cube>('cube', { size: 'foo' })
|
||||
// @ts-expect-error
|
||||
provide<Cube>(123, { size: 'foo' })
|
||||
|
||||
// #10602
|
||||
const app = createApp({})
|
||||
// @ts-expect-error
|
||||
app.provide(injectionKeyRef, ref({}))
|
||||
|
|
|
|||
|
|
@ -120,3 +120,13 @@ describe('should unwrap extended Set correctly', () => {
|
|||
expectType<string>(eset1.foo)
|
||||
expectType<number>(eset1.bar)
|
||||
})
|
||||
|
||||
describe('should not error when assignment', () => {
|
||||
const arr = reactive([''])
|
||||
let record: Record<number, string>
|
||||
record = arr
|
||||
expectType<string>(record[0])
|
||||
let record2: { [key: number]: string }
|
||||
record2 = arr
|
||||
expectType<string>(record2[0])
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import {
|
||||
type ComputedRef,
|
||||
type Ref,
|
||||
computed,
|
||||
defineComponent,
|
||||
defineModel,
|
||||
reactive,
|
||||
ref,
|
||||
shallowRef,
|
||||
watch,
|
||||
|
|
@ -12,8 +15,12 @@ const source = ref('foo')
|
|||
const source2 = computed(() => source.value)
|
||||
const source3 = () => 1
|
||||
|
||||
type Bar = Ref<string> | ComputedRef<string> | (() => number)
|
||||
type Foo = readonly [Ref<string>, ComputedRef<string>, () => number]
|
||||
type OnCleanup = (fn: () => void) => void
|
||||
|
||||
const readonlyArr: Foo = [source, source2, source3]
|
||||
|
||||
// lazy watcher will have consistent types for oldValue.
|
||||
watch(source, (value, oldValue, onCleanup) => {
|
||||
expectType<string>(value)
|
||||
|
|
@ -32,6 +39,29 @@ watch([source, source2, source3] as const, (values, oldValues) => {
|
|||
expectType<Readonly<[string, string, number]>>(oldValues)
|
||||
})
|
||||
|
||||
// reactive array
|
||||
watch(reactive([source, source2, source3]), (value, oldValues) => {
|
||||
expectType<Bar[]>(value)
|
||||
expectType<Bar[]>(oldValues)
|
||||
})
|
||||
|
||||
// reactive w/ readonly tuple
|
||||
watch(reactive([source, source2, source3] as const), (value, oldValues) => {
|
||||
expectType<Foo>(value)
|
||||
expectType<Foo>(oldValues)
|
||||
})
|
||||
|
||||
// readonly array
|
||||
watch(readonlyArr, (values, oldValues) => {
|
||||
expectType<Readonly<[string, string, number]>>(values)
|
||||
expectType<Readonly<[string, string, number]>>(oldValues)
|
||||
})
|
||||
|
||||
// no type error, case from vueuse
|
||||
declare const aAny: any
|
||||
watch(aAny, (v, ov) => {})
|
||||
watch(aAny, (v, ov) => {}, { immediate: true })
|
||||
|
||||
// immediate watcher's oldValue will be undefined on first run.
|
||||
watch(
|
||||
source,
|
||||
|
|
@ -65,6 +95,34 @@ watch(
|
|||
{ immediate: true },
|
||||
)
|
||||
|
||||
// reactive array
|
||||
watch(
|
||||
reactive([source, source2, source3]),
|
||||
(value, oldVals) => {
|
||||
expectType<Bar[]>(value)
|
||||
expectType<Bar[] | undefined>(oldVals)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// reactive w/ readonly tuple
|
||||
watch(reactive([source, source2, source3] as const), (value, oldVals) => {
|
||||
expectType<Foo>(value)
|
||||
expectType<Foo | undefined>(oldVals)
|
||||
})
|
||||
|
||||
// readonly array
|
||||
watch(
|
||||
readonlyArr,
|
||||
(values, oldValues) => {
|
||||
expectType<Readonly<[string, string, number]>>(values)
|
||||
expectType<
|
||||
Readonly<[string | undefined, string | undefined, number | undefined]>
|
||||
>(oldValues)
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
// should provide correct ref.value inner type to callbacks
|
||||
const nestedRefSource = ref({
|
||||
foo: ref(1),
|
||||
|
|
|
|||
|
|
@ -245,7 +245,7 @@ describe('reactivity/computed', () => {
|
|||
])
|
||||
})
|
||||
|
||||
it('debug: onTrigger', () => {
|
||||
it('debug: onTrigger (reactive)', () => {
|
||||
let events: DebuggerEvent[] = []
|
||||
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||
events.push(e)
|
||||
|
|
@ -852,4 +852,29 @@ describe('reactivity/computed', () => {
|
|||
await nextTick()
|
||||
expect(calls).toMatchObject(['b eval', 'mounted', 'b eval'])
|
||||
})
|
||||
|
||||
it('debug: onTrigger (ref)', () => {
|
||||
let events: DebuggerEvent[] = []
|
||||
const onTrigger = vi.fn((e: DebuggerEvent) => {
|
||||
events.push(e)
|
||||
})
|
||||
const obj = ref(1)
|
||||
const c = computed(() => obj.value, { onTrigger })
|
||||
|
||||
// computed won't track until it has a subscriber
|
||||
effect(() => c.value)
|
||||
|
||||
obj.value++
|
||||
|
||||
expect(c.value).toBe(2)
|
||||
expect(onTrigger).toHaveBeenCalledTimes(1)
|
||||
expect(events[0]).toEqual({
|
||||
effect: c,
|
||||
target: toRaw(obj),
|
||||
type: TriggerOpTypes.SET,
|
||||
key: 'value',
|
||||
oldValue: 1,
|
||||
newValue: 2,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -258,6 +258,22 @@ describe('reactivity/effect', () => {
|
|||
expect(dummy).toBe(undefined)
|
||||
})
|
||||
|
||||
it('should not observe well-known symbol keyed properties in has operation', () => {
|
||||
const key = Symbol.isConcatSpreadable
|
||||
const obj = reactive({
|
||||
[key]: true,
|
||||
}) as any
|
||||
|
||||
const spy = vi.fn(() => {
|
||||
key in obj
|
||||
})
|
||||
effect(spy)
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
|
||||
obj[key] = false
|
||||
expect(spy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it('should support manipulating an array while observing symbol keyed properties', () => {
|
||||
const key = Symbol()
|
||||
let dummy
|
||||
|
|
@ -895,6 +911,31 @@ describe('reactivity/effect', () => {
|
|||
expect(dummy).toBe(3)
|
||||
})
|
||||
|
||||
it('stop with multiple dependencies', () => {
|
||||
let dummy1, dummy2
|
||||
const obj1 = reactive({ prop: 1 })
|
||||
const obj2 = reactive({ prop: 1 })
|
||||
const runner = effect(() => {
|
||||
dummy1 = obj1.prop
|
||||
dummy2 = obj2.prop
|
||||
})
|
||||
|
||||
obj1.prop = 2
|
||||
expect(dummy1).toBe(2)
|
||||
|
||||
obj2.prop = 3
|
||||
expect(dummy2).toBe(3)
|
||||
|
||||
stop(runner)
|
||||
|
||||
obj1.prop = 4
|
||||
obj2.prop = 5
|
||||
|
||||
// Check that both dependencies have been cleared
|
||||
expect(dummy1).toBe(2)
|
||||
expect(dummy2).toBe(3)
|
||||
})
|
||||
|
||||
it('events: onStop', () => {
|
||||
const onStop = vi.fn()
|
||||
const runner = effect(() => {}, {
|
||||
|
|
|
|||
|
|
@ -450,7 +450,7 @@ describe('reactivity/readonly', () => {
|
|||
bar: markRaw({ b: 2 }),
|
||||
})
|
||||
expect(isReadonly(obj.foo)).toBe(true)
|
||||
expect(isReactive(obj.bar)).toBe(false)
|
||||
expect(isReadonly(obj.bar)).toBe(false)
|
||||
})
|
||||
|
||||
test('should make ref readonly', () => {
|
||||
|
|
|
|||
|
|
@ -47,11 +47,11 @@ export class ComputedRefImpl<T = any> implements Subscriber {
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly dep = new Dep(this);
|
||||
readonly dep = new Dep(this)
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly [ReactiveFlags.IS_REF] = true;
|
||||
readonly [ReactiveFlags.IS_REF] = true
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -22,11 +22,3 @@ export enum ReactiveFlags {
|
|||
RAW = '__v_raw',
|
||||
IS_REF = '__v_isRef',
|
||||
}
|
||||
|
||||
export enum DirtyLevels {
|
||||
NotDirty = 0,
|
||||
QueryingDirty = 1,
|
||||
MaybeDirty_ComputedSideEffect = 2,
|
||||
MaybeDirty = 3,
|
||||
Dirty = 4,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,6 +37,8 @@ export {
|
|||
type DeepReadonly,
|
||||
type ShallowReactive,
|
||||
type UnwrapNestedRefs,
|
||||
type Reactive,
|
||||
type ReactiveMarker,
|
||||
} from './reactive'
|
||||
export {
|
||||
computed,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,15 @@ function getTargetType(value: Target) {
|
|||
// only unwrap nested ref
|
||||
export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
|
||||
|
||||
declare const ReactiveMarkerSymbol: unique symbol
|
||||
|
||||
export declare class ReactiveMarker {
|
||||
private [ReactiveMarkerSymbol]?: void
|
||||
}
|
||||
|
||||
export type Reactive<T> = UnwrapNestedRefs<T> &
|
||||
(T extends readonly any[] ? ReactiveMarker : {})
|
||||
|
||||
/**
|
||||
* Returns a reactive proxy of the object.
|
||||
*
|
||||
|
|
@ -73,7 +82,7 @@ export type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRefSimple<T>
|
|||
* @param target - The source object.
|
||||
* @see {@link https://vuejs.org/api/reactivity-core.html#reactive}
|
||||
*/
|
||||
export function reactive<T extends object>(target: T): UnwrapNestedRefs<T>
|
||||
export function reactive<T extends object>(target: T): Reactive<T>
|
||||
export function reactive(target: object) {
|
||||
// if trying to observe a readonly proxy, return the readonly version.
|
||||
if (isReadonly(target)) {
|
||||
|
|
@ -135,7 +144,7 @@ export function shallowReactive<T extends object>(
|
|||
}
|
||||
|
||||
type Primitive = string | number | boolean | bigint | symbol | undefined | null
|
||||
type Builtin = Primitive | Function | Date | Error | RegExp
|
||||
export type Builtin = Primitive | Function | Date | Error | RegExp
|
||||
export type DeepReadonly<T> = T extends Builtin
|
||||
? T
|
||||
: T extends Map<infer K, infer V>
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
} from '@vue/shared'
|
||||
import { Dep, getDepFromReactive } from './dep'
|
||||
import {
|
||||
type Builtin,
|
||||
type ShallowReactiveMarker,
|
||||
isProxy,
|
||||
isReactive,
|
||||
|
|
@ -450,11 +451,6 @@ function propertyToRef(
|
|||
: (new ObjectRefImpl(source, key, defaultValue) as any)
|
||||
}
|
||||
|
||||
// corner case when use narrows type
|
||||
// Ex. type RelativePath = string & { __brand: unknown }
|
||||
// RelativePath extends object -> true
|
||||
type BaseTypes = string | number | boolean
|
||||
|
||||
/**
|
||||
* This is a special exported interface for other packages to declare
|
||||
* additional types that should bail out for ref unwrapping. For example
|
||||
|
|
@ -471,10 +467,10 @@ type BaseTypes = string | number | boolean
|
|||
export interface RefUnwrapBailTypes {}
|
||||
|
||||
export type ShallowUnwrapRef<T> = {
|
||||
[K in keyof T]: DistrubuteRef<T[K]>
|
||||
[K in keyof T]: DistributeRef<T[K]>
|
||||
}
|
||||
|
||||
type DistrubuteRef<T> = T extends Ref<infer V> ? V : T
|
||||
type DistributeRef<T> = T extends Ref<infer V> ? V : T
|
||||
|
||||
export type UnwrapRef<T> =
|
||||
T extends ShallowRef<infer V>
|
||||
|
|
@ -484,8 +480,7 @@ export type UnwrapRef<T> =
|
|||
: UnwrapRefSimple<T>
|
||||
|
||||
export type UnwrapRefSimple<T> = T extends
|
||||
| Function
|
||||
| BaseTypes
|
||||
| Builtin
|
||||
| Ref
|
||||
| RefUnwrapBailTypes[keyof RefUnwrapBailTypes]
|
||||
| { [RawSymbol]?: true }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import {
|
||||
KeepAlive,
|
||||
TrackOpTypes,
|
||||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
onActivated,
|
||||
onBeforeMount,
|
||||
onBeforeUnmount,
|
||||
onBeforeUpdate,
|
||||
|
|
@ -407,4 +409,60 @@ describe('api: lifecycle hooks', () => {
|
|||
await nextTick()
|
||||
expect(fn).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
it('immediately trigger unmount during rendering', async () => {
|
||||
const fn = vi.fn()
|
||||
const toggle = ref(false)
|
||||
|
||||
const Child = {
|
||||
setup() {
|
||||
onMounted(fn)
|
||||
// trigger unmount immediately
|
||||
toggle.value = false
|
||||
return () => h('div')
|
||||
},
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () => (toggle.value ? [h(Child)] : null)
|
||||
},
|
||||
}
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
toggle.value = true
|
||||
await nextTick()
|
||||
expect(fn).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
|
||||
it('immediately trigger unmount during rendering(with KeepAlive)', async () => {
|
||||
const mountedSpy = vi.fn()
|
||||
const activeSpy = vi.fn()
|
||||
const toggle = ref(false)
|
||||
|
||||
const Child = {
|
||||
setup() {
|
||||
onMounted(mountedSpy)
|
||||
onActivated(activeSpy)
|
||||
|
||||
// trigger unmount immediately
|
||||
toggle.value = false
|
||||
return () => h('div')
|
||||
},
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () => h(KeepAlive, [toggle.value ? h(Child) : null])
|
||||
},
|
||||
}
|
||||
|
||||
render(h(Comp), nodeOps.createElement('div'))
|
||||
|
||||
toggle.value = true
|
||||
await nextTick()
|
||||
expect(mountedSpy).toHaveBeenCalledTimes(0)
|
||||
expect(activeSpy).toHaveBeenCalledTimes(0)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -749,4 +749,39 @@ describe('component props', () => {
|
|||
expect(`Invalid prop name: "ref"`).toHaveBeenWarned()
|
||||
expect(`Invalid prop name: "$foo"`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
// #5517
|
||||
test('events should not be props when component updating', async () => {
|
||||
let props: any
|
||||
function eventHandler() {}
|
||||
const foo = ref(1)
|
||||
|
||||
const Child = defineComponent({
|
||||
setup(_props) {
|
||||
props = _props
|
||||
},
|
||||
emits: ['event'],
|
||||
props: ['foo'],
|
||||
template: `<div>{{ foo }}</div>`,
|
||||
})
|
||||
|
||||
const Comp = defineComponent({
|
||||
setup() {
|
||||
return {
|
||||
foo,
|
||||
eventHandler,
|
||||
}
|
||||
},
|
||||
components: { Child },
|
||||
template: `<Child @event="eventHandler" :foo="foo" />`,
|
||||
})
|
||||
|
||||
const root = document.createElement('div')
|
||||
domRender(h(Comp), root)
|
||||
expect(props).not.toHaveProperty('onEvent')
|
||||
|
||||
foo.value++
|
||||
await nextTick()
|
||||
expect(props).not.toHaveProperty('onEvent')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -148,6 +148,17 @@ describe('v-memo', () => {
|
|||
// should update
|
||||
await nextTick()
|
||||
expect(el.innerHTML).toBe(`<div>3 3</div>`)
|
||||
|
||||
vm.ok = true
|
||||
await nextTick()
|
||||
vm.ok = false
|
||||
await nextTick()
|
||||
expect(el.innerHTML).toBe(`<div>3 3</div>`)
|
||||
|
||||
vm.y++
|
||||
// should update
|
||||
await nextTick()
|
||||
expect(el.innerHTML).toBe(`<div>4 3</div>`)
|
||||
})
|
||||
|
||||
test('on v-for', async () => {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import {
|
|||
h,
|
||||
nextTick,
|
||||
nodeOps,
|
||||
ref,
|
||||
render,
|
||||
serializeInner,
|
||||
triggerEvent,
|
||||
|
|
@ -415,6 +416,53 @@ describe('hot module replacement', () => {
|
|||
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
// #6930
|
||||
test('reload: avoid infinite recursion', async () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
const childId = 'test-child-6930'
|
||||
const unmountSpy = vi.fn()
|
||||
const mountSpy = vi.fn()
|
||||
|
||||
const Child: ComponentOptions = {
|
||||
__hmrId: childId,
|
||||
data() {
|
||||
return { count: 0 }
|
||||
},
|
||||
expose: ['count'],
|
||||
unmounted: unmountSpy,
|
||||
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
|
||||
}
|
||||
createRecord(childId, Child)
|
||||
|
||||
const Parent: ComponentOptions = {
|
||||
setup() {
|
||||
const com = ref()
|
||||
const changeRef = (value: any) => {
|
||||
com.value = value
|
||||
}
|
||||
|
||||
return () => [h(Child, { ref: changeRef }), com.value?.count]
|
||||
},
|
||||
}
|
||||
|
||||
render(h(Parent), root)
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>0</div>0`)
|
||||
|
||||
reload(childId, {
|
||||
__hmrId: childId,
|
||||
data() {
|
||||
return { count: 1 }
|
||||
},
|
||||
mounted: mountSpy,
|
||||
render: compileToFunction(`<div @click="count++">{{ count }}</div>`),
|
||||
})
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>1</div>1`)
|
||||
expect(unmountSpy).toHaveBeenCalledTimes(1)
|
||||
expect(mountSpy).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
// #1156 - static nodes should retain DOM element reference across updates
|
||||
// when HMR is active
|
||||
test('static el reference', async () => {
|
||||
|
|
|
|||
|
|
@ -1122,7 +1122,7 @@ describe('SSR hydration', () => {
|
|||
'input',
|
||||
{ type: 'checkbox', indeterminate: '' },
|
||||
null,
|
||||
PatchFlags.HOISTED,
|
||||
PatchFlags.CACHED,
|
||||
),
|
||||
)
|
||||
expect((container.firstChild as any).indeterminate).toBe(true)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
NodeOpTypes,
|
||||
type TestElement,
|
||||
TestNodeTypes,
|
||||
type VNode,
|
||||
createBlock,
|
||||
createCommentVNode,
|
||||
createTextVNode,
|
||||
|
|
@ -316,6 +317,64 @@ describe('renderer: fragment', () => {
|
|||
)
|
||||
})
|
||||
|
||||
// #10547
|
||||
test('`template` fragment w/ render function', () => {
|
||||
const renderFn = (vnode: VNode) => {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(
|
||||
Fragment,
|
||||
null,
|
||||
[createTextVNode('text'), (openBlock(), createBlock(vnode))],
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
const foo = h('div', ['foo'])
|
||||
const bar = h('div', [h('div', 'bar')])
|
||||
|
||||
render(renderFn(foo), root)
|
||||
expect(serializeInner(root)).toBe(`text<div>foo</div>`)
|
||||
|
||||
render(renderFn(bar), root)
|
||||
expect(serializeInner(root)).toBe(`text<div><div>bar</div></div>`)
|
||||
|
||||
render(renderFn(foo), root)
|
||||
expect(serializeInner(root)).toBe(`text<div>foo</div>`)
|
||||
})
|
||||
|
||||
// #10547
|
||||
test('`template` fragment w/ render function + keyed vnode', () => {
|
||||
const renderFn = (vnode: VNode) => {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(
|
||||
Fragment,
|
||||
null,
|
||||
[createTextVNode('text'), (openBlock(), createBlock(vnode))],
|
||||
PatchFlags.STABLE_FRAGMENT,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
const foo = h('div', { key: 1 }, [h('div', 'foo')])
|
||||
const bar = h('div', { key: 2 }, [h('div', 'bar'), h('div', 'bar')])
|
||||
|
||||
render(renderFn(foo), root)
|
||||
expect(serializeInner(root)).toBe(`text<div><div>foo</div></div>`)
|
||||
|
||||
render(renderFn(bar), root)
|
||||
expect(serializeInner(root)).toBe(
|
||||
`text<div><div>bar</div><div>bar</div></div>`,
|
||||
)
|
||||
|
||||
render(renderFn(foo), root)
|
||||
expect(serializeInner(root)).toBe(`text<div><div>foo</div></div>`)
|
||||
})
|
||||
|
||||
// #6852
|
||||
test('`template` keyed fragment w/ text', () => {
|
||||
const root = nodeOps.createElement('div')
|
||||
|
|
|
|||
|
|
@ -63,6 +63,17 @@ describe('vnode', () => {
|
|||
})
|
||||
})
|
||||
|
||||
test('create from an existing text vnode', () => {
|
||||
const vnode1 = createVNode('div', null, 'text', PatchFlags.TEXT)
|
||||
const vnode2 = createVNode(vnode1)
|
||||
expect(vnode2).toMatchObject({
|
||||
type: 'div',
|
||||
patchFlag: PatchFlags.BAIL,
|
||||
children: 'text',
|
||||
shapeFlag: ShapeFlags.ELEMENT | ShapeFlags.TEXT_CHILDREN,
|
||||
})
|
||||
})
|
||||
|
||||
test('vnode keys', () => {
|
||||
for (const key of ['', 'a', 0, 1, NaN]) {
|
||||
expect(createVNode('div', { key }).key).toBe(key)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
type Component,
|
||||
type ComponentInternalInstance,
|
||||
type ConcreteComponent,
|
||||
getExposeProxy,
|
||||
getComponentPublicInstance,
|
||||
validateComponentName,
|
||||
} from './component'
|
||||
import type {
|
||||
|
|
@ -55,7 +55,10 @@ export interface App<HostElement = any> {
|
|||
): ComponentPublicInstance
|
||||
unmount(): void
|
||||
onUnmount(cb: () => void): void
|
||||
provide<T>(key: InjectionKey<T> | string, value: T): this
|
||||
provide<T, K = InjectionKey<T> | string | number>(
|
||||
key: K,
|
||||
value: K extends InjectionKey<infer V> ? V : T,
|
||||
): this
|
||||
|
||||
/**
|
||||
* Runs a function with the app as active instance. This allows using of `inject()` within the function to get access
|
||||
|
|
@ -361,7 +364,7 @@ export function createAppAPI<HostElement>(
|
|||
devtoolsInitApp(app, version)
|
||||
}
|
||||
|
||||
return getExposeProxy(vnode.component!) || vnode.component!.proxy
|
||||
return getComponentPublicInstance(vnode.component!)
|
||||
} else if (__DEV__) {
|
||||
warn(
|
||||
`App has already been mounted.\n` +
|
||||
|
|
|
|||
|
|
@ -31,9 +31,6 @@ export function injectHook(
|
|||
const wrappedHook =
|
||||
hook.__weh ||
|
||||
(hook.__weh = (...args: unknown[]) => {
|
||||
if (target.isUnmounted) {
|
||||
return
|
||||
}
|
||||
// disable tracking inside all lifecycle hooks
|
||||
// since they can potentially be called inside effects.
|
||||
pauseTracking()
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ type BooleanKey<T, K extends keyof T = keyof T> = K extends any
|
|||
* const emit = defineEmits<{
|
||||
* // <eventName>: <expected arguments>
|
||||
* change: []
|
||||
* update: [value: string] // named tuple syntax
|
||||
* update: [value: number] // named tuple syntax
|
||||
* }>()
|
||||
*
|
||||
* emit('change')
|
||||
|
|
@ -239,7 +239,7 @@ export function defineSlots<
|
|||
return null as any
|
||||
}
|
||||
|
||||
export type ModelRef<T, M extends string | number | symbol = string> = Ref<T> &
|
||||
export type ModelRef<T, M extends PropertyKey = string> = Ref<T> &
|
||||
[ModelRef<T, M>, Record<M, true | undefined>]
|
||||
|
||||
export type DefineModelOptions<T = any> = {
|
||||
|
|
@ -280,24 +280,24 @@ export type DefineModelOptions<T = any> = {
|
|||
* const count = defineModel<number>('count', { default: 0 })
|
||||
* ```
|
||||
*/
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
export function defineModel<T, M extends PropertyKey = string>(
|
||||
options: { required: true } & PropOptions<T> & DefineModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
export function defineModel<T, M extends PropertyKey = string>(
|
||||
options: { default: any } & PropOptions<T> & DefineModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
export function defineModel<T, M extends PropertyKey = string>(
|
||||
options?: PropOptions<T> & DefineModelOptions<T>,
|
||||
): ModelRef<T | undefined, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
export function defineModel<T, M extends PropertyKey = string>(
|
||||
name: string,
|
||||
options: { required: true } & PropOptions<T> & DefineModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
export function defineModel<T, M extends PropertyKey = string>(
|
||||
name: string,
|
||||
options: { default: any } & PropOptions<T> & DefineModelOptions<T>,
|
||||
): ModelRef<T, M>
|
||||
export function defineModel<T, M extends string | number | symbol = string>(
|
||||
export function defineModel<T, M extends PropertyKey = string>(
|
||||
name: string,
|
||||
options?: PropOptions<T> & DefineModelOptions<T>,
|
||||
): ModelRef<T | undefined, M>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import {
|
|||
type BaseWatchOptions,
|
||||
type ComputedRef,
|
||||
type DebuggerOptions,
|
||||
type ReactiveMarker,
|
||||
type Ref,
|
||||
baseWatch,
|
||||
getCurrentScope,
|
||||
|
|
@ -42,15 +43,13 @@ export type WatchCallback<V = any, OV = any> = (
|
|||
onCleanup: OnCleanup,
|
||||
) => any
|
||||
|
||||
type MaybeUndefined<T, I> = I extends true ? T | undefined : T
|
||||
|
||||
type MapSources<T, Immediate> = {
|
||||
[K in keyof T]: T[K] extends WatchSource<infer V>
|
||||
? Immediate extends true
|
||||
? V | undefined
|
||||
: V
|
||||
? MaybeUndefined<V, Immediate>
|
||||
: T[K] extends object
|
||||
? Immediate extends true
|
||||
? T[K] | undefined
|
||||
: T[K]
|
||||
? MaybeUndefined<T[K], Immediate>
|
||||
: never
|
||||
}
|
||||
|
||||
|
|
@ -103,7 +102,19 @@ type MultiWatchSources = (WatchSource<unknown> | object)[]
|
|||
// overload: single source + cb
|
||||
export function watch<T, Immediate extends Readonly<boolean> = false>(
|
||||
source: WatchSource<T>,
|
||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
||||
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
// overload: reactive array or tuple of multiple sources + cb
|
||||
export function watch<
|
||||
T extends Readonly<MultiWatchSources>,
|
||||
Immediate extends Readonly<boolean> = false,
|
||||
>(
|
||||
sources: readonly [...T] | T,
|
||||
cb: [T] extends [ReactiveMarker]
|
||||
? WatchCallback<T, MaybeUndefined<T, Immediate>>
|
||||
: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
|
|
@ -117,25 +128,13 @@ export function watch<
|
|||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
// overload: multiple sources w/ `as const`
|
||||
// watch([foo, bar] as const, () => {})
|
||||
// somehow [...T] breaks when the type is readonly
|
||||
export function watch<
|
||||
T extends Readonly<MultiWatchSources>,
|
||||
Immediate extends Readonly<boolean> = false,
|
||||
>(
|
||||
source: T,
|
||||
cb: WatchCallback<MapSources<T, false>, MapSources<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
// overload: watching reactive object w/ cb
|
||||
export function watch<
|
||||
T extends object,
|
||||
Immediate extends Readonly<boolean> = false,
|
||||
>(
|
||||
source: T,
|
||||
cb: WatchCallback<T, Immediate extends true ? T | undefined : T>,
|
||||
cb: WatchCallback<T, MaybeUndefined<T, Immediate>>,
|
||||
options?: WatchOptions<Immediate>,
|
||||
): WatchStopHandle
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,15 @@ export function convertLegacyComponent(
|
|||
|
||||
// 2.x constructor
|
||||
if (isFunction(comp) && comp.cid) {
|
||||
// #7766
|
||||
if (comp.render) {
|
||||
// only necessary when compiled from SFC
|
||||
comp.options.render = comp.render
|
||||
}
|
||||
// copy over internal properties set by the SFC compiler
|
||||
comp.options.__file = comp.__file
|
||||
comp.options.__hmrId = comp.__hmrId
|
||||
comp.options.__scopeId = comp.__scopeId
|
||||
comp = comp.options
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -102,11 +102,11 @@ export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
|
|||
/**
|
||||
* @deprecated Vue 3 no longer needs set() for adding new properties.
|
||||
*/
|
||||
set(target: any, key: string | number | symbol, value: any): void
|
||||
set(target: any, key: PropertyKey, value: any): void
|
||||
/**
|
||||
* @deprecated Vue 3 no longer needs delete() for property deletions.
|
||||
*/
|
||||
delete(target: any, key: string | number | symbol): void
|
||||
delete(target: any, key: PropertyKey): void
|
||||
/**
|
||||
* @deprecated use `reactive` instead.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -43,8 +43,15 @@ export type LegacyPublicInstance = ComponentPublicInstance &
|
|||
LegacyPublicProperties
|
||||
|
||||
export interface LegacyPublicProperties {
|
||||
$set(target: object, key: string, value: any): void
|
||||
$delete(target: object, key: string): void
|
||||
$set<T extends Record<keyof any, any>, K extends keyof T>(
|
||||
target: T,
|
||||
key: K,
|
||||
value: T[K],
|
||||
): void
|
||||
$delete<T extends Record<keyof any, any>, K extends keyof T>(
|
||||
target: T,
|
||||
key: K,
|
||||
): void
|
||||
$mount(el?: string | Element): this
|
||||
$destroy(): void
|
||||
$scopedSlots: Slots
|
||||
|
|
|
|||
|
|
@ -268,7 +268,7 @@ export type Component<
|
|||
|
||||
export type { ComponentOptions }
|
||||
|
||||
type LifecycleHook<TFn = Function> = TFn[] | null
|
||||
export type LifecycleHook<TFn = Function> = (TFn & SchedulerJob)[] | null
|
||||
|
||||
// use `E extends any` to force evaluating type to fix #2362
|
||||
export type SetupContext<
|
||||
|
|
@ -279,7 +279,9 @@ export type SetupContext<
|
|||
attrs: Data
|
||||
slots: UnwrapSlotsType<S>
|
||||
emit: EmitFn<E>
|
||||
expose: (exposed?: Record<string, any>) => void
|
||||
expose: <Exposed extends Record<string, any> = Record<string, any>>(
|
||||
exposed?: Exposed,
|
||||
) => void
|
||||
}
|
||||
: never
|
||||
|
||||
|
|
@ -369,7 +371,7 @@ export interface ComponentInternalInstance {
|
|||
* after initialized (e.g. inline handlers)
|
||||
* @internal
|
||||
*/
|
||||
renderCache: (Function | VNode)[]
|
||||
renderCache: (Function | VNode | undefined)[]
|
||||
|
||||
/**
|
||||
* Resolved component registry, only for components with mixins or extends
|
||||
|
|
@ -614,6 +616,7 @@ export function createComponentInstance(
|
|||
exposed: null,
|
||||
exposeProxy: null,
|
||||
withProxy: null,
|
||||
|
||||
provides: parent ? parent.provides : Object.create(appContext.provides),
|
||||
accessCache: null!,
|
||||
renderCache: [],
|
||||
|
|
@ -1153,7 +1156,9 @@ export function createSetupContext(
|
|||
}
|
||||
}
|
||||
|
||||
export function getExposeProxy(instance: ComponentInternalInstance) {
|
||||
export function getComponentPublicInstance(
|
||||
instance: ComponentInternalInstance,
|
||||
) {
|
||||
if (instance.exposed) {
|
||||
return (
|
||||
instance.exposeProxy ||
|
||||
|
|
@ -1170,6 +1175,8 @@ export function getExposeProxy(instance: ComponentInternalInstance) {
|
|||
},
|
||||
}))
|
||||
)
|
||||
} else {
|
||||
return instance.proxy
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
type Component,
|
||||
type ComponentInternalInstance,
|
||||
getExposeProxy,
|
||||
getComponentPublicInstance,
|
||||
isStatefulComponent,
|
||||
} from './component'
|
||||
import { nextTick, queueJob } from './scheduler'
|
||||
|
|
@ -292,7 +292,7 @@ export type ComponentPublicInstance<
|
|||
C extends ComputedOptions = {},
|
||||
M extends MethodOptions = {},
|
||||
E extends EmitsOptions = {},
|
||||
PublicProps = P,
|
||||
PublicProps = {},
|
||||
Defaults = {},
|
||||
MakeDefaultsOptional extends boolean = false,
|
||||
Options = ComponentOptionsBase<any, any, any, any, any, any, any, any, any>,
|
||||
|
|
@ -323,7 +323,11 @@ export type ComponentPublicInstance<
|
|||
options?: WatchOptions,
|
||||
): WatchStopHandle
|
||||
} & ExposedKeys<
|
||||
IfAny<P, P, Omit<P, keyof ShallowUnwrapRef<B>>> &
|
||||
IfAny<
|
||||
P,
|
||||
P,
|
||||
Readonly<Defaults> & Omit<P, keyof ShallowUnwrapRef<B> | keyof Defaults>
|
||||
> &
|
||||
ShallowUnwrapRef<B> &
|
||||
UnwrapNestedRefs<D> &
|
||||
ExtractComputedReturns<C> &
|
||||
|
|
@ -347,7 +351,7 @@ const getPublicInstance = (
|
|||
i: ComponentInternalInstance | null,
|
||||
): ComponentPublicInstance | ComponentInternalInstance['exposed'] | null => {
|
||||
if (!i) return null
|
||||
if (isStatefulComponent(i)) return getExposeProxy(i) || i.proxy
|
||||
if (isStatefulComponent(i)) return getComponentPublicInstance(i)
|
||||
return getPublicInstance(i.parent)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -136,6 +136,11 @@ export const BaseTransitionPropsValidators = {
|
|||
onAppearCancelled: TransitionHookValidator,
|
||||
}
|
||||
|
||||
const recursiveGetSubtree = (instance: ComponentInternalInstance): VNode => {
|
||||
const subTree = instance.subTree
|
||||
return subTree.component ? recursiveGetSubtree(subTree.component) : subTree
|
||||
}
|
||||
|
||||
const BaseTransitionImpl: ComponentOptions = {
|
||||
name: `BaseTransition`,
|
||||
|
||||
|
|
@ -179,11 +184,13 @@ const BaseTransitionImpl: ComponentOptions = {
|
|||
return emptyPlaceholder(child)
|
||||
}
|
||||
|
||||
const enterHooks = resolveTransitionHooks(
|
||||
let enterHooks = resolveTransitionHooks(
|
||||
innerChild,
|
||||
rawProps,
|
||||
state,
|
||||
instance,
|
||||
// #11061, ensure enterHooks is fresh after clone
|
||||
hooks => (enterHooks = hooks),
|
||||
)
|
||||
setTransitionHooks(innerChild, enterHooks)
|
||||
|
||||
|
|
@ -194,7 +201,8 @@ const BaseTransitionImpl: ComponentOptions = {
|
|||
if (
|
||||
oldInnerChild &&
|
||||
oldInnerChild.type !== Comment &&
|
||||
!isSameVNodeType(innerChild, oldInnerChild)
|
||||
!isSameVNodeType(innerChild, oldInnerChild) &&
|
||||
recursiveGetSubtree(instance).type !== Comment
|
||||
) {
|
||||
const leavingHooks = resolveTransitionHooks(
|
||||
oldInnerChild,
|
||||
|
|
@ -303,6 +311,7 @@ export function resolveTransitionHooks(
|
|||
props: BaseTransitionProps<any>,
|
||||
state: TransitionState,
|
||||
instance: ComponentInternalInstance,
|
||||
postClone?: (hooks: TransitionHooks) => void,
|
||||
): TransitionHooks {
|
||||
const {
|
||||
appear,
|
||||
|
|
@ -443,7 +452,15 @@ export function resolveTransitionHooks(
|
|||
},
|
||||
|
||||
clone(vnode) {
|
||||
return resolveTransitionHooks(vnode, props, state, instance)
|
||||
const hooks = resolveTransitionHooks(
|
||||
vnode,
|
||||
props,
|
||||
state,
|
||||
instance,
|
||||
postClone,
|
||||
)
|
||||
if (postClone) postClone(hooks)
|
||||
return hooks
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import {
|
|||
type RendererElement,
|
||||
type RendererInternals,
|
||||
type RendererNode,
|
||||
invalidateMount,
|
||||
queuePostRenderEffect,
|
||||
} from '../renderer'
|
||||
import { setTransitionHooks } from './BaseTransition'
|
||||
|
|
@ -55,7 +56,7 @@ export interface KeepAliveProps {
|
|||
max?: number | string
|
||||
}
|
||||
|
||||
type CacheKey = string | number | symbol | ConcreteComponent
|
||||
type CacheKey = PropertyKey | ConcreteComponent
|
||||
type Cache = Map<CacheKey, VNode>
|
||||
type Keys = Set<CacheKey>
|
||||
|
||||
|
|
@ -166,6 +167,9 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
|
||||
sharedContext.deactivate = (vnode: VNode) => {
|
||||
const instance = vnode.component!
|
||||
invalidateMount(instance.m)
|
||||
invalidateMount(instance.a)
|
||||
|
||||
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
|
||||
queuePostRenderEffect(() => {
|
||||
if (instance.da) {
|
||||
|
|
|
|||
|
|
@ -438,6 +438,7 @@ export interface SuspenseBoundary {
|
|||
registerDep(
|
||||
instance: ComponentInternalInstance,
|
||||
setupRenderEffect: SetupRenderEffectFn,
|
||||
optimized: boolean,
|
||||
): void
|
||||
unmount(parentSuspense: SuspenseBoundary | null, doRemove?: boolean): void
|
||||
}
|
||||
|
|
@ -679,7 +680,7 @@ function createSuspenseBoundary(
|
|||
return suspense.activeBranch && next(suspense.activeBranch)
|
||||
},
|
||||
|
||||
registerDep(instance, setupRenderEffect) {
|
||||
registerDep(instance, setupRenderEffect, optimized) {
|
||||
const isInPendingSuspense = !!suspense.pendingBranch
|
||||
if (isInPendingSuspense) {
|
||||
suspense.deps++
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ import type { VNode } from './vnode'
|
|||
import { EMPTY_OBJ, isBuiltInDirective, isFunction } from '@vue/shared'
|
||||
import type { Data } from '@vue/runtime-shared'
|
||||
import { warn } from './warning'
|
||||
import { type ComponentInternalInstance, getExposeProxy } from './component'
|
||||
import {
|
||||
type ComponentInternalInstance,
|
||||
getComponentPublicInstance,
|
||||
} from './component'
|
||||
import { currentRenderingInstance } from './componentRenderContext'
|
||||
import { ErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
|
||||
import type { ComponentPublicInstance } from './componentPublicInstance'
|
||||
|
|
@ -27,7 +30,7 @@ export interface DirectiveBinding<
|
|||
Modifiers extends string = string,
|
||||
Arg extends string = string,
|
||||
> {
|
||||
instance: ComponentPublicInstance | null
|
||||
instance: ComponentPublicInstance | Record<string, any> | null
|
||||
value: Value
|
||||
oldValue: Value | null
|
||||
arg?: Arg
|
||||
|
|
@ -135,9 +138,7 @@ export function withDirectives<T extends VNode>(
|
|||
__DEV__ && warn(`withDirectives can only be used inside render functions.`)
|
||||
return vnode
|
||||
}
|
||||
const instance =
|
||||
(getExposeProxy(currentRenderingInstance) as ComponentPublicInstance) ||
|
||||
currentRenderingInstance.proxy
|
||||
const instance = getComponentPublicInstance(currentRenderingInstance)
|
||||
const bindings: DirectiveBinding[] = vnode.dirs || (vnode.dirs = [])
|
||||
for (let i = 0; i < directives.length; i++) {
|
||||
let [dir, value, arg, modifiers = EMPTY_OBJ] = directives[i]
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import type { NormalizedProps } from '../componentProps'
|
|||
import { watchSyncEffect } from '../apiWatch'
|
||||
|
||||
export function useModel<
|
||||
M extends string | number | symbol,
|
||||
M extends PropertyKey,
|
||||
T extends Record<string, any>,
|
||||
K extends keyof T,
|
||||
>(props: T, name: K, options?: DefineModelOptions<T[K]>): ModelRef<T[K], M>
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ export function withMemo(
|
|||
|
||||
// shallow clone
|
||||
ret.memo = memo.slice()
|
||||
ret.memoIndex = index
|
||||
|
||||
return (cache[index] = ret)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,11 @@ function reload(id: string, newComp: HMRComponent) {
|
|||
// 4. Force the parent instance to re-render. This will cause all updated
|
||||
// components to be unmounted and re-mounted. Queue the update so that we
|
||||
// don't end up forcing the same parent to re-render multiple times.
|
||||
queueJob(instance.parent.update)
|
||||
queueJob(() => {
|
||||
instance.parent!.update()
|
||||
// #6930 avoid infinite recursion
|
||||
hmrDirtyComponents.delete(oldComp)
|
||||
})
|
||||
} else if (instance.appContext.reload) {
|
||||
// root instance mounted via createApp() has a reload method
|
||||
instance.appContext.reload()
|
||||
|
|
|
|||
|
|
@ -50,7 +50,15 @@ enum DOMNodeTypes {
|
|||
COMMENT = 8,
|
||||
}
|
||||
|
||||
let hasMismatch = false
|
||||
let hasLoggedMismatchError = false
|
||||
const logMismatchError = () => {
|
||||
if (__TEST__ || hasLoggedMismatchError) {
|
||||
return
|
||||
}
|
||||
// this error should show up in production
|
||||
console.error('Hydration completed but contains mismatches.')
|
||||
hasLoggedMismatchError = true
|
||||
}
|
||||
|
||||
const isSVGContainer = (container: Element) =>
|
||||
container.namespaceURI!.includes('svg') &&
|
||||
|
|
@ -102,14 +110,10 @@ export function createHydrationFunctions(
|
|||
container._vnode = vnode
|
||||
return
|
||||
}
|
||||
hasMismatch = false
|
||||
|
||||
hydrateNode(container.firstChild!, vnode, null, null, null)
|
||||
flushPostFlushCbs()
|
||||
container._vnode = vnode
|
||||
if (hasMismatch && !__TEST__) {
|
||||
// this error should show up in production
|
||||
console.error(`Hydration completed but contains mismatches.`)
|
||||
}
|
||||
}
|
||||
|
||||
const hydrateNode = (
|
||||
|
|
@ -170,7 +174,6 @@ export function createHydrationFunctions(
|
|||
}
|
||||
} else {
|
||||
if ((node as Text).data !== vnode.children) {
|
||||
hasMismatch = true
|
||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
warn(
|
||||
`Hydration text mismatch in`,
|
||||
|
|
@ -180,6 +183,7 @@ export function createHydrationFunctions(
|
|||
)}` +
|
||||
`\n - expected on client: ${JSON.stringify(vnode.children)}`,
|
||||
)
|
||||
logMismatchError()
|
||||
;(node as Text).data = vnode.children as string
|
||||
}
|
||||
nextNode = nextSibling(node)
|
||||
|
|
@ -366,7 +370,7 @@ export function createHydrationFunctions(
|
|||
const forcePatch = type === 'input' || type === 'option'
|
||||
// skip props & children if this is hoisted static nodes
|
||||
// #5405 in dev, always hydrate children for HMR
|
||||
if (__DEV__ || forcePatch || patchFlag !== PatchFlags.HOISTED) {
|
||||
if (__DEV__ || forcePatch || patchFlag !== PatchFlags.CACHED) {
|
||||
if (dirs) {
|
||||
invokeDirectiveHook(vnode, null, parentComponent, 'created')
|
||||
}
|
||||
|
|
@ -409,7 +413,6 @@ export function createHydrationFunctions(
|
|||
)
|
||||
let hasWarned = false
|
||||
while (next) {
|
||||
hasMismatch = true
|
||||
if (
|
||||
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
!hasWarned
|
||||
|
|
@ -421,6 +424,8 @@ export function createHydrationFunctions(
|
|||
)
|
||||
hasWarned = true
|
||||
}
|
||||
logMismatchError()
|
||||
|
||||
// The SSRed DOM contains more nodes than it should. Remove them.
|
||||
const cur = next
|
||||
next = next.nextSibling
|
||||
|
|
@ -428,7 +433,6 @@ export function createHydrationFunctions(
|
|||
}
|
||||
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
||||
if (el.textContent !== vnode.children) {
|
||||
hasMismatch = true
|
||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
warn(
|
||||
`Hydration text content mismatch on`,
|
||||
|
|
@ -436,6 +440,8 @@ export function createHydrationFunctions(
|
|||
`\n - rendered on server: ${el.textContent}` +
|
||||
`\n - expected on client: ${vnode.children as string}`,
|
||||
)
|
||||
logMismatchError()
|
||||
|
||||
el.textContent = vnode.children as string
|
||||
}
|
||||
}
|
||||
|
|
@ -455,7 +461,7 @@ export function createHydrationFunctions(
|
|||
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
propHasMismatch(el, key, props[key], vnode, parentComponent)
|
||||
) {
|
||||
hasMismatch = true
|
||||
logMismatchError()
|
||||
}
|
||||
if (
|
||||
(forcePatch &&
|
||||
|
|
@ -545,7 +551,6 @@ export function createHydrationFunctions(
|
|||
// because server rendered HTML won't contain a text node
|
||||
insert((vnode.el = createText('')), container)
|
||||
} else {
|
||||
hasMismatch = true
|
||||
if (
|
||||
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
!hasWarned
|
||||
|
|
@ -557,6 +562,8 @@ export function createHydrationFunctions(
|
|||
)
|
||||
hasWarned = true
|
||||
}
|
||||
logMismatchError()
|
||||
|
||||
// the SSRed DOM didn't contain enough nodes. Mount the missing ones.
|
||||
patch(
|
||||
null,
|
||||
|
|
@ -603,7 +610,8 @@ export function createHydrationFunctions(
|
|||
} else {
|
||||
// fragment didn't hydrate successfully, since we didn't get a end anchor
|
||||
// back. This should have led to node/children mismatch warnings.
|
||||
hasMismatch = true
|
||||
logMismatchError()
|
||||
|
||||
// since the anchor is missing, we need to create one and insert it
|
||||
insert((vnode.anchor = createComment(`]`)), container, next)
|
||||
return next
|
||||
|
|
@ -618,7 +626,6 @@ export function createHydrationFunctions(
|
|||
slotScopeIds: string[] | null,
|
||||
isFragment: boolean,
|
||||
): Node | null => {
|
||||
hasMismatch = true
|
||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
warn(
|
||||
`Hydration node mismatch:\n- rendered on server:`,
|
||||
|
|
@ -631,6 +638,8 @@ export function createHydrationFunctions(
|
|||
`\n- expected on client:`,
|
||||
vnode.type,
|
||||
)
|
||||
logMismatchError()
|
||||
|
||||
vnode.el = null
|
||||
|
||||
if (isFragment) {
|
||||
|
|
|
|||
|
|
@ -214,6 +214,7 @@ export type {
|
|||
DebuggerEvent,
|
||||
DebuggerEventExtraInfo,
|
||||
Raw,
|
||||
Reactive,
|
||||
} from '@vue/reactivity'
|
||||
export type {
|
||||
WatchEffect,
|
||||
|
|
@ -375,7 +376,11 @@ export { transformVNodeArgs } from './vnode'
|
|||
// **IMPORTANT** These APIs are exposed solely for @vue/server-renderer and may
|
||||
// change without notice between versions. User code should never rely on them.
|
||||
|
||||
import { createComponentInstance, setupComponent } from './component'
|
||||
import {
|
||||
createComponentInstance,
|
||||
getComponentPublicInstance,
|
||||
setupComponent,
|
||||
} from './component'
|
||||
import { renderComponentRoot } from './componentRenderUtils'
|
||||
import { setCurrentRenderingInstance } from './componentRenderContext'
|
||||
import { isVNode, normalizeVNode } from './vnode'
|
||||
|
|
@ -387,6 +392,7 @@ const _ssrUtils = {
|
|||
setCurrentRenderingInstance,
|
||||
isVNode,
|
||||
normalizeVNode,
|
||||
getComponentPublicInstance,
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {
|
|||
import {
|
||||
type ComponentInternalInstance,
|
||||
type ComponentOptions,
|
||||
type LifecycleHook,
|
||||
createComponentInstance,
|
||||
setupComponent,
|
||||
} from './component'
|
||||
|
|
@ -1257,7 +1258,8 @@ function baseCreateRenderer(
|
|||
// setup() is async. This component relies on async logic to be resolved
|
||||
// before proceeding
|
||||
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
|
||||
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
|
||||
parentSuspense &&
|
||||
parentSuspense.registerDep(instance, setupRenderEffect, optimized)
|
||||
|
||||
// Give it a placeholder if this is not hydration
|
||||
// TODO handle self-defined fallback
|
||||
|
|
@ -1921,7 +1923,7 @@ function baseCreateRenderer(
|
|||
const s2 = i // next starting index
|
||||
|
||||
// 5.1 build key:index map for newChildren
|
||||
const keyToNewIndexMap: Map<string | number | symbol, number> = new Map()
|
||||
const keyToNewIndexMap: Map<PropertyKey, number> = new Map()
|
||||
for (i = s2; i <= e2; i++) {
|
||||
const nextChild = (c2[i] = optimized
|
||||
? cloneIfMounted(c2[i] as VNode)
|
||||
|
|
@ -2122,12 +2124,18 @@ function baseCreateRenderer(
|
|||
shapeFlag,
|
||||
patchFlag,
|
||||
dirs,
|
||||
memoIndex,
|
||||
} = vnode
|
||||
// unset ref
|
||||
if (ref != null) {
|
||||
setRef(ref, null, parentSuspense, vnode, true)
|
||||
}
|
||||
|
||||
// #6593 should clean memo cache when unmount
|
||||
if (memoIndex != null) {
|
||||
parentComponent!.renderCache[memoIndex] = undefined
|
||||
}
|
||||
|
||||
if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
|
||||
;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
|
||||
return
|
||||
|
|
@ -2279,7 +2287,9 @@ function baseCreateRenderer(
|
|||
unregisterHMR(instance)
|
||||
}
|
||||
|
||||
const { bum, scope, job, subTree, um } = instance
|
||||
const { bum, scope, job, subTree, um, m, a } = instance
|
||||
invalidateMount(m)
|
||||
invalidateMount(a)
|
||||
|
||||
// beforeUnmount hook
|
||||
if (bum) {
|
||||
|
|
@ -2483,7 +2493,8 @@ export function traverseStaticChildren(n1: VNode, n2: VNode, shallow = false) {
|
|||
c2 = ch2[i] = cloneIfMounted(ch2[i] as VNode)
|
||||
c2.el = c1.el
|
||||
}
|
||||
if (!shallow) traverseStaticChildren(c1, c2)
|
||||
if (!shallow && c2.patchFlag !== PatchFlags.BAIL)
|
||||
traverseStaticChildren(c1, c2)
|
||||
}
|
||||
// #6852 also inherit for text nodes
|
||||
if (c2.type === Text) {
|
||||
|
|
@ -2552,3 +2563,10 @@ function locateNonHydratedAsyncRoot(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function invalidateMount(hooks: LifecycleHook) {
|
||||
if (hooks) {
|
||||
for (let i = 0; i < hooks.length; i++)
|
||||
hooks[i].flags! |= SchedulerJobFlags.DISPOSED
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@ import {
|
|||
remove,
|
||||
} from '@vue/shared'
|
||||
import { isAsyncWrapper } from './apiAsyncComponent'
|
||||
import { getExposeProxy } from './component'
|
||||
import { warn } from './warning'
|
||||
import { isRef } from '@vue/reactivity'
|
||||
import { ErrorCodes, callWithErrorHandling } from './errorHandling'
|
||||
import type { SchedulerJob } from './scheduler'
|
||||
import { queuePostRenderEffect } from './renderer'
|
||||
import { getComponentPublicInstance } from './component'
|
||||
|
||||
/**
|
||||
* Function for handling a template ref
|
||||
|
|
@ -48,7 +48,7 @@ export function setRef(
|
|||
|
||||
const refValue =
|
||||
vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT
|
||||
? getExposeProxy(vnode.component!) || vnode.component!.proxy
|
||||
? getComponentPublicInstance(vnode.component!)
|
||||
: vnode.el
|
||||
const value = isUnmount ? null : refValue
|
||||
|
||||
|
|
|
|||
|
|
@ -170,14 +170,12 @@ export function flushPostFlushCbs(seen?: CountMap) {
|
|||
postFlushIndex < activePostFlushCbs.length;
|
||||
postFlushIndex++
|
||||
) {
|
||||
if (
|
||||
__DEV__ &&
|
||||
checkRecursiveUpdates(seen!, activePostFlushCbs[postFlushIndex])
|
||||
) {
|
||||
const cb = activePostFlushCbs[postFlushIndex]
|
||||
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
|
||||
continue
|
||||
}
|
||||
activePostFlushCbs[postFlushIndex]()
|
||||
activePostFlushCbs[postFlushIndex].flags! &= ~SchedulerJobFlags.QUEUED
|
||||
if (!(cb.flags! & SchedulerJobFlags.DISPOSED)) cb()
|
||||
cb.flags! &= ~SchedulerJobFlags.QUEUED
|
||||
}
|
||||
activePostFlushCbs = null
|
||||
postFlushIndex = 0
|
||||
|
|
|
|||
|
|
@ -36,7 +36,10 @@ import {
|
|||
isSuspense,
|
||||
} from './components/Suspense'
|
||||
import type { DirectiveBinding } from './directives'
|
||||
import type { TransitionHooks } from './components/BaseTransition'
|
||||
import {
|
||||
type TransitionHooks,
|
||||
setTransitionHooks,
|
||||
} from './components/BaseTransition'
|
||||
import { warn } from './warning'
|
||||
import {
|
||||
type Teleport,
|
||||
|
|
@ -109,7 +112,7 @@ export type VNodeHook =
|
|||
|
||||
// https://github.com/microsoft/TypeScript/issues/33099
|
||||
export type VNodeProps = {
|
||||
key?: string | number | symbol
|
||||
key?: PropertyKey
|
||||
ref?: VNodeRef
|
||||
ref_for?: boolean
|
||||
ref_key?: string
|
||||
|
|
@ -159,7 +162,7 @@ export interface VNode<
|
|||
|
||||
type: VNodeTypes
|
||||
props: (VNodeProps & ExtraProps) | null
|
||||
key: string | number | symbol | null
|
||||
key: PropertyKey | null
|
||||
ref: VNodeNormalizedRef | null
|
||||
/**
|
||||
* SFC only. This is assigned on vnode creation using currentScopeId
|
||||
|
|
@ -225,6 +228,10 @@ export interface VNode<
|
|||
* @internal attached by v-memo
|
||||
*/
|
||||
memo?: any[]
|
||||
/**
|
||||
* @internal index for cleaning v-memo cache
|
||||
*/
|
||||
memoIndex?: number
|
||||
/**
|
||||
* @internal __COMPAT__ only
|
||||
*/
|
||||
|
|
@ -546,7 +553,7 @@ function _createVNode(
|
|||
currentBlock.push(cloned)
|
||||
}
|
||||
}
|
||||
cloned.patchFlag |= PatchFlags.BAIL
|
||||
cloned.patchFlag = PatchFlags.BAIL
|
||||
return cloned
|
||||
}
|
||||
|
||||
|
|
@ -650,7 +657,7 @@ export function cloneVNode<T, U>(
|
|||
scopeId: vnode.scopeId,
|
||||
slotScopeIds: vnode.slotScopeIds,
|
||||
children:
|
||||
__DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
|
||||
__DEV__ && patchFlag === PatchFlags.CACHED && isArray(children)
|
||||
? (children as VNode[]).map(deepCloneVNode)
|
||||
: children,
|
||||
target: vnode.target,
|
||||
|
|
@ -663,7 +670,7 @@ export function cloneVNode<T, U>(
|
|||
// fast paths only.
|
||||
patchFlag:
|
||||
extraProps && vnode.type !== Fragment
|
||||
? patchFlag === PatchFlags.HOISTED // hoisted node
|
||||
? patchFlag === PatchFlags.CACHED // hoisted node
|
||||
? PatchFlags.FULL_PROPS
|
||||
: patchFlag | PatchFlags.FULL_PROPS
|
||||
: patchFlag,
|
||||
|
|
@ -691,7 +698,10 @@ export function cloneVNode<T, U>(
|
|||
// to clone the transition to ensure that the vnode referenced within
|
||||
// the transition hooks is fresh.
|
||||
if (transition && cloneTransition) {
|
||||
cloned.transition = transition.clone(cloned as VNode)
|
||||
setTransitionHooks(
|
||||
cloned as VNode,
|
||||
transition.clone(cloned as VNode) as TransitionHooks,
|
||||
)
|
||||
}
|
||||
|
||||
if (__COMPAT__) {
|
||||
|
|
@ -772,7 +782,7 @@ export function normalizeVNode(child: VNodeChild): VNode {
|
|||
|
||||
// optimized normalization for template-compiled render fns
|
||||
export function cloneIfMounted(child: VNode): VNode {
|
||||
return (child.el === null && child.patchFlag !== PatchFlags.HOISTED) ||
|
||||
return (child.el === null && child.patchFlag !== PatchFlags.CACHED) ||
|
||||
child.memo
|
||||
? child
|
||||
: cloneVNode(child)
|
||||
|
|
|
|||
|
|
@ -342,6 +342,23 @@ describe('defineCustomElement', () => {
|
|||
expect(el.maxAge).toBe(50)
|
||||
expect(el.shadowRoot.innerHTML).toBe('max age: 50/type: number')
|
||||
})
|
||||
|
||||
test('support direct setup function syntax with extra options', () => {
|
||||
const E = defineCustomElement(
|
||||
props => {
|
||||
return () => props.text
|
||||
},
|
||||
{
|
||||
props: {
|
||||
text: String,
|
||||
},
|
||||
},
|
||||
)
|
||||
customElements.define('my-el-setup-with-props', E)
|
||||
container.innerHTML = `<my-el-setup-with-props text="hello"></my-el-setup-with-props>`
|
||||
const e = container.childNodes[0] as VueElement
|
||||
expect(e.shadowRoot!.innerHTML).toBe('hello')
|
||||
})
|
||||
})
|
||||
|
||||
describe('attrs', () => {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,20 @@ describe('runtime-dom: node-ops', () => {
|
|||
expect(option2.selected).toBe(true)
|
||||
})
|
||||
|
||||
test('create custom elements', () => {
|
||||
const spyCreateElement = vi.spyOn(document, 'createElement')
|
||||
|
||||
nodeOps.createElement('custom-element')
|
||||
expect(spyCreateElement).toHaveBeenLastCalledWith('custom-element')
|
||||
|
||||
nodeOps.createElement('custom-element', undefined, 'li')
|
||||
expect(spyCreateElement).toHaveBeenLastCalledWith('custom-element', {
|
||||
is: 'li',
|
||||
})
|
||||
|
||||
spyCreateElement.mockClear()
|
||||
})
|
||||
|
||||
describe('insertStaticContent', () => {
|
||||
test('fresh insertion', () => {
|
||||
const content = `<div>one</div><div>two</div>three`
|
||||
|
|
|
|||
|
|
@ -53,4 +53,20 @@ describe('runtime-dom: attrs patching', () => {
|
|||
patchProp(el, 'onwards', 'a', null)
|
||||
expect(el.getAttribute('onwards')).toBe(null)
|
||||
})
|
||||
|
||||
// #10597
|
||||
test('should allow setting attribute to symbol', () => {
|
||||
const el = document.createElement('div')
|
||||
const symbol = Symbol('foo')
|
||||
patchProp(el, 'foo', null, symbol)
|
||||
expect(el.getAttribute('foo')).toBe(symbol.toString())
|
||||
})
|
||||
|
||||
// #10598
|
||||
test('should allow setting value to symbol', () => {
|
||||
const el = document.createElement('input')
|
||||
const symbol = Symbol('foo')
|
||||
patchProp(el, 'value', null, symbol)
|
||||
expect(el.value).toBe(symbol.toString())
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@
|
|||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/runtime-core": "workspace:*",
|
||||
"@vue/reactivity": "workspace:*",
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,10 +38,16 @@ export type VueElementConstructor<P = {}> = {
|
|||
|
||||
// overload 1: direct setup function
|
||||
export function defineCustomElement<Props, RawBindings = object>(
|
||||
setup: (
|
||||
props: Readonly<Props>,
|
||||
ctx: SetupContext,
|
||||
) => RawBindings | RenderFunction,
|
||||
setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction,
|
||||
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs' | 'emits'> & {
|
||||
props?: (keyof Props)[]
|
||||
},
|
||||
): VueElementConstructor<Props>
|
||||
export function defineCustomElement<Props, RawBindings = object>(
|
||||
setup: (props: Props, ctx: SetupContext) => RawBindings | RenderFunction,
|
||||
options?: Pick<ComponentOptions, 'name' | 'inheritAttrs' | 'emits'> & {
|
||||
props?: ComponentObjectPropsOptions<Props>
|
||||
},
|
||||
): VueElementConstructor<Props>
|
||||
|
||||
// overload 2: defineCustomElement with options object, infer props from options
|
||||
|
|
@ -127,9 +133,13 @@ export function defineCustomElement<P>(
|
|||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export function defineCustomElement(
|
||||
options: any,
|
||||
extraOptions?: ComponentOptions,
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
hydrate?: RootHydrateFunction,
|
||||
): VueElementConstructor {
|
||||
const Comp = defineComponent(options) as any
|
||||
const Comp = defineComponent(options, extraOptions) as any
|
||||
class VueCustomElement extends VueElement {
|
||||
static def = Comp
|
||||
constructor(initialProps?: Record<string, any>) {
|
||||
|
|
@ -141,9 +151,12 @@ export function defineCustomElement(
|
|||
}
|
||||
|
||||
/*! #__NO_SIDE_EFFECTS__ */
|
||||
export const defineSSRCustomElement = ((options: any) => {
|
||||
export const defineSSRCustomElement = ((
|
||||
options: any,
|
||||
extraOptions?: ComponentOptions,
|
||||
) => {
|
||||
// @ts-expect-error
|
||||
return defineCustomElement(options, hydrate)
|
||||
return defineCustomElement(options, extraOptions, hydrate)
|
||||
}) as typeof defineCustomElement
|
||||
|
||||
const BaseClass = (
|
||||
|
|
|
|||
|
|
@ -85,7 +85,11 @@ export const vModelText: ModelDirective<
|
|||
mounted(el, { value }) {
|
||||
el.value = value == null ? '' : value
|
||||
},
|
||||
beforeUpdate(el, { value, modifiers: { lazy, trim, number } }, vnode) {
|
||||
beforeUpdate(
|
||||
el,
|
||||
{ value, oldValue, modifiers: { lazy, trim, number } },
|
||||
vnode,
|
||||
) {
|
||||
el[assignKey] = getModelAssigner(vnode)
|
||||
// avoid clearing unresolved text. #2302
|
||||
if ((el as any).composing) return
|
||||
|
|
@ -100,7 +104,8 @@ export const vModelText: ModelDirective<
|
|||
}
|
||||
|
||||
if (document.activeElement === el && el.type !== 'range') {
|
||||
if (lazy) {
|
||||
// #8546
|
||||
if (lazy && value === oldValue) {
|
||||
return
|
||||
}
|
||||
if (trim && el.value.trim() === newValue) {
|
||||
|
|
|
|||
|
|
@ -1390,7 +1390,7 @@ type EventHandlers<E> = {
|
|||
import type { VNodeRef } from '@vue/runtime-core'
|
||||
|
||||
export type ReservedProps = {
|
||||
key?: string | number | symbol
|
||||
key?: PropertyKey
|
||||
ref?: VNodeRef
|
||||
ref_for?: boolean
|
||||
ref_key?: string
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export function patchAttr(
|
|||
value: any,
|
||||
isSVG: boolean,
|
||||
instance?: ComponentInternalInstance | null,
|
||||
isBoolean = isSpecialBooleanAttr(key),
|
||||
) {
|
||||
if (isSVG && key.startsWith('xlink:')) {
|
||||
if (value == null) {
|
||||
|
|
@ -32,11 +33,11 @@ export function patchAttr(
|
|||
|
||||
// note we are only checking boolean attributes that don't have a
|
||||
// corresponding dom prop of the same name here.
|
||||
const isBoolean = isSpecialBooleanAttr(key)
|
||||
if (value == null || (isBoolean && !includeBooleanAttr(value))) {
|
||||
el.removeAttribute(key)
|
||||
} else {
|
||||
el.setAttribute(key, isBoolean ? '' : value)
|
||||
// attribute value is a string https://html.spec.whatwg.org/multipage/dom.html#attributes
|
||||
el.setAttribute(key, isBoolean ? '' : String(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -75,12 +76,13 @@ export function compatCoerceAttr(
|
|||
} else if (
|
||||
value === false &&
|
||||
!isSpecialBooleanAttr(key) &&
|
||||
compatUtils.softAssertCompatEnabled(
|
||||
compatUtils.isCompatEnabled(DeprecationTypes.ATTR_FALSE_VALUE, instance)
|
||||
) {
|
||||
compatUtils.warnDeprecation(
|
||||
DeprecationTypes.ATTR_FALSE_VALUE,
|
||||
instance,
|
||||
key,
|
||||
)
|
||||
) {
|
||||
el.removeAttribute(key)
|
||||
return true
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ export function patchDOMProp(
|
|||
// compare against its attribute value instead.
|
||||
const oldValue =
|
||||
tag === 'OPTION' ? el.getAttribute('value') || '' : el.value
|
||||
const newValue = value == null ? '' : value
|
||||
const newValue = value == null ? '' : String(value)
|
||||
if (oldValue !== newValue || !('_value' in el)) {
|
||||
el.value = newValue
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,9 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
|
|||
? doc.createElementNS(svgNS, tag)
|
||||
: namespace === 'mathml'
|
||||
? doc.createElementNS(mathmlNS, tag)
|
||||
: doc.createElement(tag, is ? { is } : undefined)
|
||||
: is
|
||||
? doc.createElement(tag, { is })
|
||||
: doc.createElement(tag)
|
||||
|
||||
if (tag === 'select' && props && props.multiple != null) {
|
||||
;(el as HTMLSelectElement).setAttribute('multiple', props.multiple)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,11 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
parentSuspense,
|
||||
unmountChildren,
|
||||
)
|
||||
// #6007 also set form state as attributes so they work with
|
||||
// <input type="reset"> or libs / extensions that expect attributes
|
||||
if (key === 'value' || key === 'checked' || key === 'selected') {
|
||||
patchAttr(el, key, nextValue, isSVG, parentComponent, key !== 'value')
|
||||
}
|
||||
} else {
|
||||
// special case for <input v-model type="checkbox"> with
|
||||
// :true-value & :false-value
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ import { renderToString } from '../src/renderToString'
|
|||
import {
|
||||
createApp,
|
||||
h,
|
||||
mergeProps,
|
||||
ref,
|
||||
resolveDirective,
|
||||
unref,
|
||||
vModelCheckbox,
|
||||
vModelDynamic,
|
||||
vModelRadio,
|
||||
|
|
@ -542,4 +545,44 @@ describe('ssr: directives', () => {
|
|||
),
|
||||
).toBe(`<div id="foo-arg-true"></div>`)
|
||||
})
|
||||
|
||||
// #7499
|
||||
test('custom directive w/ getSSRProps (expose)', async () => {
|
||||
let exposeVars: null | string | undefined = null
|
||||
const useTestDirective = () => ({
|
||||
vTest: {
|
||||
getSSRProps({ instance }: any) {
|
||||
if (instance) {
|
||||
exposeVars = instance.x
|
||||
}
|
||||
return { id: exposeVars }
|
||||
},
|
||||
},
|
||||
})
|
||||
const { vTest } = useTestDirective()
|
||||
|
||||
const renderString = await renderToString(
|
||||
createApp({
|
||||
setup(props, { expose }) {
|
||||
const x = ref('foo')
|
||||
expose({ x })
|
||||
const __returned__ = { useTestDirective, vTest, ref, x }
|
||||
Object.defineProperty(__returned__, '__isScriptSetup', {
|
||||
enumerable: false,
|
||||
value: true,
|
||||
})
|
||||
return __returned__
|
||||
},
|
||||
ssrRender(_ctx, _push, _parent, _attrs) {
|
||||
_push(
|
||||
`<div${ssrRenderAttrs(
|
||||
mergeProps(_attrs!, ssrGetDirectiveProps(_ctx, unref(vTest))),
|
||||
)}></div>`,
|
||||
)
|
||||
},
|
||||
}),
|
||||
)
|
||||
expect(renderString).toBe(`<div id="foo"></div>`)
|
||||
expect(exposeVars).toBe('foo')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -179,4 +179,94 @@ describe('ssr: scopedId runtime behavior', () => {
|
|||
const result = await renderToString(createApp(Comp)) // output: `<div></div>`
|
||||
expect(result).toBe(`<div parent></div>`)
|
||||
})
|
||||
|
||||
// #6093
|
||||
test(':slotted on forwarded slots on component', async () => {
|
||||
const Wrapper = {
|
||||
__scopeId: 'wrapper',
|
||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||
// <div class="wrapper"><slot/></div>
|
||||
push(
|
||||
`<div${ssrRenderAttrs(
|
||||
mergeProps({ class: 'wrapper' }, attrs),
|
||||
)} wrapper>`,
|
||||
)
|
||||
ssrRenderSlot(
|
||||
ctx.$slots,
|
||||
'default',
|
||||
{},
|
||||
null,
|
||||
push,
|
||||
parent,
|
||||
'wrapper-s',
|
||||
)
|
||||
push(`</div>`)
|
||||
},
|
||||
}
|
||||
|
||||
const Slotted = {
|
||||
__scopeId: 'slotted',
|
||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||
// <Wrapper><slot/></Wrapper>
|
||||
push(
|
||||
ssrRenderComponent(
|
||||
Wrapper,
|
||||
attrs,
|
||||
{
|
||||
default: withCtx(
|
||||
(_: any, push: any, parent: any, scopeId: string) => {
|
||||
ssrRenderSlot(
|
||||
ctx.$slots,
|
||||
'default',
|
||||
{},
|
||||
null,
|
||||
push,
|
||||
parent,
|
||||
'slotted-s' + scopeId,
|
||||
)
|
||||
},
|
||||
),
|
||||
_: 1,
|
||||
} as any,
|
||||
parent,
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const Child = {
|
||||
ssrRender: (ctx: any, push: any, parent: any, attrs: any) => {
|
||||
push(`<div${ssrRenderAttrs(attrs)}></div>`)
|
||||
},
|
||||
}
|
||||
|
||||
const Root = {
|
||||
__scopeId: 'root',
|
||||
// <Slotted><Child></Child></Slotted>
|
||||
ssrRender: (_: any, push: any, parent: any, attrs: any) => {
|
||||
push(
|
||||
ssrRenderComponent(
|
||||
Slotted,
|
||||
attrs,
|
||||
{
|
||||
default: withCtx(
|
||||
(_: any, push: any, parent: any, scopeId: string) => {
|
||||
push(ssrRenderComponent(Child, null, null, parent, scopeId))
|
||||
},
|
||||
),
|
||||
_: 1,
|
||||
} as any,
|
||||
parent,
|
||||
),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
const result = await renderToString(createApp(Root))
|
||||
expect(result).toBe(
|
||||
`<div class="wrapper" root slotted wrapper>` +
|
||||
`<!--[--><!--[--><div root slotted-s wrapper-s></div><!--]--><!--]-->` +
|
||||
`</div>`,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue