mirror of https://github.com/vuejs/core.git
chore: Merge branch 'vapor' into edison/feat/vaporKeepAlive
This commit is contained in:
commit
a9d1053f17
|
@ -290,27 +290,39 @@ This is made possible via several configurations:
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart LR
|
flowchart LR
|
||||||
|
vue["vue"]
|
||||||
compiler-sfc["@vue/compiler-sfc"]
|
compiler-sfc["@vue/compiler-sfc"]
|
||||||
compiler-dom["@vue/compiler-dom"]
|
compiler-dom["@vue/compiler-dom"]
|
||||||
|
compiler-vapor["@vue/compiler-vapor"]
|
||||||
compiler-core["@vue/compiler-core"]
|
compiler-core["@vue/compiler-core"]
|
||||||
vue["vue"]
|
|
||||||
runtime-dom["@vue/runtime-dom"]
|
runtime-dom["@vue/runtime-dom"]
|
||||||
|
runtime-vapor["@vue/runtime-vapor"]
|
||||||
runtime-core["@vue/runtime-core"]
|
runtime-core["@vue/runtime-core"]
|
||||||
reactivity["@vue/reactivity"]
|
reactivity["@vue/reactivity"]
|
||||||
|
|
||||||
subgraph "Runtime Packages"
|
subgraph "Runtime Packages"
|
||||||
runtime-dom --> runtime-core
|
runtime-dom --> runtime-core
|
||||||
|
runtime-vapor --> runtime-core
|
||||||
runtime-core --> reactivity
|
runtime-core --> reactivity
|
||||||
end
|
end
|
||||||
|
|
||||||
subgraph "Compiler Packages"
|
subgraph "Compiler Packages"
|
||||||
compiler-sfc --> compiler-core
|
compiler-sfc --> compiler-core
|
||||||
compiler-sfc --> compiler-dom
|
compiler-sfc --> compiler-dom
|
||||||
|
compiler-sfc --> compiler-vapor
|
||||||
compiler-dom --> compiler-core
|
compiler-dom --> compiler-core
|
||||||
|
compiler-vapor --> compiler-core
|
||||||
end
|
end
|
||||||
|
|
||||||
|
vue --> compiler-sfc
|
||||||
vue ---> compiler-dom
|
vue ---> compiler-dom
|
||||||
vue --> runtime-dom
|
vue --> runtime-dom
|
||||||
|
vue --> compiler-vapor
|
||||||
|
vue --> runtime-vapor
|
||||||
|
|
||||||
|
%% Highlight class
|
||||||
|
classDef highlight stroke:#35eb9a,stroke-width:3px;
|
||||||
|
class compiler-vapor,runtime-vapor highlight;
|
||||||
```
|
```
|
||||||
|
|
||||||
There are some rules to follow when importing across package boundaries:
|
There are some rules to follow when importing across package boundaries:
|
||||||
|
|
40
CHANGELOG.md
40
CHANGELOG.md
|
@ -1,3 +1,41 @@
|
||||||
|
## [3.5.14](https://github.com/vuejs/core/compare/v3.5.13...v3.5.14) (2025-05-15)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **compat:** correct deprecation message for v-bind.sync usage ([#13137](https://github.com/vuejs/core/issues/13137)) ([466b30f](https://github.com/vuejs/core/commit/466b30f4049ec89fb282624ec17d1a93472ab93f)), closes [#13133](https://github.com/vuejs/core/issues/13133)
|
||||||
|
* **compiler-core:** remove slot cache from parent renderCache during unmounting ([#13215](https://github.com/vuejs/core/issues/13215)) ([5d166f3](https://github.com/vuejs/core/commit/5d166f3796a03a497435fc079c6a83a4e9c6cf52))
|
||||||
|
* **compiler-sfc:** fix scope handling for props destructure in function parameters and catch clauses ([8e34357](https://github.com/vuejs/core/commit/8e3435779a667de485cf9efd78667d0ca14c5f84)), closes [#12790](https://github.com/vuejs/core/issues/12790)
|
||||||
|
* **compiler-sfc:** treat the return value of `useTemplateRef` as a definite ref ([#13197](https://github.com/vuejs/core/issues/13197)) ([8ae1122](https://github.com/vuejs/core/commit/8ae11226e8ee938615e17c7b81dc38ae3f7cefb9))
|
||||||
|
* **compiler:** fix spelling error in domTagConfig ([#13043](https://github.com/vuejs/core/issues/13043)) ([388295b](https://github.com/vuejs/core/commit/388295b27f3cc69eba25d325bbe60a36a3df831a))
|
||||||
|
* **customFormatter:** properly accessing ref value during debugger ([#12948](https://github.com/vuejs/core/issues/12948)) ([fdbd026](https://github.com/vuejs/core/commit/fdbd02658301dd794fe0c84f0018d080a07fca9f))
|
||||||
|
* **hmr/teleport:** adjust static children traversal for HMR in dev mode ([#12819](https://github.com/vuejs/core/issues/12819)) ([5e37dd0](https://github.com/vuejs/core/commit/5e37dd009562bcd8080a200c32abde2d6e4f0305)), closes [#12816](https://github.com/vuejs/core/issues/12816)
|
||||||
|
* **hmr:** avoid hydration for hmr root reload ([#12450](https://github.com/vuejs/core/issues/12450)) ([1f98a9c](https://github.com/vuejs/core/commit/1f98a9c493d01c21befa90107f0593bc92a58932)), closes [vitejs/vite-plugin-vue#146](https://github.com/vitejs/vite-plugin-vue/issues/146) [vitejs/vite-plugin-vue#477](https://github.com/vitejs/vite-plugin-vue/issues/477)
|
||||||
|
* **hmr:** avoid hydration for hmr updating ([#12262](https://github.com/vuejs/core/issues/12262)) ([9c4dbbc](https://github.com/vuejs/core/commit/9c4dbbc5185125835ad3e49baba303bd54676111)), closes [#7706](https://github.com/vuejs/core/issues/7706) [#8170](https://github.com/vuejs/core/issues/8170)
|
||||||
|
* **reactivity:** ensure markRaw objects are not reactive ([#12824](https://github.com/vuejs/core/issues/12824)) ([295b5ec](https://github.com/vuejs/core/commit/295b5ec19b6a52c4a56652cc4d6e93a4ea7c14ed)), closes [#12807](https://github.com/vuejs/core/issues/12807)
|
||||||
|
* **reactivity:** ensure multiple effectScope on() and off() calls maintains correct active scope ([22dcbf3](https://github.com/vuejs/core/commit/22dcbf3e20eb84f69c8952f6f70d9990136a4a68)), closes [#12631](https://github.com/vuejs/core/issues/12631) [#12632](https://github.com/vuejs/core/issues/12632) [#12641](https://github.com/vuejs/core/issues/12641)
|
||||||
|
* **reactivity:** should not recompute if computed does not track reactive data ([#12341](https://github.com/vuejs/core/issues/12341)) ([0b23fd2](https://github.com/vuejs/core/commit/0b23fd23833cf085e7e112bf4435cfc9b360d072)), closes [#12337](https://github.com/vuejs/core/issues/12337)
|
||||||
|
* **runtime-core:** stop tracking deps in setRef during unmount ([#13210](https://github.com/vuejs/core/issues/13210)) ([016c472](https://github.com/vuejs/core/commit/016c472bd2e7604b21c69dee1da8545ce26e4d2f))
|
||||||
|
* **runtime-core:** update __vnode of static nodes when patching along the optimized path ([#13223](https://github.com/vuejs/core/issues/13223)) ([b3ecee3](https://github.com/vuejs/core/commit/b3ecee3da8ed5c55dea89ce6b4b376b2b722b018))
|
||||||
|
* **runtime-core:** inherit comment nodes during block patch in production build ([#10748](https://github.com/vuejs/core/issues/10748)) ([6264505](https://github.com/vuejs/core/commit/626450590d81f79117b34d2a73073b1dc8f551bd)), closes [#10747](https://github.com/vuejs/core/issues/10747) [#12650](https://github.com/vuejs/core/issues/12650)
|
||||||
|
* **runtime-core:** prevent unmounted vnode from being inserted during transition leave ([#12862](https://github.com/vuejs/core/issues/12862)) ([d6a6ec1](https://github.com/vuejs/core/commit/d6a6ec13ce521683bfb2a22932778ef7b51f8600)), closes [#12860](https://github.com/vuejs/core/issues/12860)
|
||||||
|
* **runtime-core:** respect immutability for readonly reactive arrays in `v-for` ([#13091](https://github.com/vuejs/core/issues/13091)) ([3f27c58](https://github.com/vuejs/core/commit/3f27c58ffbd4309df369bc89493fdc284dc540bb)), closes [#13087](https://github.com/vuejs/core/issues/13087)
|
||||||
|
* **runtime-dom:** always treat autocorrect as attribute ([#13001](https://github.com/vuejs/core/issues/13001)) ([1499135](https://github.com/vuejs/core/commit/1499135c227236e037bb746beeb777941b0b58ff)), closes [#5705](https://github.com/vuejs/core/issues/5705)
|
||||||
|
* **slots:** properly warn if slot invoked in setup ([#12195](https://github.com/vuejs/core/issues/12195)) ([9196222](https://github.com/vuejs/core/commit/9196222ae1d63b52b35ac5fbf5e71494587ccf05)), closes [#12194](https://github.com/vuejs/core/issues/12194)
|
||||||
|
* **ssr:** properly init slots during ssr rendering ([#12441](https://github.com/vuejs/core/issues/12441)) ([2206cd2](https://github.com/vuejs/core/commit/2206cd235a1627c540e795e378b7564a55b47313)), closes [#12438](https://github.com/vuejs/core/issues/12438)
|
||||||
|
* **transition:** fix KeepAlive with transition out-in mode behavior in production ([#12468](https://github.com/vuejs/core/issues/12468)) ([343c891](https://github.com/vuejs/core/commit/343c89122448719bd6ed6bd9de986dfb2721d6bf)), closes [#12465](https://github.com/vuejs/core/issues/12465)
|
||||||
|
* **TransitionGroup:** reset prevChildren to prevent memory leak ([#13183](https://github.com/vuejs/core/issues/13183)) ([8b848cb](https://github.com/vuejs/core/commit/8b848cbbd2af337d23e19e202f9ab433f8580855)), closes [#13181](https://github.com/vuejs/core/issues/13181)
|
||||||
|
* **types:** allow return any for Options API lifecycle hooks ([#5914](https://github.com/vuejs/core/issues/5914)) ([06310e8](https://github.com/vuejs/core/commit/06310e82f5bed62d1b9733dcb18cd8d6edc988de))
|
||||||
|
* **types:** the directive's modifiers should be optional ([#12605](https://github.com/vuejs/core/issues/12605)) ([10e54dc](https://github.com/vuejs/core/commit/10e54dcc86a7967f3196d96200bcbd1d3d42082f))
|
||||||
|
* **typos:** fix comments referencing transformElement.ts ([#12551](https://github.com/vuejs/core/issues/12551))[ci-skip] ([11c053a](https://github.com/vuejs/core/commit/11c053a5429ad0d27a0e2c78b6b026ea00ace116))
|
||||||
|
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
* **types:** add type TemplateRef ([#12645](https://github.com/vuejs/core/issues/12645)) ([636a861](https://github.com/vuejs/core/commit/636a8619f06c71dfd79f7f6412fd130c4f84226f))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## [3.5.13](https://github.com/vuejs/core/compare/v3.5.12...v3.5.13) (2024-11-15)
|
## [3.5.13](https://github.com/vuejs/core/compare/v3.5.12...v3.5.13) (2024-11-15)
|
||||||
|
|
||||||
|
|
||||||
|
@ -8,7 +46,7 @@
|
||||||
* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
|
* **custom-element:** avoid triggering mutationObserver when relecting props ([352bc88](https://github.com/vuejs/core/commit/352bc88c1bd2fda09c61ab17ea1a5967ffcd7bc0)), closes [#12214](https://github.com/vuejs/core/issues/12214) [#12215](https://github.com/vuejs/core/issues/12215)
|
||||||
* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
|
* **deps:** update dependency postcss to ^8.4.48 ([#12356](https://github.com/vuejs/core/issues/12356)) ([b5ff930](https://github.com/vuejs/core/commit/b5ff930089985a58c3553977ef999cec2a6708a4))
|
||||||
* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
|
* **hydration:** the component vnode's el should be updated when a mismatch occurs. ([#12255](https://github.com/vuejs/core/issues/12255)) ([a20a4cb](https://github.com/vuejs/core/commit/a20a4cb36a3e717d1f8f259d0d59f133f508ff0a)), closes [#12253](https://github.com/vuejs/core/issues/12253)
|
||||||
* **reactiivty:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
|
* **reactivity:** avoid unnecessary watcher effect removal from inactive scope ([2193284](https://github.com/vuejs/core/commit/21932840eae72ffcd357a62ec596aaecc7ec224a)), closes [#5783](https://github.com/vuejs/core/issues/5783) [#5806](https://github.com/vuejs/core/issues/5806)
|
||||||
* **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370)
|
* **reactivity:** release nested effects/scopes on effect scope stop ([#12373](https://github.com/vuejs/core/issues/12373)) ([bee2f5e](https://github.com/vuejs/core/commit/bee2f5ee62dc0cd04123b737779550726374dd0a)), closes [#12370](https://github.com/vuejs/core/issues/12370)
|
||||||
* **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533)
|
* **runtime-dom:** set css vars before user onMounted hooks ([2d5c5e2](https://github.com/vuejs/core/commit/2d5c5e25e9b7a56e883674fb434135ac514429b5)), closes [#11533](https://github.com/vuejs/core/issues/11533)
|
||||||
* **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6))
|
* **runtime-dom:** set css vars on update to handle child forcing reflow in onMount ([#11561](https://github.com/vuejs/core/issues/11561)) ([c4312f9](https://github.com/vuejs/core/commit/c4312f9c715c131a09e552ba46e9beb4b36d55e6))
|
||||||
|
|
|
@ -34,7 +34,8 @@ Please make sure to respect issue requirements and use [the new issue helper](ht
|
||||||
|
|
||||||
## Stay In Touch
|
## Stay In Touch
|
||||||
|
|
||||||
- [Twitter](https://twitter.com/vuejs)
|
- [X](https://x.com/vuejs)
|
||||||
|
- [Bluesky](https://bsky.app/profile/vuejs.org)
|
||||||
- [Blog](https://blog.vuejs.org/)
|
- [Blog](https://blog.vuejs.org/)
|
||||||
- [Job Board](https://vuejobs.com/?ref=vuejs)
|
- [Job Board](https://vuejobs.com/?ref=vuejs)
|
||||||
|
|
||||||
|
|
51
package.json
51
package.json
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"packageManager": "pnpm@10.6.5",
|
"packageManager": "pnpm@10.9.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "node scripts/dev.js",
|
"dev": "node scripts/dev.js",
|
||||||
|
@ -69,24 +69,24 @@
|
||||||
"@rollup/plugin-json": "^6.1.0",
|
"@rollup/plugin-json": "^6.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^16.0.1",
|
"@rollup/plugin-node-resolve": "^16.0.1",
|
||||||
"@rollup/plugin-replace": "5.0.4",
|
"@rollup/plugin-replace": "5.0.4",
|
||||||
"@swc/core": "^1.11.12",
|
"@swc/core": "^1.11.24",
|
||||||
"@types/hash-sum": "^1.0.2",
|
"@types/hash-sum": "^1.0.2",
|
||||||
"@types/node": "^22.13.13",
|
"@types/node": "^22.14.1",
|
||||||
"@types/semver": "^7.5.8",
|
"@types/semver": "^7.7.0",
|
||||||
"@types/serve-handler": "^6.1.4",
|
"@types/serve-handler": "^6.1.4",
|
||||||
"@vitest/ui": "^3.0.2",
|
"@vitest/ui": "^3.0.2",
|
||||||
"@vitest/coverage-v8": "^3.0.9",
|
"@vitest/coverage-v8": "^3.1.3",
|
||||||
"@vitest/eslint-plugin": "^1.1.38",
|
"@vitest/eslint-plugin": "^1.1.44",
|
||||||
"@vue/consolidate": "1.0.0",
|
"@vue/consolidate": "1.0.0",
|
||||||
"conventional-changelog-cli": "^5.0.0",
|
"conventional-changelog-cli": "^5.0.0",
|
||||||
"enquirer": "^2.4.1",
|
"enquirer": "^2.4.1",
|
||||||
"esbuild": "^0.25.1",
|
"esbuild": "^0.25.4",
|
||||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||||
"eslint": "^9.23.0",
|
"eslint": "^9.25.1",
|
||||||
"eslint-plugin-import-x": "^4.9.1",
|
"eslint-plugin-import-x": "^4.11.0",
|
||||||
"estree-walker": "catalog:",
|
"estree-walker": "catalog:",
|
||||||
"jsdom": "^26.0.0",
|
"jsdom": "^26.1.0",
|
||||||
"lint-staged": "^15.5.0",
|
"lint-staged": "^15.5.1",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"magic-string": "^0.30.17",
|
"magic-string": "^0.30.17",
|
||||||
"markdown-table": "^3.0.4",
|
"markdown-table": "^3.0.4",
|
||||||
|
@ -96,38 +96,21 @@
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
"pretty-bytes": "^6.1.1",
|
"pretty-bytes": "^6.1.1",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"puppeteer": "~24.4.0",
|
"puppeteer": "~24.8.2",
|
||||||
"rimraf": "^6.0.1",
|
"rimraf": "^6.0.1",
|
||||||
"rollup": "^4.37.0",
|
"rollup": "^4.40.2",
|
||||||
"rollup-plugin-dts": "^6.2.1",
|
"rollup-plugin-dts": "^6.2.1",
|
||||||
"rollup-plugin-esbuild": "^6.2.1",
|
"rollup-plugin-esbuild": "^6.2.1",
|
||||||
"rollup-plugin-polyfill-node": "^0.13.0",
|
"rollup-plugin-polyfill-node": "^0.13.0",
|
||||||
"semver": "^7.7.1",
|
"semver": "^7.7.1",
|
||||||
"serve": "^14.2.4",
|
"serve": "^14.2.4",
|
||||||
"serve-handler": "^6.1.6",
|
"serve-handler": "^6.1.6",
|
||||||
"simple-git-hooks": "^2.12.1",
|
"simple-git-hooks": "^2.13.0",
|
||||||
"todomvc-app-css": "^2.4.3",
|
"todomvc-app-css": "^2.4.3",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"typescript-eslint": "^8.27.0",
|
"typescript-eslint": "^8.31.1",
|
||||||
"vite": "catalog:",
|
"vite": "catalog:",
|
||||||
"vitest": "^3.0.9"
|
"vitest": "^3.1.3"
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"peerDependencyRules": {
|
|
||||||
"allowedVersions": {
|
|
||||||
"typescript-eslint>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/eslint-plugin>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/parser>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/type-utils>eslint": "^9.0.0",
|
|
||||||
"@typescript-eslint/utils>eslint": "^9.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"onlyBuiltDependencies": [
|
|
||||||
"@swc/core",
|
|
||||||
"esbuild",
|
|
||||||
"puppeteer",
|
|
||||||
"simple-git-hooks"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,5 +4,5 @@
|
||||||
"isolatedDeclarations": false,
|
"isolatedDeclarations": false,
|
||||||
"allowJs": true
|
"allowJs": true
|
||||||
},
|
},
|
||||||
"include": ["./**/*", "../packages/*/src"]
|
"include": ["./**/*", "../../packages/*/src"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"vue": "latest"
|
"vue": "latest"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.2.3",
|
"@vitejs/plugin-vue": "^5.2.4",
|
||||||
"vite": "^6.2.2"
|
"vite": "^6.3.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -170,6 +170,11 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_PROPERTY,
|
||||||
|
key: { content: '__' },
|
||||||
|
value: { content: '[0]' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -197,6 +202,11 @@ describe('compiler: cacheStatic transform', () => {
|
||||||
{
|
{
|
||||||
/* _ slot flag */
|
/* _ slot flag */
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: NodeTypes.JS_PROPERTY,
|
||||||
|
key: { content: '__' },
|
||||||
|
value: { content: '[0]' },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-core",
|
"name": "@vue/compiler-core",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-core",
|
"description": "@vue/compiler-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-core.esm-bundler.js",
|
"module": "dist/compiler-core.esm-bundler.js",
|
||||||
|
|
|
@ -17,6 +17,7 @@ export {
|
||||||
createTransformContext,
|
createTransformContext,
|
||||||
traverseNode,
|
traverseNode,
|
||||||
createStructuralDirectiveTransform,
|
createStructuralDirectiveTransform,
|
||||||
|
getSelfName,
|
||||||
type NodeTransform,
|
type NodeTransform,
|
||||||
type StructuralDirectiveTransform,
|
type StructuralDirectiveTransform,
|
||||||
type DirectiveTransform,
|
type DirectiveTransform,
|
||||||
|
|
|
@ -388,7 +388,7 @@ const tokenizer = new Tokenizer(stack, {
|
||||||
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
CompilerDeprecationTypes.COMPILER_V_BIND_SYNC,
|
||||||
currentOptions,
|
currentOptions,
|
||||||
currentProp.loc,
|
currentProp.loc,
|
||||||
currentProp.rawName,
|
currentProp.arg!.loc.source,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
currentProp.name = 'model'
|
currentProp.name = 'model'
|
||||||
|
|
|
@ -123,6 +123,11 @@ export interface TransformContext
|
||||||
filters?: Set<string>
|
filters?: Set<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getSelfName(filename: string): string | null {
|
||||||
|
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
|
||||||
|
return nameMatch ? capitalize(camelize(nameMatch[1])) : null
|
||||||
|
}
|
||||||
|
|
||||||
export function createTransformContext(
|
export function createTransformContext(
|
||||||
root: RootNode,
|
root: RootNode,
|
||||||
{
|
{
|
||||||
|
@ -150,11 +155,10 @@ export function createTransformContext(
|
||||||
compatConfig,
|
compatConfig,
|
||||||
}: TransformOptions,
|
}: TransformOptions,
|
||||||
): TransformContext {
|
): TransformContext {
|
||||||
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
|
|
||||||
const context: TransformContext = {
|
const context: TransformContext = {
|
||||||
// options
|
// options
|
||||||
filename,
|
filename,
|
||||||
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
|
selfName: getSelfName(filename),
|
||||||
prefixIdentifiers,
|
prefixIdentifiers,
|
||||||
hoistStatic,
|
hoistStatic,
|
||||||
hmr,
|
hmr,
|
||||||
|
|
|
@ -12,11 +12,14 @@ import {
|
||||||
type RootNode,
|
type RootNode,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
type SlotFunctionExpression,
|
type SlotFunctionExpression,
|
||||||
|
type SlotsObjectProperty,
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
type TemplateNode,
|
type TemplateNode,
|
||||||
type TextCallNode,
|
type TextCallNode,
|
||||||
type VNodeCall,
|
type VNodeCall,
|
||||||
createArrayExpression,
|
createArrayExpression,
|
||||||
|
createObjectProperty,
|
||||||
|
createSimpleExpression,
|
||||||
getVNodeBlockHelper,
|
getVNodeBlockHelper,
|
||||||
getVNodeHelper,
|
getVNodeHelper,
|
||||||
} from '../ast'
|
} from '../ast'
|
||||||
|
@ -140,6 +143,7 @@ function walk(
|
||||||
}
|
}
|
||||||
|
|
||||||
let cachedAsArray = false
|
let cachedAsArray = false
|
||||||
|
const slotCacheKeys = []
|
||||||
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
if (toCache.length === children.length && node.type === NodeTypes.ELEMENT) {
|
||||||
if (
|
if (
|
||||||
node.tagType === ElementTypes.ELEMENT &&
|
node.tagType === ElementTypes.ELEMENT &&
|
||||||
|
@ -163,6 +167,7 @@ function walk(
|
||||||
// default slot
|
// default slot
|
||||||
const slot = getSlotNode(node.codegenNode, 'default')
|
const slot = getSlotNode(node.codegenNode, 'default')
|
||||||
if (slot) {
|
if (slot) {
|
||||||
|
slotCacheKeys.push(context.cached.length)
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -186,6 +191,7 @@ function walk(
|
||||||
slotName.arg &&
|
slotName.arg &&
|
||||||
getSlotNode(parent.codegenNode, slotName.arg)
|
getSlotNode(parent.codegenNode, slotName.arg)
|
||||||
if (slot) {
|
if (slot) {
|
||||||
|
slotCacheKeys.push(context.cached.length)
|
||||||
slot.returns = getCacheExpression(
|
slot.returns = getCacheExpression(
|
||||||
createArrayExpression(slot.returns as TemplateChildNode[]),
|
createArrayExpression(slot.returns as TemplateChildNode[]),
|
||||||
)
|
)
|
||||||
|
@ -196,10 +202,31 @@ function walk(
|
||||||
|
|
||||||
if (!cachedAsArray) {
|
if (!cachedAsArray) {
|
||||||
for (const child of toCache) {
|
for (const child of toCache) {
|
||||||
|
slotCacheKeys.push(context.cached.length)
|
||||||
child.codegenNode = context.cache(child.codegenNode!)
|
child.codegenNode = context.cache(child.codegenNode!)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// put the slot cached keys on the slot object, so that the cache
|
||||||
|
// can be removed when component unmounting to prevent memory leaks
|
||||||
|
if (
|
||||||
|
slotCacheKeys.length &&
|
||||||
|
node.type === NodeTypes.ELEMENT &&
|
||||||
|
node.tagType === ElementTypes.COMPONENT &&
|
||||||
|
node.codegenNode &&
|
||||||
|
node.codegenNode.type === NodeTypes.VNODE_CALL &&
|
||||||
|
node.codegenNode.children &&
|
||||||
|
!isArray(node.codegenNode.children) &&
|
||||||
|
node.codegenNode.children.type === NodeTypes.JS_OBJECT_EXPRESSION
|
||||||
|
) {
|
||||||
|
node.codegenNode.children.properties.push(
|
||||||
|
createObjectProperty(
|
||||||
|
`__`,
|
||||||
|
createSimpleExpression(JSON.stringify(slotCacheKeys), false),
|
||||||
|
) as SlotsObjectProperty,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
function getCacheExpression(value: JSChildNode): CacheExpression {
|
function getCacheExpression(value: JSChildNode): CacheExpression {
|
||||||
const exp = context.cache(value)
|
const exp = context.cache(value)
|
||||||
// #6978, #7138, #7114
|
// #6978, #7138, #7114
|
||||||
|
|
|
@ -342,7 +342,6 @@ export function buildSlots(
|
||||||
: hasForwardedSlots(node.children)
|
: hasForwardedSlots(node.children)
|
||||||
? SlotFlags.FORWARDED
|
? SlotFlags.FORWARDED
|
||||||
: SlotFlags.STABLE
|
: SlotFlags.STABLE
|
||||||
|
|
||||||
let slots = createObjectExpression(
|
let slots = createObjectExpression(
|
||||||
slotsProperties.concat(
|
slotsProperties.concat(
|
||||||
createObjectProperty(
|
createObjectProperty(
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-dom",
|
"name": "@vue/compiler-dom",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-dom",
|
"description": "@vue/compiler-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/compiler-dom.esm-bundler.js",
|
"module": "dist/compiler-dom.esm-bundler.js",
|
||||||
|
|
|
@ -196,6 +196,25 @@ return () => {}
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`sfc reactive props destructure > handle function parameters with same name as destructured props 1`] = `
|
||||||
|
"
|
||||||
|
export default {
|
||||||
|
setup(__props) {
|
||||||
|
|
||||||
|
|
||||||
|
function test(value) {
|
||||||
|
try {
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(__props.value)
|
||||||
|
|
||||||
|
return () => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
exports[`sfc reactive props destructure > multi-variable declaration 1`] = `
|
||||||
"
|
"
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -360,6 +360,22 @@ describe('sfc reactive props destructure', () => {
|
||||||
expect(content).toMatch(`props: ['item'],`)
|
expect(content).toMatch(`props: ['item'],`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('handle function parameters with same name as destructured props', () => {
|
||||||
|
const { content } = compile(`
|
||||||
|
<script setup>
|
||||||
|
const { value } = defineProps()
|
||||||
|
function test(value) {
|
||||||
|
try {
|
||||||
|
} catch {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(value)
|
||||||
|
</script>
|
||||||
|
`)
|
||||||
|
assertCode(content)
|
||||||
|
expect(content).toMatch(`console.log(__props.value)`)
|
||||||
|
})
|
||||||
|
|
||||||
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
|
test('defineProps/defineEmits in multi-variable declaration (full removal)', () => {
|
||||||
const { content } = compile(`
|
const { content } = compile(`
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|
|
@ -211,38 +211,42 @@ color: red
|
||||||
expect(
|
expect(
|
||||||
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
|
compileScoped(`.div { color: red; } .div:where(:hover) { color: blue; }`),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:where(:hover) { color: blue;
|
.div[data-v-test]:where(:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
|
compileScoped(`.div { color: red; } .div:is(:hover) { color: blue; }`),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:is(:hover) { color: blue;
|
.div[data-v-test]:is(:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileScoped(
|
compileScoped(
|
||||||
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
|
`.div { color: red; } .div:where(.foo:hover) { color: blue; }`,
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:where(.foo:hover) { color: blue;
|
.div[data-v-test]:where(.foo:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
compileScoped(
|
compileScoped(
|
||||||
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
|
`.div { color: red; } .div:is(.foo:hover) { color: blue; }`,
|
||||||
),
|
),
|
||||||
).toMatchInlineSnapshot(`
|
).toMatchInlineSnapshot(`
|
||||||
".div[data-v-test] { color: red;
|
".div[data-v-test] { color: red;
|
||||||
}
|
}
|
||||||
.div[data-v-test]:is(.foo:hover) { color: blue;
|
.div[data-v-test]:is(.foo:hover) { color: blue;
|
||||||
}"`)
|
}"
|
||||||
|
`)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('media query', () => {
|
test('media query', () => {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-sfc",
|
"name": "@vue/compiler-sfc",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-sfc",
|
"description": "@vue/compiler-sfc",
|
||||||
"main": "dist/compiler-sfc.cjs.js",
|
"main": "dist/compiler-sfc.cjs.js",
|
||||||
"module": "dist/compiler-sfc.esm-browser.js",
|
"module": "dist/compiler-sfc.esm-browser.js",
|
||||||
|
@ -63,6 +63,6 @@
|
||||||
"postcss-modules": "^6.0.1",
|
"postcss-modules": "^6.0.1",
|
||||||
"postcss-selector-parser": "^7.1.0",
|
"postcss-selector-parser": "^7.1.0",
|
||||||
"pug": "^3.0.3",
|
"pug": "^3.0.3",
|
||||||
"sass": "^1.86.0"
|
"sass": "^1.86.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1120,6 +1120,7 @@ function walkDeclaration(
|
||||||
m === userImportAliases['shallowRef'] ||
|
m === userImportAliases['shallowRef'] ||
|
||||||
m === userImportAliases['customRef'] ||
|
m === userImportAliases['customRef'] ||
|
||||||
m === userImportAliases['toRef'] ||
|
m === userImportAliases['toRef'] ||
|
||||||
|
m === userImportAliases['useTemplateRef'] ||
|
||||||
m === DEFINE_MODEL,
|
m === DEFINE_MODEL,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -291,7 +291,8 @@ export function transformDestructuredProps(
|
||||||
parent && parentStack.pop()
|
parent && parentStack.pop()
|
||||||
if (
|
if (
|
||||||
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
(node.type === 'BlockStatement' && !isFunctionType(parent!)) ||
|
||||||
isFunctionType(node)
|
isFunctionType(node) ||
|
||||||
|
node.type === 'CatchClause'
|
||||||
) {
|
) {
|
||||||
popScope()
|
popScope()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compiler-ssr",
|
"name": "@vue/compiler-ssr",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/compiler-ssr",
|
"description": "@vue/compiler-ssr",
|
||||||
"main": "dist/compiler-ssr.cjs.js",
|
"main": "dist/compiler-ssr.cjs.js",
|
||||||
"types": "dist/compiler-ssr.d.ts",
|
"types": "dist/compiler-ssr.d.ts",
|
||||||
|
|
|
@ -149,7 +149,7 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
|
exports[`compile > directives > v-pre > should not affect siblings after it 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
|
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
|
const t0 = _template("<div :id=\\"foo\\"><Comp></Comp>{{ bar }}</div>")
|
||||||
const t1 = _template("<div> </div>")
|
const t1 = _template("<div> </div>")
|
||||||
|
|
||||||
|
@ -157,12 +157,12 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
const _component_Comp = _resolveComponent("Comp")
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
const n3 = t1()
|
const n3 = t1()
|
||||||
|
const n2 = _child(n3)
|
||||||
_setInsertionState(n3, 0)
|
_setInsertionState(n3, 0)
|
||||||
const n1 = _createComponentWithFallback(_component_Comp)
|
const n1 = _createComponentWithFallback(_component_Comp)
|
||||||
const n2 = _child(n3)
|
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
_setText(n2, _toDisplayString(_ctx.bar))
|
|
||||||
_setProp(n3, "id", _ctx.foo)
|
_setProp(n3, "id", _ctx.foo)
|
||||||
|
_setText(n2, _toDisplayString(_ctx.bar))
|
||||||
})
|
})
|
||||||
return [n0, n3]
|
return [n0, n3]
|
||||||
}"
|
}"
|
||||||
|
@ -180,7 +180,7 @@ export function render(_ctx) {
|
||||||
`;
|
`;
|
||||||
|
|
||||||
exports[`compile > dynamic root nodes and interpolation 1`] = `
|
exports[`compile > dynamic root nodes and interpolation 1`] = `
|
||||||
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, setProp as _setProp, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue';
|
"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, delegateEvents as _delegateEvents, template as _template } from 'vue';
|
||||||
const t0 = _template("<button> </button>", true)
|
const t0 = _template("<button> </button>", true)
|
||||||
_delegateEvents("click")
|
_delegateEvents("click")
|
||||||
|
|
||||||
|
@ -190,13 +190,47 @@ export function render(_ctx) {
|
||||||
n0.$evtclick = e => _ctx.handleClick(e)
|
n0.$evtclick = e => _ctx.handleClick(e)
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
const _count = _ctx.count
|
const _count = _ctx.count
|
||||||
_setText(x0, _toDisplayString(_count) + "foo" + _toDisplayString(_count) + "foo" + _toDisplayString(_count))
|
|
||||||
_setProp(n0, "id", _count)
|
_setProp(n0, "id", _count)
|
||||||
|
_setText(x0, _toDisplayString(_count) + "foo" + _toDisplayString(_count) + "foo" + _toDisplayString(_count))
|
||||||
})
|
})
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compile > execution order > basic 1`] = `
|
||||||
|
"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div> </div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const n0 = t0()
|
||||||
|
const x0 = _child(n0)
|
||||||
|
_renderEffect(() => {
|
||||||
|
_setProp(n0, "id", _ctx.foo)
|
||||||
|
_setText(x0, _toDisplayString(_ctx.bar))
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compile > execution order > with v-once 1`] = `
|
||||||
|
"import { child as _child, next as _next, nthChild as _nthChild, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div><span> </span> <br> </div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const n3 = t0()
|
||||||
|
const n0 = _child(n3)
|
||||||
|
const n1 = _next(n0)
|
||||||
|
const n2 = _nthChild(n3, 3)
|
||||||
|
const x0 = _child(n0)
|
||||||
|
_setText(x0, _toDisplayString(_ctx.foo))
|
||||||
|
_renderEffect(() => {
|
||||||
|
_setText(n1, " " + _toDisplayString(_ctx.bar))
|
||||||
|
_setText(n2, " " + _toDisplayString(_ctx.baz))
|
||||||
|
})
|
||||||
|
return n3
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compile > expression parsing > interpolation 1`] = `
|
exports[`compile > expression parsing > interpolation 1`] = `
|
||||||
"
|
"
|
||||||
const n0 = t0()
|
const n0 = t0()
|
||||||
|
@ -230,6 +264,30 @@ export function render(_ctx) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compile > setInsertionState > next, child and nthChild should be above the setInsertionState 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, child as _child, next as _next, setInsertionState as _setInsertionState, createComponentWithFallback as _createComponentWithFallback, nthChild as _nthChild, createIf as _createIf, setProp as _setProp, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div></div>")
|
||||||
|
const t1 = _template("<div><div></div><!><div></div><!><div><button></button></div></div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n6 = t1()
|
||||||
|
const n5 = _next(_child(n6))
|
||||||
|
const n7 = _nthChild(n6, 3)
|
||||||
|
const p0 = _next(n7)
|
||||||
|
const n4 = _child(p0)
|
||||||
|
_setInsertionState(n6, n5)
|
||||||
|
const n0 = _createComponentWithFallback(_component_Comp)
|
||||||
|
_setInsertionState(n6, n7)
|
||||||
|
const n1 = _createIf(() => (true), () => {
|
||||||
|
const n3 = t0()
|
||||||
|
return n3
|
||||||
|
})
|
||||||
|
_renderEffect(() => _setProp(n4, "disabled", _ctx.foo))
|
||||||
|
return n6
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compile > static + dynamic root 1`] = `
|
exports[`compile > static + dynamic root 1`] = `
|
||||||
"import { toDisplayString as _toDisplayString, setText as _setText, template as _template } from 'vue';
|
"import { toDisplayString as _toDisplayString, setText as _setText, template as _template } from 'vue';
|
||||||
const t0 = _template(" ")
|
const t0 = _template(" ")
|
||||||
|
|
|
@ -220,4 +220,46 @@ describe('compile', () => {
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('setInsertionState', () => {
|
||||||
|
test('next, child and nthChild should be above the setInsertionState', () => {
|
||||||
|
const code = compile(`
|
||||||
|
<div>
|
||||||
|
<div />
|
||||||
|
<Comp />
|
||||||
|
<div />
|
||||||
|
<div v-if="true" />
|
||||||
|
<div>
|
||||||
|
<button :disabled="foo" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('execution order', () => {
|
||||||
|
test('basic', () => {
|
||||||
|
const code = compile(`<div :id="foo">{{ bar }}</div>`)
|
||||||
|
expect(code).matchSnapshot()
|
||||||
|
expect(code).contains(
|
||||||
|
`_setProp(n0, "id", _ctx.foo)
|
||||||
|
_setText(x0, _toDisplayString(_ctx.bar))`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
test('with v-once', () => {
|
||||||
|
const code = compile(
|
||||||
|
`<div>
|
||||||
|
<span v-once>{{ foo }}</span>
|
||||||
|
{{ bar }}<br>
|
||||||
|
{{ baz }}
|
||||||
|
</div>`,
|
||||||
|
)
|
||||||
|
expect(code).matchSnapshot()
|
||||||
|
expect(code).contains(
|
||||||
|
`_setText(n1, " " + _toDisplayString(_ctx.bar))
|
||||||
|
_setText(n2, " " + _toDisplayString(_ctx.baz))`,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -32,3 +32,24 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: expression > update expression 1`] = `
|
||||||
|
"import { child as _child, setProp as _setProp, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div> </div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const n1 = t0()
|
||||||
|
const n0 = _child(n1)
|
||||||
|
const x1 = _child(n1)
|
||||||
|
_renderEffect(() => {
|
||||||
|
const _String = String
|
||||||
|
const _foo = _ctx.foo
|
||||||
|
_setProp(n1, "id", _String(_foo.id++))
|
||||||
|
_setProp(n1, "foo", _foo)
|
||||||
|
_setProp(n1, "bar", _ctx.bar++)
|
||||||
|
_setText(n0, _toDisplayString(_String(_foo.id++)) + " " + _toDisplayString(_foo) + " " + _toDisplayString(_ctx.bar))
|
||||||
|
_setText(x1, _toDisplayString(_String(_foo.id++)) + " " + _toDisplayString(_foo) + " " + _toDisplayString(_ctx.bar))
|
||||||
|
})
|
||||||
|
return n1
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
|
@ -67,7 +67,6 @@ export function render(_ctx) {
|
||||||
const x2 = _child(n2)
|
const x2 = _child(n2)
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
const _msg = _ctx.msg
|
const _msg = _ctx.msg
|
||||||
|
|
||||||
_setText(x0, _toDisplayString(_msg))
|
_setText(x0, _toDisplayString(_msg))
|
||||||
_setText(x1, _toDisplayString(_msg))
|
_setText(x1, _toDisplayString(_msg))
|
||||||
_setText(x2, _toDisplayString(_msg))
|
_setText(x2, _toDisplayString(_msg))
|
||||||
|
|
|
@ -77,6 +77,16 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: element transform > component > resolve implicitly self-referencing component 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Example__self = _resolveComponent("Example", true)
|
||||||
|
const n0 = _createComponentWithFallback(_component_Example__self, null, null, true)
|
||||||
|
return n0
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
|
exports[`compiler: element transform > component > resolve namespaced component from props bindings (inline) 1`] = `
|
||||||
"
|
"
|
||||||
const n0 = _createComponent(Foo.Example, null, null, true)
|
const n0 = _createComponent(Foo.Example, null, null, true)
|
||||||
|
@ -178,6 +188,18 @@ export function render(_ctx) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: element transform > component > v-for on component should not mark as single root 1`] = `
|
||||||
|
"import { createComponent as _createComponent, createFor as _createFor } from 'vue';
|
||||||
|
|
||||||
|
export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
|
const n0 = _createFor(() => (_ctx.items), (_for_item0) => {
|
||||||
|
const n2 = _createComponent(_ctx.Comp)
|
||||||
|
return n2
|
||||||
|
}, (item) => (item), 2)
|
||||||
|
return n0
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: element transform > component > v-on expression is a function call 1`] = `
|
exports[`compiler: element transform > component > v-on expression is a function call 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,26 @@ export function render(_ctx) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: template ref transform > function ref 1`] = `
|
||||||
|
"import { createTemplateRefSetter as _createTemplateRefSetter, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div></div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _setTemplateRef = _createTemplateRefSetter()
|
||||||
|
const n0 = t0()
|
||||||
|
let r0
|
||||||
|
_renderEffect(() => {
|
||||||
|
const _foo = _ctx.foo
|
||||||
|
r0 = _setTemplateRef(n0, bar => {
|
||||||
|
_foo.value = bar
|
||||||
|
;({ baz: _ctx.baz } = bar)
|
||||||
|
console.log(_foo.value, _ctx.baz)
|
||||||
|
}, r0)
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: template ref transform > ref + v-for 1`] = `
|
exports[`compiler: template ref transform > ref + v-for 1`] = `
|
||||||
"import { createTemplateRefSetter as _createTemplateRefSetter, createFor as _createFor, template as _template } from 'vue';
|
"import { createTemplateRefSetter as _createTemplateRefSetter, createFor as _createFor, template as _template } from 'vue';
|
||||||
const t0 = _template("<div></div>", true)
|
const t0 = _template("<div></div>", true)
|
||||||
|
|
|
@ -55,7 +55,6 @@ export function render(_ctx) {
|
||||||
const _foo = _ctx.foo
|
const _foo = _ctx.foo
|
||||||
const _bar = _ctx.bar
|
const _bar = _ctx.bar
|
||||||
const _foo_bar_baz = _foo[_bar(_ctx.baz)]
|
const _foo_bar_baz = _foo[_bar(_ctx.baz)]
|
||||||
|
|
||||||
_setProp(n0, "id", _foo_bar_baz)
|
_setProp(n0, "id", _foo_bar_baz)
|
||||||
_setProp(n1, "id", _foo_bar_baz)
|
_setProp(n1, "id", _foo_bar_baz)
|
||||||
_setProp(n2, "id", _bar() + _foo)
|
_setProp(n2, "id", _bar() + _foo)
|
||||||
|
@ -75,6 +74,17 @@ export function render(_ctx) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`cache multiple access > not cache variable in function expression 1`] = `
|
||||||
|
"import { setDynamicProps as _setDynamicProps, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div></div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const n0 = t0()
|
||||||
|
_renderEffect(() => _setDynamicProps(n0, [{ foo: bar => _ctx.foo = bar }], true))
|
||||||
|
return n0
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`cache multiple access > not cache variable only used in property shorthand 1`] = `
|
exports[`cache multiple access > not cache variable only used in property shorthand 1`] = `
|
||||||
"import { setStyle as _setStyle, renderEffect as _renderEffect, template as _template } from 'vue';
|
"import { setStyle as _setStyle, renderEffect as _renderEffect, template as _template } from 'vue';
|
||||||
const t0 = _template("<div></div>", true)
|
const t0 = _template("<div></div>", true)
|
||||||
|
@ -96,7 +106,6 @@ export function render(_ctx) {
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
const _obj = _ctx.obj
|
const _obj = _ctx.obj
|
||||||
const _obj_foo_baz_obj_bar = _obj['foo']['baz'] + _obj.bar
|
const _obj_foo_baz_obj_bar = _obj['foo']['baz'] + _obj.bar
|
||||||
|
|
||||||
_setProp(n0, "id", _obj_foo_baz_obj_bar)
|
_setProp(n0, "id", _obj_foo_baz_obj_bar)
|
||||||
_setProp(n1, "id", _obj_foo_baz_obj_bar)
|
_setProp(n1, "id", _obj_foo_baz_obj_bar)
|
||||||
})
|
})
|
||||||
|
@ -115,7 +124,6 @@ export function render(_ctx) {
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
const _foo = _ctx.foo
|
const _foo = _ctx.foo
|
||||||
const _foo_bar = _foo + _ctx.bar
|
const _foo_bar = _foo + _ctx.bar
|
||||||
|
|
||||||
_setProp(n0, "id", _foo_bar)
|
_setProp(n0, "id", _foo_bar)
|
||||||
_setProp(n1, "id", _foo_bar)
|
_setProp(n1, "id", _foo_bar)
|
||||||
_setProp(n2, "id", _foo + _foo_bar)
|
_setProp(n2, "id", _foo + _foo_bar)
|
||||||
|
@ -133,7 +141,6 @@ export function render(_ctx) {
|
||||||
const n1 = t0()
|
const n1 = t0()
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
const _foo_bar = _ctx.foo + _ctx.bar
|
const _foo_bar = _ctx.foo + _ctx.bar
|
||||||
|
|
||||||
_setProp(n0, "id", _foo_bar)
|
_setProp(n0, "id", _foo_bar)
|
||||||
_setProp(n1, "id", _foo_bar)
|
_setProp(n1, "id", _foo_bar)
|
||||||
})
|
})
|
||||||
|
@ -166,7 +173,6 @@ export function render(_ctx) {
|
||||||
const n1 = t0()
|
const n1 = t0()
|
||||||
_renderEffect(() => {
|
_renderEffect(() => {
|
||||||
const _foo = _ctx.foo
|
const _foo = _ctx.foo
|
||||||
|
|
||||||
_setClass(n0, _foo)
|
_setClass(n0, _foo)
|
||||||
_setClass(n1, _foo)
|
_setClass(n1, _foo)
|
||||||
})
|
})
|
||||||
|
@ -487,15 +493,13 @@ export function render(_ctx) {
|
||||||
_setAttr(n0, "form", _ctx.form)
|
_setAttr(n0, "form", _ctx.form)
|
||||||
_setAttr(n1, "list", _ctx.list)
|
_setAttr(n1, "list", _ctx.list)
|
||||||
_setAttr(n2, "type", _ctx.type)
|
_setAttr(n2, "type", _ctx.type)
|
||||||
|
|
||||||
_setAttr(n3, "width", _width)
|
_setAttr(n3, "width", _width)
|
||||||
_setAttr(n4, "width", _width)
|
|
||||||
_setAttr(n5, "width", _width)
|
|
||||||
_setAttr(n6, "width", _width)
|
|
||||||
|
|
||||||
_setAttr(n3, "height", _height)
|
_setAttr(n3, "height", _height)
|
||||||
|
_setAttr(n4, "width", _width)
|
||||||
_setAttr(n4, "height", _height)
|
_setAttr(n4, "height", _height)
|
||||||
|
_setAttr(n5, "width", _width)
|
||||||
_setAttr(n5, "height", _height)
|
_setAttr(n5, "height", _height)
|
||||||
|
_setAttr(n6, "width", _width)
|
||||||
_setAttr(n6, "height", _height)
|
_setAttr(n6, "height", _height)
|
||||||
})
|
})
|
||||||
return [n0, n1, n2, n3, n4, n5, n6]
|
return [n0, n1, n2, n3, n4, n5, n6]
|
||||||
|
|
|
@ -115,6 +115,21 @@ export function render(_ctx) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-for > object value, key and index 1`] = `
|
||||||
|
"import { child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div> </div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const n0 = _createFor(() => (_ctx.list), (_for_item0, _for_key0, _for_index0) => {
|
||||||
|
const n2 = t0()
|
||||||
|
const x2 = _child(n2)
|
||||||
|
_renderEffect(() => _setText(x2, _toDisplayString(_for_item0.value + _for_key0.value + _for_index0.value)))
|
||||||
|
return n2
|
||||||
|
}, (value, key, index) => (key))
|
||||||
|
return n0
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
|
exports[`compiler: v-for > v-for aliases w/ complex expressions 1`] = `
|
||||||
"import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
|
"import { getDefaultValue as _getDefaultValue, child as _child, toDisplayString as _toDisplayString, setText as _setText, renderEffect as _renderEffect, createFor as _createFor, template as _template } from 'vue';
|
||||||
const t0 = _template("<div> </div>", true)
|
const t0 = _template("<div> </div>", true)
|
||||||
|
|
|
@ -134,3 +134,29 @@ export function render(_ctx) {
|
||||||
return n0
|
return n0
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: v-if > v-if + v-if / v-else[-if] 1`] = `
|
||||||
|
"import { setInsertionState as _setInsertionState, createIf as _createIf, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<span>foo</span>")
|
||||||
|
const t1 = _template("<span>bar</span>")
|
||||||
|
const t2 = _template("<span>baz</span>")
|
||||||
|
const t3 = _template("<div></div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const n8 = t3()
|
||||||
|
_setInsertionState(n8)
|
||||||
|
const n0 = _createIf(() => (_ctx.foo), () => {
|
||||||
|
const n2 = t0()
|
||||||
|
return n2
|
||||||
|
})
|
||||||
|
_setInsertionState(n8)
|
||||||
|
const n3 = _createIf(() => (_ctx.bar), () => {
|
||||||
|
const n5 = t1()
|
||||||
|
return n5
|
||||||
|
}, () => {
|
||||||
|
const n7 = t2()
|
||||||
|
return n7
|
||||||
|
})
|
||||||
|
return n8
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
|
@ -123,6 +123,18 @@ export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`v-on > expression with type 1`] = `
|
||||||
|
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div></div>", true)
|
||||||
|
_delegateEvents("click")
|
||||||
|
|
||||||
|
export function render(_ctx, $props, $emit, $attrs, $slots) {
|
||||||
|
const n0 = t0()
|
||||||
|
n0.$evtclick = e => _ctx.handleClick(e)
|
||||||
|
return n0
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`v-on > function expression w/ prefixIdentifiers: true 1`] = `
|
exports[`v-on > function expression w/ prefixIdentifiers: true 1`] = `
|
||||||
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
|
"import { delegateEvents as _delegateEvents, template as _template } from 'vue';
|
||||||
const t0 = _template("<div></div>", true)
|
const t0 = _template("<div></div>", true)
|
||||||
|
|
|
@ -252,3 +252,90 @@ export function render(_ctx) {
|
||||||
return n1
|
return n1
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > slot + v-if / v-else[-if] should not cause error 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, setInsertionState as _setInsertionState, createSlot as _createSlot, createComponentWithFallback as _createComponentWithFallback, createIf as _createIf, template as _template } from 'vue';
|
||||||
|
const t0 = _template("<div></div>", true)
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Foo = _resolveComponent("Foo")
|
||||||
|
const _component_Bar = _resolveComponent("Bar")
|
||||||
|
const n6 = t0()
|
||||||
|
_setInsertionState(n6)
|
||||||
|
const n0 = _createSlot("foo", null)
|
||||||
|
_setInsertionState(n6)
|
||||||
|
const n1 = _createIf(() => (true), () => {
|
||||||
|
const n3 = _createComponentWithFallback(_component_Foo)
|
||||||
|
return n3
|
||||||
|
}, () => {
|
||||||
|
const n5 = _createComponentWithFallback(_component_Bar)
|
||||||
|
return n5
|
||||||
|
})
|
||||||
|
return n6
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > with whitespace: 'preserve' > implicit default slot 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
|
||||||
|
const t0 = _template(" Header ")
|
||||||
|
const t1 = _template(" ")
|
||||||
|
const t2 = _template("<p></p>")
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n4 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"header": () => {
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
"default": () => {
|
||||||
|
const n2 = t1()
|
||||||
|
const n3 = t2()
|
||||||
|
return [n2, n3]
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n4
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > with whitespace: 'preserve' > named default slot + implicit whitespace content 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
|
||||||
|
const t0 = _template(" Header ")
|
||||||
|
const t1 = _template(" Default ")
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n5 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"header": () => {
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
"default": () => {
|
||||||
|
const n3 = t1()
|
||||||
|
return n3
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n5
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > with whitespace: 'preserve' > should not generate whitespace only default slot 1`] = `
|
||||||
|
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
|
||||||
|
const t0 = _template(" Header ")
|
||||||
|
const t1 = _template(" Footer ")
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n5 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"header": () => {
|
||||||
|
const n0 = t0()
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
"footer": () => {
|
||||||
|
const n3 = t1()
|
||||||
|
return n3
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n5
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import { BindingTypes } from '@vue/compiler-dom'
|
import { BindingTypes } from '@vue/compiler-dom'
|
||||||
import { transformChildren, transformText } from '../../src'
|
import {
|
||||||
|
transformChildren,
|
||||||
|
transformElement,
|
||||||
|
transformText,
|
||||||
|
transformVBind,
|
||||||
|
} from '../../src'
|
||||||
import { makeCompile } from './_utils'
|
import { makeCompile } from './_utils'
|
||||||
|
|
||||||
const compileWithExpression = makeCompile({
|
const compileWithExpression = makeCompile({
|
||||||
nodeTransforms: [transformChildren, transformText],
|
nodeTransforms: [transformElement, transformChildren, transformText],
|
||||||
|
directiveTransforms: { bind: transformVBind },
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('compiler: expression', () => {
|
describe('compiler: expression', () => {
|
||||||
|
@ -31,4 +37,14 @@ describe('compiler: expression', () => {
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
expect(code).contains(`$props['bar']`)
|
expect(code).contains(`$props['bar']`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('update expression', () => {
|
||||||
|
const { code } = compileWithExpression(`
|
||||||
|
<div :id="String(foo.id++)" :foo="foo" :bar="bar++">
|
||||||
|
{{ String(foo.id++) }} {{ foo }} {{ bar }}
|
||||||
|
</div>
|
||||||
|
`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
expect(code).contains(`_String(_foo.id++)`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -69,8 +69,8 @@ describe('compiler: children transform', () => {
|
||||||
</div>`,
|
</div>`,
|
||||||
)
|
)
|
||||||
// ensure the insertion anchor is generated before the insertion statement
|
// ensure the insertion anchor is generated before the insertion statement
|
||||||
expect(code).toMatch(`const n3 = _next(_child(n4))
|
expect(code).toMatch(`const n3 = _next(_child(n4))`)
|
||||||
_setInsertionState(n4, n3)`)
|
expect(code).toMatch(`_setInsertionState(n4, n3)`)
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,16 +6,22 @@ import {
|
||||||
transformElement,
|
transformElement,
|
||||||
transformText,
|
transformText,
|
||||||
transformVBind,
|
transformVBind,
|
||||||
|
transformVFor,
|
||||||
transformVOn,
|
transformVOn,
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import {
|
import {
|
||||||
type BindingMetadata,
|
type BindingMetadata,
|
||||||
BindingTypes,
|
BindingTypes,
|
||||||
NodeTypes,
|
NodeTypes,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-dom'
|
||||||
|
|
||||||
const compileWithElementTransform = makeCompile({
|
const compileWithElementTransform = makeCompile({
|
||||||
nodeTransforms: [transformElement, transformChildren, transformText],
|
nodeTransforms: [
|
||||||
|
transformVFor,
|
||||||
|
transformElement,
|
||||||
|
transformChildren,
|
||||||
|
transformText,
|
||||||
|
],
|
||||||
directiveTransforms: {
|
directiveTransforms: {
|
||||||
bind: transformVBind,
|
bind: transformVBind,
|
||||||
on: transformVOn,
|
on: transformVOn,
|
||||||
|
@ -39,11 +45,12 @@ describe('compiler: element transform', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
test.todo('resolve implicitly self-referencing component', () => {
|
test('resolve implicitly self-referencing component', () => {
|
||||||
const { code, helpers } = compileWithElementTransform(`<Example/>`, {
|
const { code, helpers } = compileWithElementTransform(`<Example/>`, {
|
||||||
filename: `/foo/bar/Example.vue?vue&type=template`,
|
filename: `/foo/bar/Example.vue?vue&type=template`,
|
||||||
})
|
})
|
||||||
expect(code).toMatchSnapshot()
|
expect(code).toMatchSnapshot()
|
||||||
|
expect(code).toContain('_resolveComponent("Example", true)')
|
||||||
expect(helpers).toContain('resolveComponent')
|
expect(helpers).toContain('resolveComponent')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -169,6 +176,17 @@ describe('compiler: element transform', () => {
|
||||||
expect(code).contains('_createComponent(_ctx.Comp)')
|
expect(code).contains('_createComponent(_ctx.Comp)')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-for on component should not mark as single root', () => {
|
||||||
|
const { code } = compileWithElementTransform(
|
||||||
|
`<Comp v-for="item in items" :key="item"/>`,
|
||||||
|
{
|
||||||
|
bindingMetadata: { Comp: BindingTypes.SETUP_CONST },
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
expect(code).contains('_createComponent(_ctx.Comp)')
|
||||||
|
})
|
||||||
|
|
||||||
test('static props', () => {
|
test('static props', () => {
|
||||||
const { code, ir } = compileWithElementTransform(
|
const { code, ir } = compileWithElementTransform(
|
||||||
`<Foo id="foo" class="bar" />`,
|
`<Foo id="foo" class="bar" />`,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
|
import { ErrorCodes, NodeTypes } from '@vue/compiler-dom'
|
||||||
import {
|
import {
|
||||||
IRNodeTypes,
|
IRNodeTypes,
|
||||||
transformChildren,
|
transformChildren,
|
||||||
|
|
|
@ -81,6 +81,47 @@ describe('compiler: template ref transform', () => {
|
||||||
expect(code).contains('_setTemplateRef(n0, _ctx.foo, r0)')
|
expect(code).contains('_setTemplateRef(n0, _ctx.foo, r0)')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('function ref', () => {
|
||||||
|
const { ir, code } = compileWithTransformRef(
|
||||||
|
`<div :ref="bar => {
|
||||||
|
foo.value = bar
|
||||||
|
;({ baz } = bar)
|
||||||
|
console.log(foo.value, baz)
|
||||||
|
}" />`,
|
||||||
|
)
|
||||||
|
expect(ir.block.dynamic.children[0]).toMatchObject({
|
||||||
|
id: 0,
|
||||||
|
flags: DynamicFlag.REFERENCED,
|
||||||
|
})
|
||||||
|
expect(ir.template).toEqual(['<div></div>'])
|
||||||
|
expect(ir.block.operation).toMatchObject([
|
||||||
|
{
|
||||||
|
type: IRNodeTypes.DECLARE_OLD_REF,
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(ir.block.effect).toMatchObject([
|
||||||
|
{
|
||||||
|
operations: [
|
||||||
|
{
|
||||||
|
type: IRNodeTypes.SET_TEMPLATE_REF,
|
||||||
|
element: 0,
|
||||||
|
value: {
|
||||||
|
isStatic: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
])
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
expect(code).contains('const _setTemplateRef = _createTemplateRefSetter()')
|
||||||
|
expect(code).contains(`_setTemplateRef(n0, bar => {
|
||||||
|
_foo.value = bar
|
||||||
|
;({ baz: _ctx.baz } = bar)
|
||||||
|
console.log(_foo.value, _ctx.baz)
|
||||||
|
}, r0)`)
|
||||||
|
})
|
||||||
|
|
||||||
test('ref + v-if', () => {
|
test('ref + v-if', () => {
|
||||||
const { ir, code } = compileWithTransformRef(
|
const { ir, code } = compileWithTransformRef(
|
||||||
`<div ref="foo" v-if="true" />`,
|
`<div ref="foo" v-if="true" />`,
|
||||||
|
|
|
@ -809,4 +809,12 @@ describe('cache multiple access', () => {
|
||||||
expect(code).matchSnapshot()
|
expect(code).matchSnapshot()
|
||||||
expect(code).not.contains('const _bar = _ctx.bar')
|
expect(code).not.contains('const _bar = _ctx.bar')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('not cache variable in function expression', () => {
|
||||||
|
const { code } = compileWithVBind(`
|
||||||
|
<div v-bind="{ foo: bar => foo = bar }"></div>
|
||||||
|
`)
|
||||||
|
expect(code).matchSnapshot()
|
||||||
|
expect(code).not.contains('const _bar = _ctx.bar')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -119,6 +119,32 @@ describe('compiler: v-for', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('object value, key and index', () => {
|
||||||
|
const { code, ir } = compileWithVFor(
|
||||||
|
`<div v-for="(value, key, index) in list" :key="key">{{ value + key + index }}</div>`,
|
||||||
|
)
|
||||||
|
expect(code).matchSnapshot()
|
||||||
|
expect(ir.block.dynamic.children[0].operation).toMatchObject({
|
||||||
|
type: IRNodeTypes.FOR,
|
||||||
|
source: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'list',
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'value',
|
||||||
|
},
|
||||||
|
key: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'key',
|
||||||
|
},
|
||||||
|
index: {
|
||||||
|
type: NodeTypes.SIMPLE_EXPRESSION,
|
||||||
|
content: 'index',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('object de-structured value', () => {
|
test('object de-structured value', () => {
|
||||||
const { code, ir } = compileWithVFor(
|
const { code, ir } = compileWithVFor(
|
||||||
'<span v-for="({ id, value }) in items" :key="id">{{ id }}{{ value }}</span>',
|
'<span v-for="({ id, value }) in items" :key="id">{{ id }}{{ value }}</span>',
|
||||||
|
|
|
@ -10,7 +10,7 @@ import {
|
||||||
transformVOnce,
|
transformVOnce,
|
||||||
transformVText,
|
transformVText,
|
||||||
} from '../../src'
|
} from '../../src'
|
||||||
import { NodeTypes } from '@vue/compiler-core'
|
import { NodeTypes } from '@vue/compiler-dom'
|
||||||
|
|
||||||
const compileWithVIf = makeCompile({
|
const compileWithVIf = makeCompile({
|
||||||
nodeTransforms: [
|
nodeTransforms: [
|
||||||
|
@ -215,6 +215,17 @@ describe('compiler: v-if', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('v-if + v-if / v-else[-if]', () => {
|
||||||
|
const { code } = compileWithVIf(
|
||||||
|
`<div>
|
||||||
|
<span v-if="foo">foo</span>
|
||||||
|
<span v-if="bar">bar</span>
|
||||||
|
<span v-else>baz</span>
|
||||||
|
</div>`,
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('comment between branches', () => {
|
test('comment between branches', () => {
|
||||||
const { code, ir } = compileWithVIf(`
|
const { code, ir } = compileWithVIf(`
|
||||||
<div v-if="ok"/>
|
<div v-if="ok"/>
|
||||||
|
|
|
@ -682,4 +682,17 @@ describe('v-on', () => {
|
||||||
'_delegate(n0, "click", _withModifiers(e => _ctx.test(e), ["stop"]))',
|
'_delegate(n0, "click", _withModifiers(e => _ctx.test(e), ["stop"]))',
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('expression with type', () => {
|
||||||
|
const { code } = compileWithVOn(
|
||||||
|
`<div @click="(<number>handleClick as any)"></div>`,
|
||||||
|
{
|
||||||
|
bindingMetadata: {
|
||||||
|
handleClick: BindingTypes.SETUP_CONST,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
expect(code).matchSnapshot()
|
||||||
|
expect(code).include('n0.$evtclick = e => _ctx.handleClick(e)')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { ErrorCodes, NodeTypes } from '@vue/compiler-core'
|
import { ErrorCodes, NodeTypes } from '@vue/compiler-dom'
|
||||||
import {
|
import {
|
||||||
IRNodeTypes,
|
IRNodeTypes,
|
||||||
IRSlotType,
|
IRSlotType,
|
||||||
|
@ -371,6 +371,17 @@ describe('compiler: transform slot', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('slot + v-if / v-else[-if] should not cause error', () => {
|
||||||
|
const { code } = compileWithSlots(
|
||||||
|
`<div>
|
||||||
|
<slot name="foo"></slot>
|
||||||
|
<Foo v-if="true"></Foo>
|
||||||
|
<Bar v-else />
|
||||||
|
</div>`,
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
test('quote slot name', () => {
|
test('quote slot name', () => {
|
||||||
const { code } = compileWithSlots(
|
const { code } = compileWithSlots(
|
||||||
`<Comp><template #nav-bar-title-before></template></Comp>`,
|
`<Comp><template #nav-bar-title-before></template></Comp>`,
|
||||||
|
@ -498,4 +509,60 @@ describe('compiler: transform slot', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe(`with whitespace: 'preserve'`, () => {
|
||||||
|
test('named default slot + implicit whitespace content', () => {
|
||||||
|
const source = `
|
||||||
|
<Comp>
|
||||||
|
<template #header> Header </template>
|
||||||
|
<template #default> Default </template>
|
||||||
|
</Comp>
|
||||||
|
`
|
||||||
|
const { code } = compileWithSlots(source, {
|
||||||
|
whitespace: 'preserve',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
`Extraneous children found when component already has explicitly named default slot.`,
|
||||||
|
).not.toHaveBeenWarned()
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('implicit default slot', () => {
|
||||||
|
const source = `
|
||||||
|
<Comp>
|
||||||
|
<template #header> Header </template>
|
||||||
|
<p/>
|
||||||
|
</Comp>
|
||||||
|
`
|
||||||
|
const { code } = compileWithSlots(source, {
|
||||||
|
whitespace: 'preserve',
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(
|
||||||
|
`Extraneous children found when component already has explicitly named default slot.`,
|
||||||
|
).not.toHaveBeenWarned()
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not generate whitespace only default slot', () => {
|
||||||
|
const source = `
|
||||||
|
<Comp>
|
||||||
|
<template #header> Header </template>
|
||||||
|
<template #footer> Footer </template>
|
||||||
|
</Comp>
|
||||||
|
`
|
||||||
|
const { code, ir } = compileWithSlots(source, {
|
||||||
|
whitespace: 'preserve',
|
||||||
|
})
|
||||||
|
|
||||||
|
const slots = (ir.block.dynamic.children[0].operation as any).slots[0]
|
||||||
|
.slots
|
||||||
|
// should be: header, footer (no default)
|
||||||
|
expect(Object.keys(slots).length).toBe(2)
|
||||||
|
expect(!!slots['default']).toBe(false)
|
||||||
|
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -81,8 +81,8 @@ export function getBaseTransformPreset(): TransformPreset {
|
||||||
transformVFor,
|
transformVFor,
|
||||||
transformSlotOutlet,
|
transformSlotOutlet,
|
||||||
transformTemplateRef,
|
transformTemplateRef,
|
||||||
transformText,
|
|
||||||
transformElement,
|
transformElement,
|
||||||
|
transformText,
|
||||||
transformVSlot,
|
transformVSlot,
|
||||||
transformComment,
|
transformComment,
|
||||||
transformChildren,
|
transformChildren,
|
||||||
|
|
|
@ -44,7 +44,21 @@ export function genBlockContent(
|
||||||
const resetBlock = context.enterBlock(block)
|
const resetBlock = context.enterBlock(block)
|
||||||
|
|
||||||
if (root) {
|
if (root) {
|
||||||
genResolveAssets('component', 'resolveComponent')
|
for (let name of context.ir.component) {
|
||||||
|
const id = toValidAssetId(name, 'component')
|
||||||
|
const maybeSelfReference = name.endsWith('__self')
|
||||||
|
if (maybeSelfReference) name = name.slice(0, -6)
|
||||||
|
push(
|
||||||
|
NEWLINE,
|
||||||
|
`const ${id} = `,
|
||||||
|
...genCall(
|
||||||
|
context.helper('resolveComponent'),
|
||||||
|
JSON.stringify(name),
|
||||||
|
// pass additional `maybeSelfReference` flag
|
||||||
|
maybeSelfReference ? 'true' : undefined,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
genResolveAssets('directive', 'resolveDirective')
|
genResolveAssets('directive', 'resolveDirective')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +66,7 @@ export function genBlockContent(
|
||||||
push(...genSelf(child, context))
|
push(...genSelf(child, context))
|
||||||
}
|
}
|
||||||
for (const child of dynamic.children) {
|
for (const child of dynamic.children) {
|
||||||
push(...genChildren(child, context, `n${child.id!}`))
|
push(...genChildren(child, context, push, `n${child.id!}`))
|
||||||
}
|
}
|
||||||
|
|
||||||
push(...genOperations(operation, context))
|
push(...genOperations(operation, context))
|
||||||
|
|
|
@ -34,7 +34,7 @@ import {
|
||||||
isMemberExpression,
|
isMemberExpression,
|
||||||
toValidAssetId,
|
toValidAssetId,
|
||||||
walkIdentifiers,
|
walkIdentifiers,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-dom'
|
||||||
import { genEventHandler } from './event'
|
import { genEventHandler } from './event'
|
||||||
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
|
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
|
||||||
import { genBlock } from './block'
|
import { genBlock } from './block'
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {
|
||||||
NewlineType,
|
NewlineType,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
type SourceLocation,
|
type SourceLocation,
|
||||||
|
TS_NODE_TYPES,
|
||||||
advancePositionWithClone,
|
advancePositionWithClone,
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
isInDestructureAssignment,
|
isInDestructureAssignment,
|
||||||
|
@ -20,7 +21,6 @@ import type { Identifier, Node } from '@babel/types'
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodegenContext } from '../generate'
|
||||||
import { isConstantExpression } from '../utils'
|
import { isConstantExpression } from '../utils'
|
||||||
import { type CodeFragment, NEWLINE, buildCodeFragment } from './utils'
|
import { type CodeFragment, NEWLINE, buildCodeFragment } from './utils'
|
||||||
import { walk } from 'estree-walker'
|
|
||||||
import { type ParserOptions, parseExpression } from '@babel/parser'
|
import { type ParserOptions, parseExpression } from '@babel/parser'
|
||||||
|
|
||||||
export function genExpression(
|
export function genExpression(
|
||||||
|
@ -64,6 +64,7 @@ export function genExpression(
|
||||||
let hasMemberExpression = false
|
let hasMemberExpression = false
|
||||||
if (ids.length) {
|
if (ids.length) {
|
||||||
const [frag, push] = buildCodeFragment()
|
const [frag, push] = buildCodeFragment()
|
||||||
|
const isTSNode = ast && TS_NODE_TYPES.includes(ast.type)
|
||||||
ids
|
ids
|
||||||
.sort((a, b) => a.start! - b.start!)
|
.sort((a, b) => a.start! - b.start!)
|
||||||
.forEach((id, i) => {
|
.forEach((id, i) => {
|
||||||
|
@ -72,8 +73,10 @@ export function genExpression(
|
||||||
const end = id.end! - 1
|
const end = id.end! - 1
|
||||||
const last = ids[i - 1]
|
const last = ids[i - 1]
|
||||||
|
|
||||||
const leadingText = content.slice(last ? last.end! - 1 : 0, start)
|
if (!(isTSNode && i === 0)) {
|
||||||
if (leadingText.length) push([leadingText, NewlineType.Unknown])
|
const leadingText = content.slice(last ? last.end! - 1 : 0, start)
|
||||||
|
if (leadingText.length) push([leadingText, NewlineType.Unknown])
|
||||||
|
}
|
||||||
|
|
||||||
const source = content.slice(start, end)
|
const source = content.slice(start, end)
|
||||||
const parentStack = parentStackMap.get(id)!
|
const parentStack = parentStackMap.get(id)!
|
||||||
|
@ -100,7 +103,7 @@ export function genExpression(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
if (i === ids.length - 1 && end < content.length) {
|
if (i === ids.length - 1 && end < content.length && !isTSNode) {
|
||||||
push([content.slice(end), NewlineType.Unknown])
|
push([content.slice(end), NewlineType.Unknown])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -245,8 +248,13 @@ export function processExpressions(
|
||||||
expressions: SimpleExpressionNode[],
|
expressions: SimpleExpressionNode[],
|
||||||
): DeclarationResult {
|
): DeclarationResult {
|
||||||
// analyze variables
|
// analyze variables
|
||||||
const { seenVariable, variableToExpMap, expToVariableMap, seenIdentifier } =
|
const {
|
||||||
analyzeExpressions(expressions)
|
seenVariable,
|
||||||
|
variableToExpMap,
|
||||||
|
expToVariableMap,
|
||||||
|
seenIdentifier,
|
||||||
|
updatedVariable,
|
||||||
|
} = analyzeExpressions(expressions)
|
||||||
|
|
||||||
// process repeated identifiers and member expressions
|
// process repeated identifiers and member expressions
|
||||||
// e.g., `foo[baz]` will be transformed into `foo_baz`
|
// e.g., `foo[baz]` will be transformed into `foo_baz`
|
||||||
|
@ -256,6 +264,7 @@ export function processExpressions(
|
||||||
variableToExpMap,
|
variableToExpMap,
|
||||||
expToVariableMap,
|
expToVariableMap,
|
||||||
seenIdentifier,
|
seenIdentifier,
|
||||||
|
updatedVariable,
|
||||||
)
|
)
|
||||||
|
|
||||||
// process duplicate expressions after identifier and member expression handling.
|
// process duplicate expressions after identifier and member expression handling.
|
||||||
|
@ -264,6 +273,8 @@ export function processExpressions(
|
||||||
context,
|
context,
|
||||||
expressions,
|
expressions,
|
||||||
varDeclarations,
|
varDeclarations,
|
||||||
|
updatedVariable,
|
||||||
|
expToVariableMap,
|
||||||
)
|
)
|
||||||
|
|
||||||
return genDeclarations([...varDeclarations, ...expDeclarations], context)
|
return genDeclarations([...varDeclarations, ...expDeclarations], context)
|
||||||
|
@ -274,11 +285,13 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
|
||||||
const variableToExpMap = new Map<string, Set<SimpleExpressionNode>>()
|
const variableToExpMap = new Map<string, Set<SimpleExpressionNode>>()
|
||||||
const expToVariableMap = new Map<SimpleExpressionNode, string[]>()
|
const expToVariableMap = new Map<SimpleExpressionNode, string[]>()
|
||||||
const seenIdentifier = new Set<string>()
|
const seenIdentifier = new Set<string>()
|
||||||
|
const updatedVariable = new Set<string>()
|
||||||
|
|
||||||
const registerVariable = (
|
const registerVariable = (
|
||||||
name: string,
|
name: string,
|
||||||
exp: SimpleExpressionNode,
|
exp: SimpleExpressionNode,
|
||||||
isIdentifier: boolean,
|
isIdentifier: boolean,
|
||||||
|
parentStack: Node[] = [],
|
||||||
) => {
|
) => {
|
||||||
if (isIdentifier) seenIdentifier.add(name)
|
if (isIdentifier) seenIdentifier.add(name)
|
||||||
seenVariable[name] = (seenVariable[name] || 0) + 1
|
seenVariable[name] = (seenVariable[name] || 0) + 1
|
||||||
|
@ -287,6 +300,13 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
|
||||||
(variableToExpMap.get(name) || new Set()).add(exp),
|
(variableToExpMap.get(name) || new Set()).add(exp),
|
||||||
)
|
)
|
||||||
expToVariableMap.set(exp, (expToVariableMap.get(exp) || []).concat(name))
|
expToVariableMap.set(exp, (expToVariableMap.get(exp) || []).concat(name))
|
||||||
|
if (
|
||||||
|
parentStack.some(
|
||||||
|
p => p.type === 'UpdateExpression' || p.type === 'AssignmentExpression',
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
updatedVariable.add(name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const exp of expressions) {
|
for (const exp of expressions) {
|
||||||
|
@ -295,37 +315,25 @@ function analyzeExpressions(expressions: SimpleExpressionNode[]) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
walk(exp.ast, {
|
walkIdentifiers(exp.ast, (currentNode, parent, parentStack) => {
|
||||||
enter(currentNode: Node, parent: Node | null) {
|
if (parent && isMemberExpression(parent)) {
|
||||||
if (currentNode.type === 'MemberExpression') {
|
const memberExp = extractMemberExpression(parent, name => {
|
||||||
const memberExp = extractMemberExpression(
|
registerVariable(name, exp, true)
|
||||||
currentNode,
|
})
|
||||||
(name: string) => {
|
registerVariable(memberExp, exp, false, parentStack)
|
||||||
registerVariable(name, exp, true)
|
} else if (!parentStack.some(isMemberExpression)) {
|
||||||
},
|
registerVariable(currentNode.name, exp, true, parentStack)
|
||||||
)
|
}
|
||||||
registerVariable(memberExp, exp, false)
|
|
||||||
return this.skip()
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip shorthand or non-computed property keys
|
|
||||||
if (
|
|
||||||
parent &&
|
|
||||||
parent.type === 'ObjectProperty' &&
|
|
||||||
parent.key === currentNode &&
|
|
||||||
(parent.shorthand || !parent.computed)
|
|
||||||
) {
|
|
||||||
return this.skip()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentNode.type === 'Identifier') {
|
|
||||||
registerVariable(currentNode.name, exp, true)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return { seenVariable, seenIdentifier, variableToExpMap, expToVariableMap }
|
return {
|
||||||
|
seenVariable,
|
||||||
|
seenIdentifier,
|
||||||
|
variableToExpMap,
|
||||||
|
expToVariableMap,
|
||||||
|
updatedVariable,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processRepeatedVariables(
|
function processRepeatedVariables(
|
||||||
|
@ -334,9 +342,11 @@ function processRepeatedVariables(
|
||||||
variableToExpMap: Map<string, Set<SimpleExpressionNode>>,
|
variableToExpMap: Map<string, Set<SimpleExpressionNode>>,
|
||||||
expToVariableMap: Map<SimpleExpressionNode, string[]>,
|
expToVariableMap: Map<SimpleExpressionNode, string[]>,
|
||||||
seenIdentifier: Set<string>,
|
seenIdentifier: Set<string>,
|
||||||
|
updatedVariable: Set<string>,
|
||||||
): DeclarationValue[] {
|
): DeclarationValue[] {
|
||||||
const declarations: DeclarationValue[] = []
|
const declarations: DeclarationValue[] = []
|
||||||
for (const [name, exps] of variableToExpMap) {
|
for (const [name, exps] of variableToExpMap) {
|
||||||
|
if (updatedVariable.has(name)) continue
|
||||||
if (seenVariable[name] > 1 && exps.size > 0) {
|
if (seenVariable[name] > 1 && exps.size > 0) {
|
||||||
const isIdentifier = seenIdentifier.has(name)
|
const isIdentifier = seenIdentifier.has(name)
|
||||||
const varName = isIdentifier ? name : genVarName(name)
|
const varName = isIdentifier ? name : genVarName(name)
|
||||||
|
@ -428,12 +438,19 @@ function processRepeatedExpressions(
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
expressions: SimpleExpressionNode[],
|
expressions: SimpleExpressionNode[],
|
||||||
varDeclarations: DeclarationValue[],
|
varDeclarations: DeclarationValue[],
|
||||||
|
updatedVariable: Set<string>,
|
||||||
|
expToVariableMap: Map<SimpleExpressionNode, string[]>,
|
||||||
): DeclarationValue[] {
|
): DeclarationValue[] {
|
||||||
const declarations: DeclarationValue[] = []
|
const declarations: DeclarationValue[] = []
|
||||||
const seenExp = expressions.reduce(
|
const seenExp = expressions.reduce(
|
||||||
(acc, exp) => {
|
(acc, exp) => {
|
||||||
|
const variables = expToVariableMap.get(exp)
|
||||||
// only handle expressions that are not identifiers
|
// only handle expressions that are not identifiers
|
||||||
if (exp.ast && exp.ast.type !== 'Identifier') {
|
if (
|
||||||
|
exp.ast &&
|
||||||
|
exp.ast.type !== 'Identifier' &&
|
||||||
|
!(variables && variables.some(v => updatedVariable.has(v)))
|
||||||
|
) {
|
||||||
acc[exp.content] = (acc[exp.content] || 0) + 1
|
acc[exp.content] = (acc[exp.content] || 0) + 1
|
||||||
}
|
}
|
||||||
return acc
|
return acc
|
||||||
|
@ -580,3 +597,9 @@ function extractMemberExpression(
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isMemberExpression = (node: Node) => {
|
||||||
|
return (
|
||||||
|
node.type === 'MemberExpression' || node.type === 'OptionalMemberExpression'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ export function genOperationWithInsertionState(
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const [frag, push] = buildCodeFragment()
|
const [frag, push] = buildCodeFragment()
|
||||||
if (isBlockOperation(oper) && oper.parent) {
|
if (isBlockOperation(oper) && oper.parent) {
|
||||||
push(...genInsertionstate(oper, context))
|
push(...genInsertionState(oper, context))
|
||||||
}
|
}
|
||||||
push(...genOperation(oper, context))
|
push(...genOperation(oper, context))
|
||||||
return frag
|
return frag
|
||||||
|
@ -99,10 +99,8 @@ export function genEffects(
|
||||||
effects: IREffect[],
|
effects: IREffect[],
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const {
|
const { helper } = context
|
||||||
helper,
|
const expressions = effects.flatMap(effect => effect.expressions)
|
||||||
block: { expressions },
|
|
||||||
} = context
|
|
||||||
const [frag, push, unshift] = buildCodeFragment()
|
const [frag, push, unshift] = buildCodeFragment()
|
||||||
let operationsCount = 0
|
let operationsCount = 0
|
||||||
const { ids, frag: declarationFrags } = processExpressions(
|
const { ids, frag: declarationFrags } = processExpressions(
|
||||||
|
@ -152,7 +150,7 @@ export function genEffect(
|
||||||
return frag
|
return frag
|
||||||
}
|
}
|
||||||
|
|
||||||
function genInsertionstate(
|
function genInsertionState(
|
||||||
operation: InsertionStateTypes,
|
operation: InsertionStateTypes,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {
|
||||||
NewlineType,
|
NewlineType,
|
||||||
type SimpleExpressionNode,
|
type SimpleExpressionNode,
|
||||||
isSimpleIdentifier,
|
isSimpleIdentifier,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-dom'
|
||||||
import type { CodegenContext } from '../generate'
|
import type { CodegenContext } from '../generate'
|
||||||
import {
|
import {
|
||||||
IRDynamicPropsKind,
|
IRDynamicPropsKind,
|
||||||
|
|
|
@ -41,6 +41,7 @@ export function genSelf(
|
||||||
export function genChildren(
|
export function genChildren(
|
||||||
dynamic: IRDynamicInfo,
|
dynamic: IRDynamicInfo,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
|
pushBlock: (...items: CodeFragment[]) => number,
|
||||||
from: string = `n${dynamic.id}`,
|
from: string = `n${dynamic.id}`,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const { helper } = context
|
const { helper } = context
|
||||||
|
@ -72,17 +73,17 @@ export function genChildren(
|
||||||
// p for "placeholder" variables that are meant for possible reuse by
|
// p for "placeholder" variables that are meant for possible reuse by
|
||||||
// other access paths
|
// other access paths
|
||||||
const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}`
|
const variable = id === undefined ? `p${context.block.tempId++}` : `n${id}`
|
||||||
push(NEWLINE, `const ${variable} = `)
|
pushBlock(NEWLINE, `const ${variable} = `)
|
||||||
|
|
||||||
if (prev) {
|
if (prev) {
|
||||||
if (elementIndex - prev[1] === 1) {
|
if (elementIndex - prev[1] === 1) {
|
||||||
push(...genCall(helper('next'), prev[0]))
|
pushBlock(...genCall(helper('next'), prev[0]))
|
||||||
} else {
|
} else {
|
||||||
push(...genCall(helper('nthChild'), from, String(elementIndex)))
|
pushBlock(...genCall(helper('nthChild'), from, String(elementIndex)))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (elementIndex === 0) {
|
if (elementIndex === 0) {
|
||||||
push(...genCall(helper('child'), from))
|
pushBlock(...genCall(helper('child'), from))
|
||||||
} else {
|
} else {
|
||||||
// check if there's a node that we can reuse from
|
// check if there's a node that we can reuse from
|
||||||
let init = genCall(helper('child'), from)
|
let init = genCall(helper('child'), from)
|
||||||
|
@ -91,7 +92,7 @@ export function genChildren(
|
||||||
} else if (elementIndex > 1) {
|
} else if (elementIndex > 1) {
|
||||||
init = genCall(helper('nthChild'), from, String(elementIndex))
|
init = genCall(helper('nthChild'), from, String(elementIndex))
|
||||||
}
|
}
|
||||||
push(...init)
|
pushBlock(...init)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,7 +110,7 @@ export function genChildren(
|
||||||
|
|
||||||
if (childrenToGen.length) {
|
if (childrenToGen.length) {
|
||||||
for (const [child, from] of childrenToGen) {
|
for (const [child, from] of childrenToGen) {
|
||||||
push(...genChildren(child, context, from))
|
push(...genChildren(child, context, pushBlock, from))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -52,7 +52,6 @@ export interface BlockIRNode extends BaseIRNode {
|
||||||
tempId: number
|
tempId: number
|
||||||
effect: IREffect[]
|
effect: IREffect[]
|
||||||
operation: OperationNode[]
|
operation: OperationNode[]
|
||||||
expressions: SimpleExpressionNode[]
|
|
||||||
returns: number[]
|
returns: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
type TemplateChildNode,
|
type TemplateChildNode,
|
||||||
defaultOnError,
|
defaultOnError,
|
||||||
defaultOnWarn,
|
defaultOnWarn,
|
||||||
|
getSelfName,
|
||||||
isVSlot,
|
isVSlot,
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
|
import { EMPTY_OBJ, NOOP, extend, isArray, isString } from '@vue/shared'
|
||||||
|
@ -61,6 +62,7 @@ export type StructuralDirectiveTransform = (
|
||||||
export type TransformOptions = HackOptions<BaseTransformOptions>
|
export type TransformOptions = HackOptions<BaseTransformOptions>
|
||||||
|
|
||||||
export class TransformContext<T extends AllNode = AllNode> {
|
export class TransformContext<T extends AllNode = AllNode> {
|
||||||
|
selfName: string | null = null
|
||||||
parent: TransformContext<RootNode | ElementNode> | null = null
|
parent: TransformContext<RootNode | ElementNode> | null = null
|
||||||
root: TransformContext<RootNode>
|
root: TransformContext<RootNode>
|
||||||
index: number = 0
|
index: number = 0
|
||||||
|
@ -92,6 +94,7 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
) {
|
) {
|
||||||
this.options = extend({}, defaultOptions, options)
|
this.options = extend({}, defaultOptions, options)
|
||||||
this.root = this as TransformContext<RootNode>
|
this.root = this as TransformContext<RootNode>
|
||||||
|
if (options.filename) this.selfName = getSelfName(options.filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
|
enterBlock(ir: BlockIRNode, isVFor: boolean = false): () => void {
|
||||||
|
@ -137,8 +140,10 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
|
|
||||||
registerEffect(
|
registerEffect(
|
||||||
expressions: SimpleExpressionNode[],
|
expressions: SimpleExpressionNode[],
|
||||||
...operations: OperationNode[]
|
operation: OperationNode | OperationNode[],
|
||||||
|
getIndex = (): number => this.block.effect.length,
|
||||||
): void {
|
): void {
|
||||||
|
const operations = [operation].flat()
|
||||||
expressions = expressions.filter(exp => !isConstantExpression(exp))
|
expressions = expressions.filter(exp => !isConstantExpression(exp))
|
||||||
if (
|
if (
|
||||||
this.inVOnce ||
|
this.inVOnce ||
|
||||||
|
@ -150,26 +155,10 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
return this.registerOperation(...operations)
|
return this.registerOperation(...operations)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.block.expressions.push(...expressions)
|
this.block.effect.splice(getIndex(), 0, {
|
||||||
const existing = this.block.effect.find(e =>
|
expressions,
|
||||||
isSameExpression(e.expressions, expressions),
|
operations,
|
||||||
)
|
})
|
||||||
if (existing) {
|
|
||||||
existing.operations.push(...operations)
|
|
||||||
} else {
|
|
||||||
this.block.effect.push({
|
|
||||||
expressions,
|
|
||||||
operations,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function isSameExpression(
|
|
||||||
a: SimpleExpressionNode[],
|
|
||||||
b: SimpleExpressionNode[],
|
|
||||||
) {
|
|
||||||
if (a.length !== b.length) return false
|
|
||||||
return a.every((exp, i) => exp.content === b[i].content)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
registerOperation(...node: OperationNode[]): void {
|
registerOperation(...node: OperationNode[]): void {
|
||||||
|
|
|
@ -44,6 +44,8 @@ export const isReservedProp: (key: string) => boolean = /*#__PURE__*/ makeMap(
|
||||||
)
|
)
|
||||||
|
|
||||||
export const transformElement: NodeTransform = (node, context) => {
|
export const transformElement: NodeTransform = (node, context) => {
|
||||||
|
let effectIndex = context.block.effect.length
|
||||||
|
const getEffectIndex = () => effectIndex++
|
||||||
return function postTransformElement() {
|
return function postTransformElement() {
|
||||||
;({ node } = context)
|
;({ node } = context)
|
||||||
if (
|
if (
|
||||||
|
@ -62,6 +64,7 @@ export const transformElement: NodeTransform = (node, context) => {
|
||||||
context as TransformContext<ElementNode>,
|
context as TransformContext<ElementNode>,
|
||||||
isComponent,
|
isComponent,
|
||||||
isDynamicComponent,
|
isDynamicComponent,
|
||||||
|
getEffectIndex,
|
||||||
)
|
)
|
||||||
|
|
||||||
let { parent } = context
|
let { parent } = context
|
||||||
|
@ -78,13 +81,23 @@ export const transformElement: NodeTransform = (node, context) => {
|
||||||
parent.node.children.filter(child => child.type !== NodeTypes.COMMENT)
|
parent.node.children.filter(child => child.type !== NodeTypes.COMMENT)
|
||||||
.length === 1
|
.length === 1
|
||||||
|
|
||||||
;(isComponent ? transformComponentElement : transformNativeElement)(
|
if (isComponent) {
|
||||||
node as any,
|
transformComponentElement(
|
||||||
propsResult,
|
node as ComponentNode,
|
||||||
singleRoot,
|
propsResult,
|
||||||
context as TransformContext<ElementNode>,
|
singleRoot,
|
||||||
isDynamicComponent,
|
context,
|
||||||
)
|
isDynamicComponent,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
transformNativeElement(
|
||||||
|
node as PlainElementNode,
|
||||||
|
propsResult,
|
||||||
|
singleRoot,
|
||||||
|
context,
|
||||||
|
getEffectIndex,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,6 +138,13 @@ function transformComponentElement(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (asset) {
|
if (asset) {
|
||||||
|
// self referencing component (inferred from filename)
|
||||||
|
if (context.selfName && capitalize(camelize(tag)) === context.selfName) {
|
||||||
|
// generators/block.ts has special check for __self postfix when generating
|
||||||
|
// component imports, which will pass additional `maybeSelfReference` flag
|
||||||
|
// to `resolveComponent`.
|
||||||
|
tag += `__self`
|
||||||
|
}
|
||||||
context.component.add(tag)
|
context.component.add(tag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +156,7 @@ function transformComponentElement(
|
||||||
tag,
|
tag,
|
||||||
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
|
props: propsResult[0] ? propsResult[1] : [propsResult[1]],
|
||||||
asset,
|
asset,
|
||||||
root: singleRoot,
|
root: singleRoot && !context.inVFor,
|
||||||
slots: [...context.slots],
|
slots: [...context.slots],
|
||||||
once: context.inVOnce,
|
once: context.inVOnce,
|
||||||
dynamic: dynamicComponent,
|
dynamic: dynamicComponent,
|
||||||
|
@ -182,7 +202,8 @@ function transformNativeElement(
|
||||||
node: PlainElementNode,
|
node: PlainElementNode,
|
||||||
propsResult: PropsResult,
|
propsResult: PropsResult,
|
||||||
singleRoot: boolean,
|
singleRoot: boolean,
|
||||||
context: TransformContext<ElementNode>,
|
context: TransformContext,
|
||||||
|
getEffectIndex: () => number,
|
||||||
) {
|
) {
|
||||||
const { tag } = node
|
const { tag } = node
|
||||||
const { scopeId } = context.options
|
const { scopeId } = context.options
|
||||||
|
@ -195,12 +216,16 @@ function transformNativeElement(
|
||||||
const dynamicProps: string[] = []
|
const dynamicProps: string[] = []
|
||||||
if (propsResult[0] /* dynamic props */) {
|
if (propsResult[0] /* dynamic props */) {
|
||||||
const [, dynamicArgs, expressions] = propsResult
|
const [, dynamicArgs, expressions] = propsResult
|
||||||
context.registerEffect(expressions, {
|
context.registerEffect(
|
||||||
type: IRNodeTypes.SET_DYNAMIC_PROPS,
|
expressions,
|
||||||
element: context.reference(),
|
{
|
||||||
props: dynamicArgs,
|
type: IRNodeTypes.SET_DYNAMIC_PROPS,
|
||||||
root: singleRoot,
|
element: context.reference(),
|
||||||
})
|
props: dynamicArgs,
|
||||||
|
root: singleRoot,
|
||||||
|
},
|
||||||
|
getEffectIndex,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
for (const prop of propsResult[1]) {
|
for (const prop of propsResult[1]) {
|
||||||
const { key, values } = prop
|
const { key, values } = prop
|
||||||
|
@ -209,13 +234,17 @@ function transformNativeElement(
|
||||||
if (values[0].content) template += `="${values[0].content}"`
|
if (values[0].content) template += `="${values[0].content}"`
|
||||||
} else {
|
} else {
|
||||||
dynamicProps.push(key.content)
|
dynamicProps.push(key.content)
|
||||||
context.registerEffect(values, {
|
context.registerEffect(
|
||||||
type: IRNodeTypes.SET_PROP,
|
values,
|
||||||
element: context.reference(),
|
{
|
||||||
prop,
|
type: IRNodeTypes.SET_PROP,
|
||||||
root: singleRoot,
|
element: context.reference(),
|
||||||
tag,
|
prop,
|
||||||
})
|
root: singleRoot,
|
||||||
|
tag,
|
||||||
|
},
|
||||||
|
getEffectIndex,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -252,6 +281,7 @@ export function buildProps(
|
||||||
context: TransformContext<ElementNode>,
|
context: TransformContext<ElementNode>,
|
||||||
isComponent: boolean,
|
isComponent: boolean,
|
||||||
isDynamicComponent?: boolean,
|
isDynamicComponent?: boolean,
|
||||||
|
getEffectIndex?: () => number,
|
||||||
): PropsResult {
|
): PropsResult {
|
||||||
const props = node.props as (VaporDirectiveNode | AttributeNode)[]
|
const props = node.props as (VaporDirectiveNode | AttributeNode)[]
|
||||||
if (props.length === 0) return [false, []]
|
if (props.length === 0) return [false, []]
|
||||||
|
@ -298,12 +328,12 @@ export function buildProps(
|
||||||
} else {
|
} else {
|
||||||
context.registerEffect(
|
context.registerEffect(
|
||||||
[prop.exp],
|
[prop.exp],
|
||||||
|
|
||||||
{
|
{
|
||||||
type: IRNodeTypes.SET_DYNAMIC_EVENTS,
|
type: IRNodeTypes.SET_DYNAMIC_EVENTS,
|
||||||
element: context.reference(),
|
element: context.reference(),
|
||||||
event: prop.exp,
|
event: prop.exp,
|
||||||
},
|
},
|
||||||
|
getEffectIndex,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -9,7 +9,7 @@ import {
|
||||||
createSimpleExpression,
|
createSimpleExpression,
|
||||||
isStaticArgOf,
|
isStaticArgOf,
|
||||||
isStaticExp,
|
isStaticExp,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-dom'
|
||||||
import type { NodeTransform, TransformContext } from '../transform'
|
import type { NodeTransform, TransformContext } from '../transform'
|
||||||
import {
|
import {
|
||||||
type BlockIRNode,
|
type BlockIRNode,
|
||||||
|
|
|
@ -23,6 +23,13 @@ const seen = new WeakMap<
|
||||||
WeakSet<TemplateChildNode | RootNode>
|
WeakSet<TemplateChildNode | RootNode>
|
||||||
>()
|
>()
|
||||||
|
|
||||||
|
export function markNonTemplate(
|
||||||
|
node: TemplateChildNode,
|
||||||
|
context: TransformContext,
|
||||||
|
): void {
|
||||||
|
seen.get(context.root)!.add(node)
|
||||||
|
}
|
||||||
|
|
||||||
export const transformText: NodeTransform = (node, context) => {
|
export const transformText: NodeTransform = (node, context) => {
|
||||||
if (!seen.has(context.root)) seen.set(context.root, new WeakSet())
|
if (!seen.has(context.root)) seen.set(context.root, new WeakSet())
|
||||||
if (seen.get(context.root)!.has(node)) {
|
if (seen.get(context.root)!.has(node)) {
|
||||||
|
@ -68,7 +75,7 @@ export const transformText: NodeTransform = (node, context) => {
|
||||||
prev.type === NodeTypes.TEXT
|
prev.type === NodeTypes.TEXT
|
||||||
) {
|
) {
|
||||||
// mark leading text node for skipping
|
// mark leading text node for skipping
|
||||||
seen.get(context.root)!.add(prev)
|
markNonTemplate(prev, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -143,7 +150,7 @@ function processTextContainer(
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTextLikeExpression(node: TextLike, context: TransformContext) {
|
function createTextLikeExpression(node: TextLike, context: TransformContext) {
|
||||||
seen.get(context.root)!.add(node)
|
markNonTemplate(node, context)
|
||||||
if (node.type === NodeTypes.TEXT) {
|
if (node.type === NodeTypes.TEXT) {
|
||||||
return createSimpleExpression(node.content, true, node.loc)
|
return createSimpleExpression(node.content, true, node.loc)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -29,7 +29,6 @@ export const newBlock = (node: BlockIRNode['node']): BlockIRNode => ({
|
||||||
effect: [],
|
effect: [],
|
||||||
operation: [],
|
operation: [],
|
||||||
returns: [],
|
returns: [],
|
||||||
expressions: [],
|
|
||||||
tempId: 0,
|
tempId: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,13 @@ export function processIf(
|
||||||
if (siblings) {
|
if (siblings) {
|
||||||
let i = siblings.length
|
let i = siblings.length
|
||||||
while (i--) {
|
while (i--) {
|
||||||
if (siblings[i].operation) lastIfNode = siblings[i].operation
|
if (
|
||||||
|
siblings[i].operation &&
|
||||||
|
siblings[i].operation!.type === IRNodeTypes.IF
|
||||||
|
) {
|
||||||
|
lastIfNode = siblings[i].operation
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ import {
|
||||||
createCompilerError,
|
createCompilerError,
|
||||||
isTemplateNode,
|
isTemplateNode,
|
||||||
isVSlot,
|
isVSlot,
|
||||||
} from '@vue/compiler-core'
|
} from '@vue/compiler-dom'
|
||||||
import type { NodeTransform, TransformContext } from '../transform'
|
import type { NodeTransform, TransformContext } from '../transform'
|
||||||
import { newBlock } from './utils'
|
import { newBlock } from './utils'
|
||||||
import {
|
import {
|
||||||
|
@ -24,6 +24,7 @@ import {
|
||||||
type VaporDirectiveNode,
|
type VaporDirectiveNode,
|
||||||
} from '../ir'
|
} from '../ir'
|
||||||
import { findDir, resolveExpression } from '../utils'
|
import { findDir, resolveExpression } from '../utils'
|
||||||
|
import { markNonTemplate } from './transformText'
|
||||||
|
|
||||||
export const transformVSlot: NodeTransform = (node, context) => {
|
export const transformVSlot: NodeTransform = (node, context) => {
|
||||||
if (node.type !== NodeTypes.ELEMENT) return
|
if (node.type !== NodeTypes.ELEMENT) return
|
||||||
|
@ -66,11 +67,21 @@ function transformComponentSlot(
|
||||||
) {
|
) {
|
||||||
const { children } = node
|
const { children } = node
|
||||||
const arg = dir && dir.arg
|
const arg = dir && dir.arg
|
||||||
const nonSlotTemplateChildren = children.filter(
|
|
||||||
n =>
|
// whitespace: 'preserve'
|
||||||
isNonWhitespaceContent(node) &&
|
const emptyTextNodes: TemplateChildNode[] = []
|
||||||
!(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot)),
|
const nonSlotTemplateChildren = children.filter(n => {
|
||||||
)
|
if (isNonWhitespaceContent(n)) {
|
||||||
|
return !(n.type === NodeTypes.ELEMENT && n.props.some(isVSlot))
|
||||||
|
} else {
|
||||||
|
emptyTextNodes.push(n)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (!nonSlotTemplateChildren.length) {
|
||||||
|
emptyTextNodes.forEach(n => {
|
||||||
|
markNonTemplate(n, context)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const [block, onExit] = createSlotBlock(node, dir, context)
|
const [block, onExit] = createSlotBlock(node, dir, context)
|
||||||
|
|
||||||
|
|
|
@ -1017,6 +1017,17 @@ describe('reactivity/computed', () => {
|
||||||
expect(cValue.value).toBe(1)
|
expect(cValue.value).toBe(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not recompute if computed does not track reactive data', async () => {
|
||||||
|
const spy = vi.fn()
|
||||||
|
const c1 = computed(() => spy())
|
||||||
|
|
||||||
|
c1.value
|
||||||
|
ref(0).value++ // update globalVersion
|
||||||
|
c1.value
|
||||||
|
|
||||||
|
expect(spy).toBeCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('computed should remain live after losing all subscribers', () => {
|
test('computed should remain live after losing all subscribers', () => {
|
||||||
const state = reactive({ a: 1 })
|
const state = reactive({ a: 1 })
|
||||||
const p = computed(() => state.a + 1)
|
const p = computed(() => state.a + 1)
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/reactivity",
|
"name": "@vue/reactivity",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/reactivity",
|
"description": "@vue/reactivity",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/reactivity.esm-bundler.js",
|
"module": "dist/reactivity.esm-bundler.js",
|
||||||
|
|
|
@ -119,12 +119,6 @@ export class ComputedRefImpl<T = any> implements Dependency, Subscriber {
|
||||||
// dev only
|
// dev only
|
||||||
onTrigger?: (event: DebuggerEvent) => void
|
onTrigger?: (event: DebuggerEvent) => void
|
||||||
|
|
||||||
/**
|
|
||||||
* Dev only
|
|
||||||
* @internal
|
|
||||||
*/
|
|
||||||
_warnRecursive?: boolean
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public fn: ComputedGetter<T>,
|
public fn: ComputedGetter<T>,
|
||||||
private readonly setter: ComputedSetter<T> | undefined,
|
private readonly setter: ComputedSetter<T> | undefined,
|
||||||
|
|
|
@ -324,4 +324,98 @@ describe('component: slots', () => {
|
||||||
'Slot "default" invoked outside of the render function',
|
'Slot "default" invoked outside of the render function',
|
||||||
).not.toHaveBeenWarned()
|
).not.toHaveBeenWarned()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('basic warn', () => {
|
||||||
|
const Comp = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
slots.default && slots.default()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
return () => h(Comp, () => h('div'))
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('basic warn when mounting another app in setup', () => {
|
||||||
|
const Comp = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
slots.default?.()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const mountComp = () => {
|
||||||
|
createApp({
|
||||||
|
setup() {
|
||||||
|
return () => h(Comp, () => 'msg')
|
||||||
|
},
|
||||||
|
}).mount(nodeOps.createElement('div'))
|
||||||
|
}
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
mountComp()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should not warn when render in setup', () => {
|
||||||
|
const container = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
return () => slots.default && slots.default()
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = h(container, null, () => h('div'))
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
render(h(comp), nodeOps.createElement('div'))
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).not.toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('basic warn when render in setup', () => {
|
||||||
|
const container = {
|
||||||
|
setup(_: any, { slots }: any) {
|
||||||
|
slots.default && slots.default()
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const comp = h(container, null, () => h('div'))
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
render(h(comp), nodeOps.createElement('div'))
|
||||||
|
return () => null
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
createApp(App).mount(nodeOps.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'Slot "default" invoked outside of the render function',
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1198,4 +1198,51 @@ describe('BaseTransition', () => {
|
||||||
test('should not error on KeepAlive w/ function children', () => {
|
test('should not error on KeepAlive w/ function children', () => {
|
||||||
expect(() => mount({}, () => () => h('div'), true)).not.toThrow()
|
expect(() => mount({}, () => () => h('div'), true)).not.toThrow()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12465
|
||||||
|
test('mode: "out-in" w/ KeepAlive + fallthrough attrs (prod mode)', async () => {
|
||||||
|
__DEV__ = false
|
||||||
|
async function testOutIn({ trueBranch, falseBranch }: ToggleOptions) {
|
||||||
|
const toggle = ref(true)
|
||||||
|
const { props, cbs } = mockProps({ mode: 'out-in' }, true)
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const App = {
|
||||||
|
render() {
|
||||||
|
return h(
|
||||||
|
BaseTransition,
|
||||||
|
{
|
||||||
|
...props,
|
||||||
|
class: 'test',
|
||||||
|
},
|
||||||
|
() =>
|
||||||
|
h(KeepAlive, null, toggle.value ? trueBranch() : falseBranch()),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
render(h(App), root)
|
||||||
|
|
||||||
|
expect(serializeInner(root)).toBe(`<div class="test">0</div>`)
|
||||||
|
|
||||||
|
// trigger toggle
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(props.onBeforeLeave).toHaveBeenCalledTimes(1)
|
||||||
|
expect(serialize((props.onBeforeLeave as any).mock.calls[0][0])).toBe(
|
||||||
|
`<div class="test">0</div>`,
|
||||||
|
)
|
||||||
|
expect(props.onLeave).toHaveBeenCalledTimes(1)
|
||||||
|
expect(serialize((props.onLeave as any).mock.calls[0][0])).toBe(
|
||||||
|
`<div class="test">0</div>`,
|
||||||
|
)
|
||||||
|
expect(props.onAfterLeave).not.toHaveBeenCalled()
|
||||||
|
// enter should not have started
|
||||||
|
expect(props.onBeforeEnter).not.toHaveBeenCalled()
|
||||||
|
expect(props.onEnter).not.toHaveBeenCalled()
|
||||||
|
expect(props.onAfterEnter).not.toHaveBeenCalled()
|
||||||
|
cbs.doneLeave[`<div class="test">0</div>`]()
|
||||||
|
expect(serializeInner(root)).toBe(`<span class="test">0</span>`)
|
||||||
|
}
|
||||||
|
await runTestWithKeepAlive(testOutIn)
|
||||||
|
__DEV__ = true
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -10,14 +10,28 @@ import {
|
||||||
markRaw,
|
markRaw,
|
||||||
nextTick,
|
nextTick,
|
||||||
nodeOps,
|
nodeOps,
|
||||||
|
onMounted,
|
||||||
h as originalH,
|
h as originalH,
|
||||||
ref,
|
ref,
|
||||||
render,
|
render,
|
||||||
|
serialize,
|
||||||
serializeInner,
|
serializeInner,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '@vue/runtime-test'
|
} from '@vue/runtime-test'
|
||||||
import { Fragment, createCommentVNode, createVNode } from '../../src/vnode'
|
import {
|
||||||
|
Fragment,
|
||||||
|
createBlock,
|
||||||
|
createCommentVNode,
|
||||||
|
createTextVNode,
|
||||||
|
createVNode,
|
||||||
|
openBlock,
|
||||||
|
} from '../../src/vnode'
|
||||||
|
import { toDisplayString } from '@vue/shared'
|
||||||
import { compile, createApp as createDOMApp, render as domRender } from 'vue'
|
import { compile, createApp as createDOMApp, render as domRender } from 'vue'
|
||||||
|
import type { HMRRuntime } from '../../src/hmr'
|
||||||
|
|
||||||
|
declare var __VUE_HMR_RUNTIME__: HMRRuntime
|
||||||
|
const { rerender, createRecord } = __VUE_HMR_RUNTIME__
|
||||||
|
|
||||||
describe('renderer: teleport', () => {
|
describe('renderer: teleport', () => {
|
||||||
describe('eager mode', () => {
|
describe('eager mode', () => {
|
||||||
|
@ -243,6 +257,39 @@ describe('renderer: teleport', () => {
|
||||||
expect(serializeInner(target)).toBe(`teleported`)
|
expect(serializeInner(target)).toBe(`teleported`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should traverse comment node after updating in optimize mode', async () => {
|
||||||
|
const target = nodeOps.createElement('div')
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const count = ref(0)
|
||||||
|
let teleport
|
||||||
|
|
||||||
|
__DEV__ = false
|
||||||
|
render(
|
||||||
|
h(() => {
|
||||||
|
teleport =
|
||||||
|
(openBlock(),
|
||||||
|
createBlock(Teleport, { to: target }, [
|
||||||
|
createCommentVNode('comment in teleport'),
|
||||||
|
]))
|
||||||
|
return h('div', null, [
|
||||||
|
createTextVNode(toDisplayString(count.value)),
|
||||||
|
teleport,
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
root,
|
||||||
|
)
|
||||||
|
const commentNode = teleport!.children[0].el
|
||||||
|
expect(serializeInner(root)).toBe(`<div>0</div>`)
|
||||||
|
expect(serializeInner(target)).toBe(`<!--comment in teleport-->`)
|
||||||
|
expect(serialize(commentNode)).toBe(`<!--comment in teleport-->`)
|
||||||
|
|
||||||
|
count.value = 1
|
||||||
|
await nextTick()
|
||||||
|
__DEV__ = true
|
||||||
|
expect(serializeInner(root)).toBe(`<div>1</div>`)
|
||||||
|
expect(teleport!.children[0].el).toBe(commentNode)
|
||||||
|
})
|
||||||
|
|
||||||
test('should remove children when unmounted', () => {
|
test('should remove children when unmounted', () => {
|
||||||
const target = nodeOps.createElement('div')
|
const target = nodeOps.createElement('div')
|
||||||
const root = nodeOps.createElement('div')
|
const root = nodeOps.createElement('div')
|
||||||
|
@ -269,6 +316,34 @@ describe('renderer: teleport', () => {
|
||||||
testUnmount({ to: null, disabled: true })
|
testUnmount({ to: null, disabled: true })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #10747
|
||||||
|
test('should unmount correctly when using top level comment in teleport', async () => {
|
||||||
|
const target = nodeOps.createElement('div')
|
||||||
|
const root = nodeOps.createElement('div')
|
||||||
|
const count = ref(0)
|
||||||
|
|
||||||
|
__DEV__ = false
|
||||||
|
render(
|
||||||
|
h(() => {
|
||||||
|
return h('div', null, [
|
||||||
|
createTextVNode(toDisplayString(count.value)),
|
||||||
|
(openBlock(),
|
||||||
|
createBlock(Teleport, { to: target }, [
|
||||||
|
createCommentVNode('comment in teleport'),
|
||||||
|
])),
|
||||||
|
])
|
||||||
|
}),
|
||||||
|
root,
|
||||||
|
)
|
||||||
|
|
||||||
|
count.value = 1
|
||||||
|
|
||||||
|
await nextTick()
|
||||||
|
__DEV__ = true
|
||||||
|
render(null, root)
|
||||||
|
expect(root.children.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
test('component with multi roots should be removed when unmounted', () => {
|
test('component with multi roots should be removed when unmounted', () => {
|
||||||
const target = nodeOps.createElement('div')
|
const target = nodeOps.createElement('div')
|
||||||
const root = nodeOps.createElement('div')
|
const root = nodeOps.createElement('div')
|
||||||
|
@ -741,4 +816,56 @@ describe('renderer: teleport', () => {
|
||||||
expect(tRefInMounted).toBe(target.children[1])
|
expect(tRefInMounted).toBe(target.children[1])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test('handle update and hmr rerender', async () => {
|
||||||
|
const target = document.createElement('div')
|
||||||
|
const root = document.createElement('div')
|
||||||
|
|
||||||
|
const Comp = {
|
||||||
|
setup() {
|
||||||
|
const cls = ref('foo')
|
||||||
|
onMounted(() => {
|
||||||
|
// trigger update
|
||||||
|
cls.value = 'bar'
|
||||||
|
})
|
||||||
|
return { cls, target }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Teleport :to="target">
|
||||||
|
<div :class="cls">
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Teleport>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const appId = 'test-app-id'
|
||||||
|
const App = {
|
||||||
|
__hmrId: appId,
|
||||||
|
components: { Comp },
|
||||||
|
render() {
|
||||||
|
return originalH(Comp, null, { default: () => originalH('div', 'foo') })
|
||||||
|
},
|
||||||
|
}
|
||||||
|
createRecord(appId, App)
|
||||||
|
|
||||||
|
domRender(originalH(App), root)
|
||||||
|
expect(target.innerHTML).toBe(
|
||||||
|
'<div class="foo"><div><div>foo</div></div></div>',
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(target.innerHTML).toBe(
|
||||||
|
'<div class="bar"><div><div>foo</div></div></div>',
|
||||||
|
)
|
||||||
|
|
||||||
|
rerender(appId, () =>
|
||||||
|
originalH(Comp, null, { default: () => originalH('div', 'bar') }),
|
||||||
|
)
|
||||||
|
await nextTick()
|
||||||
|
expect(target.innerHTML).toBe(
|
||||||
|
'<div class="bar"><div><div>bar</div></div></div>',
|
||||||
|
)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
import { isReactive, reactive, shallowReactive } from '../../src/index'
|
import {
|
||||||
|
effect,
|
||||||
|
isReactive,
|
||||||
|
reactive,
|
||||||
|
readonly,
|
||||||
|
shallowReactive,
|
||||||
|
} from '../../src/index'
|
||||||
import { renderList } from '../../src/helpers/renderList'
|
import { renderList } from '../../src/helpers/renderList'
|
||||||
|
|
||||||
describe('renderList', () => {
|
describe('renderList', () => {
|
||||||
|
@ -65,4 +71,31 @@ describe('renderList', () => {
|
||||||
const shallowReactiveArray = shallowReactive([{ foo: 1 }])
|
const shallowReactiveArray = shallowReactive([{ foo: 1 }])
|
||||||
expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
|
expect(renderList(shallowReactiveArray, isReactive)).toEqual([false])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not allow mutation', () => {
|
||||||
|
const arr = readonly(reactive([{ foo: 1 }]))
|
||||||
|
expect(
|
||||||
|
renderList(arr, item => {
|
||||||
|
;(item as any).foo = 0
|
||||||
|
return item.foo
|
||||||
|
}),
|
||||||
|
).toEqual([1])
|
||||||
|
expect(
|
||||||
|
`Set operation on key "foo" failed: target is readonly.`,
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should trigger effect for deep mutations in readonly reactive arrays', () => {
|
||||||
|
const arr = reactive([{ foo: 1 }])
|
||||||
|
const readonlyArr = readonly(arr)
|
||||||
|
|
||||||
|
let dummy
|
||||||
|
effect(() => {
|
||||||
|
dummy = renderList(readonlyArr, item => item.foo)
|
||||||
|
})
|
||||||
|
expect(dummy).toEqual([1])
|
||||||
|
|
||||||
|
arr[0].foo = 2
|
||||||
|
expect(dummy).toEqual([2])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -32,10 +32,13 @@ import {
|
||||||
withCtx,
|
withCtx,
|
||||||
withDirectives,
|
withDirectives,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
|
import type { HMRRuntime } from '../src/hmr'
|
||||||
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
import { type SSRContext, renderToString } from '@vue/server-renderer'
|
||||||
import { PatchFlags, normalizeStyle } from '@vue/shared'
|
import { PatchFlags, normalizeStyle } from '@vue/shared'
|
||||||
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
import { vShowOriginalDisplay } from '../../runtime-dom/src/directives/vShow'
|
||||||
import { expect } from 'vitest'
|
|
||||||
|
declare var __VUE_HMR_RUNTIME__: HMRRuntime
|
||||||
|
const { createRecord, reload } = __VUE_HMR_RUNTIME__
|
||||||
|
|
||||||
function mountWithHydration(html: string, render: () => any) {
|
function mountWithHydration(html: string, render: () => any) {
|
||||||
const container = document.createElement('div')
|
const container = document.createElement('div')
|
||||||
|
@ -1843,6 +1846,60 @@ describe('SSR hydration', () => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('hmr reload child wrapped in KeepAlive', async () => {
|
||||||
|
const id = 'child-reload'
|
||||||
|
const Child = {
|
||||||
|
__hmrId: id,
|
||||||
|
template: `<div>foo</div>`,
|
||||||
|
}
|
||||||
|
createRecord(id, Child)
|
||||||
|
|
||||||
|
const appId = 'test-app-id'
|
||||||
|
const App = {
|
||||||
|
__hmrId: appId,
|
||||||
|
components: { Child },
|
||||||
|
template: `
|
||||||
|
<div>
|
||||||
|
<KeepAlive>
|
||||||
|
<Child />
|
||||||
|
</KeepAlive>
|
||||||
|
</div>
|
||||||
|
`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.innerHTML = await renderToString(h(App))
|
||||||
|
createSSRApp(App).mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<div><div>foo</div></div>')
|
||||||
|
|
||||||
|
reload(id, {
|
||||||
|
__hmrId: id,
|
||||||
|
template: `<div>bar</div>`,
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toBe('<div><div>bar</div></div>')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('hmr root reload', async () => {
|
||||||
|
const appId = 'test-app-id'
|
||||||
|
const App = {
|
||||||
|
__hmrId: appId,
|
||||||
|
template: `<div>foo</div>`,
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
root.innerHTML = await renderToString(h(App))
|
||||||
|
createSSRApp(App).mount(root)
|
||||||
|
expect(root.innerHTML).toBe('<div>foo</div>')
|
||||||
|
|
||||||
|
reload(appId, {
|
||||||
|
__hmrId: appId,
|
||||||
|
template: `<div>bar</div>`,
|
||||||
|
})
|
||||||
|
await nextTick()
|
||||||
|
expect(root.innerHTML).toBe('<div>bar</div>')
|
||||||
|
})
|
||||||
|
|
||||||
describe('mismatch handling', () => {
|
describe('mismatch handling', () => {
|
||||||
test('text node', () => {
|
test('text node', () => {
|
||||||
const { container } = mountWithHydration(`foo`, () => 'bar')
|
const { container } = mountWithHydration(`foo`, () => 'bar')
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-core",
|
"name": "@vue/runtime-core",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/runtime-core",
|
"description": "@vue/runtime-core",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-core.esm-bundler.js",
|
"module": "dist/runtime-core.esm-bundler.js",
|
||||||
|
|
|
@ -1,17 +1,10 @@
|
||||||
import { type ComputedRefImpl, computed as _computed } from '@vue/reactivity'
|
import { computed as _computed } from '@vue/reactivity'
|
||||||
import { getCurrentInstance, isInSSRComponentSetup } from './component'
|
import { isInSSRComponentSetup } from './component'
|
||||||
|
|
||||||
export const computed: typeof _computed = (
|
export const computed: typeof _computed = (
|
||||||
getterOrOptions: any,
|
getterOrOptions: any,
|
||||||
debugOptions?: any,
|
debugOptions?: any,
|
||||||
) => {
|
) => {
|
||||||
// @ts-expect-error
|
// @ts-expect-error
|
||||||
const c = _computed(getterOrOptions, debugOptions, isInSSRComponentSetup)
|
return _computed(getterOrOptions, debugOptions, isInSSRComponentSetup) as any
|
||||||
if (__DEV__) {
|
|
||||||
const i = getCurrentInstance()
|
|
||||||
if (i && i.appContext.config.warnRecursiveComputed) {
|
|
||||||
;(c as unknown as ComputedRefImpl<any>)._warnRecursive = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return c as any
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -140,12 +140,6 @@ export interface GenericAppConfig {
|
||||||
trace: string,
|
trace: string,
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
/**
|
|
||||||
* TODO document for 3.5
|
|
||||||
* Enable warnings for computed getters that recursively trigger itself.
|
|
||||||
*/
|
|
||||||
warnRecursiveComputed?: boolean
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether to throw unhandled errors in production.
|
* Whether to throw unhandled errors in production.
|
||||||
* Default is `false` to avoid crashing on any error (and only logs it)
|
* Default is `false` to avoid crashing on any error (and only logs it)
|
||||||
|
|
|
@ -841,7 +841,7 @@ export function setupComponent(
|
||||||
vi(instance)
|
vi(instance)
|
||||||
} else {
|
} else {
|
||||||
initProps(instance, props, isStateful, isSSR)
|
initProps(instance, props, isStateful, isSSR)
|
||||||
initSlots(instance, children, optimized)
|
initSlots(instance, children, optimized || isSSR)
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupResult = isStateful
|
const setupResult = isStateful
|
||||||
|
|
|
@ -17,7 +17,11 @@ import {
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import { warn } from './warning'
|
import { warn } from './warning'
|
||||||
import { isKeepAlive } from './components/KeepAlive'
|
import { isKeepAlive } from './components/KeepAlive'
|
||||||
import { type ContextualRenderFn, withCtx } from './componentRenderContext'
|
import {
|
||||||
|
type ContextualRenderFn,
|
||||||
|
currentRenderingInstance,
|
||||||
|
withCtx,
|
||||||
|
} from './componentRenderContext'
|
||||||
import { isHmrUpdating } from './hmr'
|
import { isHmrUpdating } from './hmr'
|
||||||
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
|
import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig'
|
||||||
import { TriggerOpTypes, trigger } from '@vue/reactivity'
|
import { TriggerOpTypes, trigger } from '@vue/reactivity'
|
||||||
|
@ -75,6 +79,11 @@ export type RawSlots = {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_?: SlotFlags
|
_?: SlotFlags
|
||||||
|
/**
|
||||||
|
* cache indexes for slot content
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
__?: number[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
const isInternalKey = (key: string) => key[0] === '_' || key === '$stable'
|
||||||
|
@ -98,7 +107,8 @@ const normalizeSlot = (
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
currentInstance &&
|
currentInstance &&
|
||||||
!currentInstance.vapor &&
|
!currentInstance.vapor &&
|
||||||
(!ctx || ctx.root === (currentInstance as ComponentInternalInstance).root)
|
!(ctx === null && currentRenderingInstance) &&
|
||||||
|
!(ctx && ctx.root !== currentInstance.root)
|
||||||
) {
|
) {
|
||||||
warn(
|
warn(
|
||||||
`Slot "${key}" invoked outside of the render function: ` +
|
`Slot "${key}" invoked outside of the render function: ` +
|
||||||
|
@ -171,7 +181,7 @@ const assignSlots = (
|
||||||
// when rendering the optimized slots by manually written render function,
|
// when rendering the optimized slots by manually written render function,
|
||||||
// do not copy the `slots._` compiler flag so that `renderSlot` creates
|
// do not copy the `slots._` compiler flag so that `renderSlot` creates
|
||||||
// slot Fragment with BAIL patchFlag to force full updates
|
// slot Fragment with BAIL patchFlag to force full updates
|
||||||
if (optimized || key !== '_') {
|
if (optimized || !isInternalKey(key)) {
|
||||||
slots[key] = children[key]
|
slots[key] = children[key]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -501,9 +501,8 @@ function getInnerChild(vnode: VNode): VNode | undefined {
|
||||||
|
|
||||||
return vnode
|
return vnode
|
||||||
}
|
}
|
||||||
// #7121 ensure get the child component subtree in case
|
// #7121,#12465 get the component subtree if it's been mounted
|
||||||
// it's been replaced during HMR
|
if (vnode.component) {
|
||||||
if (__DEV__ && vnode.component) {
|
|
||||||
return vnode.component.subTree
|
return vnode.component.subTree
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -220,7 +220,8 @@ export const TeleportImpl = {
|
||||||
// even in block tree mode we need to make sure all root-level nodes
|
// even in block tree mode we need to make sure all root-level nodes
|
||||||
// in the teleport inherit previous DOM references so that they can
|
// in the teleport inherit previous DOM references so that they can
|
||||||
// be moved in future patches.
|
// be moved in future patches.
|
||||||
traverseStaticChildren(n1, n2, true)
|
// in dev mode, deep traversal is necessary for HMR
|
||||||
|
traverseStaticChildren(n1, n2, !__DEV__)
|
||||||
} else if (!optimized) {
|
} else if (!optimized) {
|
||||||
patchChildren(
|
patchChildren(
|
||||||
n1,
|
n1,
|
||||||
|
|
|
@ -1,9 +1,11 @@
|
||||||
import type { VNode, VNodeChild } from '../vnode'
|
import type { VNode, VNodeChild } from '../vnode'
|
||||||
import {
|
import {
|
||||||
isReactive,
|
isReactive,
|
||||||
|
isReadonly,
|
||||||
isShallow,
|
isShallow,
|
||||||
shallowReadArray,
|
shallowReadArray,
|
||||||
toReactive,
|
toReactive,
|
||||||
|
toReadonly,
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import { isArray, isObject, isString } from '@vue/shared'
|
import { isArray, isObject, isString } from '@vue/shared'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
@ -69,14 +71,20 @@ export function renderList(
|
||||||
if (sourceIsArray || isString(source)) {
|
if (sourceIsArray || isString(source)) {
|
||||||
const sourceIsReactiveArray = sourceIsArray && isReactive(source)
|
const sourceIsReactiveArray = sourceIsArray && isReactive(source)
|
||||||
let needsWrap = false
|
let needsWrap = false
|
||||||
|
let isReadonlySource = false
|
||||||
if (sourceIsReactiveArray) {
|
if (sourceIsReactiveArray) {
|
||||||
needsWrap = !isShallow(source)
|
needsWrap = !isShallow(source)
|
||||||
|
isReadonlySource = isReadonly(source)
|
||||||
source = shallowReadArray(source)
|
source = shallowReadArray(source)
|
||||||
}
|
}
|
||||||
ret = new Array(source.length)
|
ret = new Array(source.length)
|
||||||
for (let i = 0, l = source.length; i < l; i++) {
|
for (let i = 0, l = source.length; i < l; i++) {
|
||||||
ret[i] = renderItem(
|
ret[i] = renderItem(
|
||||||
needsWrap ? toReactive(source[i]) : source[i],
|
needsWrap
|
||||||
|
? isReadonlySource
|
||||||
|
? toReadonly(toReactive(source[i]))
|
||||||
|
: toReactive(source[i])
|
||||||
|
: source[i],
|
||||||
i,
|
i,
|
||||||
undefined,
|
undefined,
|
||||||
cached && cached[i],
|
cached && cached[i],
|
||||||
|
|
|
@ -575,3 +575,7 @@ export {
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export { devtoolsComponentAdded } from './devtools'
|
export { devtoolsComponentAdded } from './devtools'
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export { createInternalObject } from './internalObject'
|
||||||
|
|
|
@ -1257,12 +1257,12 @@ function baseCreateRenderer(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// avoid hydration for hmr updating
|
||||||
|
if (__DEV__ && isHmrUpdating) initialVNode.el = null
|
||||||
|
|
||||||
// setup() is async. This component relies on async logic to be resolved
|
// setup() is async. This component relies on async logic to be resolved
|
||||||
// before proceeding
|
// before proceeding
|
||||||
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
|
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
|
||||||
// avoid hydration for hmr updating
|
|
||||||
if (__DEV__ && isHmrUpdating) initialVNode.el = null
|
|
||||||
|
|
||||||
parentSuspense &&
|
parentSuspense &&
|
||||||
parentSuspense.registerDep(instance, setupRenderEffect, optimized)
|
parentSuspense.registerDep(instance, setupRenderEffect, optimized)
|
||||||
|
|
||||||
|
@ -2178,7 +2178,9 @@ function baseCreateRenderer(
|
||||||
|
|
||||||
// unset ref
|
// unset ref
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
|
pauseTracking()
|
||||||
setRef(ref, null, parentSuspense, vnode, true)
|
setRef(ref, null, parentSuspense, vnode, true)
|
||||||
|
resetTracking()
|
||||||
}
|
}
|
||||||
|
|
||||||
// #6593 should clean memo cache when unmount
|
// #6593 should clean memo cache when unmount
|
||||||
|
@ -2359,7 +2361,17 @@ function baseCreateRenderer(
|
||||||
unregisterHMR(instance)
|
unregisterHMR(instance)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { bum, scope, job, subTree, um, m, a } = instance
|
const {
|
||||||
|
bum,
|
||||||
|
scope,
|
||||||
|
job,
|
||||||
|
subTree,
|
||||||
|
um,
|
||||||
|
m,
|
||||||
|
a,
|
||||||
|
parent,
|
||||||
|
slots: { __: slotCacheKeys },
|
||||||
|
} = instance
|
||||||
invalidateMount(m)
|
invalidateMount(m)
|
||||||
invalidateMount(a)
|
invalidateMount(a)
|
||||||
|
|
||||||
|
@ -2368,6 +2380,13 @@ function baseCreateRenderer(
|
||||||
invokeArrayFns(bum)
|
invokeArrayFns(bum)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove slots content from parent renderCache
|
||||||
|
if (parent && isArray(slotCacheKeys)) {
|
||||||
|
slotCacheKeys.forEach(v => {
|
||||||
|
;(parent as ComponentInternalInstance).renderCache[v] = undefined
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
__COMPAT__ &&
|
__COMPAT__ &&
|
||||||
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
isCompatEnabled(DeprecationTypes.INSTANCE_EVENT_HOOKS, instance)
|
||||||
|
@ -2518,9 +2537,12 @@ function baseCreateRenderer(
|
||||||
// HMR root reload
|
// HMR root reload
|
||||||
if (__DEV__) {
|
if (__DEV__) {
|
||||||
app._context.reload = () => {
|
app._context.reload = () => {
|
||||||
|
const cloned = cloneVNode(vnode)
|
||||||
|
// avoid hydration for hmr updating
|
||||||
|
cloned.el = null
|
||||||
// casting to ElementNamespace because TS doesn't guarantee type narrowing
|
// casting to ElementNamespace because TS doesn't guarantee type narrowing
|
||||||
// over function boundaries
|
// over function boundaries
|
||||||
render(cloneVNode(vnode), container, namespace as ElementNamespace)
|
render(cloned, container, namespace as ElementNamespace)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2626,11 +2648,15 @@ export function traverseStaticChildren(
|
||||||
if (c2.type === Text) {
|
if (c2.type === Text) {
|
||||||
c2.el = c1.el
|
c2.el = c1.el
|
||||||
}
|
}
|
||||||
// also inherit for comment nodes, but not placeholders (e.g. v-if which
|
// #2324 also inherit for comment nodes, but not placeholders (e.g. v-if which
|
||||||
// would have received .el during block patch)
|
// would have received .el during block patch)
|
||||||
if (__DEV__ && c2.type === Comment && !c2.el) {
|
if (c2.type === Comment && !c2.el) {
|
||||||
c2.el = c1.el
|
c2.el = c1.el
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (__DEV__) {
|
||||||
|
c2.el && (c2.el.__vnode = c2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/runtime-dom",
|
"name": "@vue/runtime-dom",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/runtime-dom",
|
"description": "@vue/runtime-dom",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/runtime-dom.esm-bundler.js",
|
"module": "dist/runtime-dom.esm-bundler.js",
|
||||||
|
|
|
@ -82,6 +82,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
|
||||||
moveClass,
|
moveClass,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
prevChildren = []
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -111,6 +112,7 @@ const TransitionGroupImpl: ComponentOptions = /*@__PURE__*/ decorate({
|
||||||
})
|
})
|
||||||
el.addEventListener('transitionend', cb)
|
el.addEventListener('transitionend', cb)
|
||||||
})
|
})
|
||||||
|
prevChildren = []
|
||||||
})
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
|
|
@ -1,6 +1,14 @@
|
||||||
import { shallowRef } from '@vue/reactivity'
|
import { ref, shallowRef } from '@vue/reactivity'
|
||||||
import { nextTick } from '@vue/runtime-dom'
|
import { nextTick, resolveDynamicComponent } from '@vue/runtime-dom'
|
||||||
import { createDynamicComponent } from '../src'
|
import {
|
||||||
|
createComponentWithFallback,
|
||||||
|
createDynamicComponent,
|
||||||
|
defineVaporComponent,
|
||||||
|
renderEffect,
|
||||||
|
setHtml,
|
||||||
|
setInsertionState,
|
||||||
|
template,
|
||||||
|
} from '../src'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
@ -54,4 +62,52 @@ describe('api: createDynamicComponent', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expect(html()).toBe('<baz></baz><!--dynamic-component-->')
|
expect(html()).toBe('<baz></baz><!--dynamic-component-->')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('render fallback with insertionState', async () => {
|
||||||
|
const { html, mount } = define({
|
||||||
|
setup() {
|
||||||
|
const html = ref('hi')
|
||||||
|
const n1 = template('<div></div>', true)() as any
|
||||||
|
setInsertionState(n1)
|
||||||
|
const n0 = createComponentWithFallback(
|
||||||
|
resolveDynamicComponent('button') as any,
|
||||||
|
) as any
|
||||||
|
renderEffect(() => setHtml(n0, html.value))
|
||||||
|
return n1
|
||||||
|
},
|
||||||
|
}).create()
|
||||||
|
|
||||||
|
mount()
|
||||||
|
expect(html()).toBe('<div><button>hi</button></div>')
|
||||||
|
})
|
||||||
|
|
||||||
|
test('switch dynamic component children', async () => {
|
||||||
|
const CompA = defineVaporComponent({
|
||||||
|
setup() {
|
||||||
|
return template('<div>A</div>')()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const CompB = defineVaporComponent({
|
||||||
|
setup() {
|
||||||
|
return template('<div>B</div>')()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const current = shallowRef(CompA)
|
||||||
|
const { html } = define({
|
||||||
|
setup() {
|
||||||
|
const t1 = template('<div></div>')
|
||||||
|
const n2 = t1() as any
|
||||||
|
setInsertionState(n2)
|
||||||
|
createDynamicComponent(() => current.value)
|
||||||
|
return n2
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(html()).toBe('<div><div>A</div><!--dynamic-component--></div>')
|
||||||
|
|
||||||
|
current.value = CompB
|
||||||
|
await nextTick()
|
||||||
|
expect(html()).toBe('<div><div>B</div><!--dynamic-component--></div>')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -2,6 +2,7 @@ import {
|
||||||
type Ref,
|
type Ref,
|
||||||
inject,
|
inject,
|
||||||
nextTick,
|
nextTick,
|
||||||
|
onMounted,
|
||||||
onUpdated,
|
onUpdated,
|
||||||
provide,
|
provide,
|
||||||
ref,
|
ref,
|
||||||
|
@ -13,6 +14,7 @@ import {
|
||||||
createIf,
|
createIf,
|
||||||
createTextNode,
|
createTextNode,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
|
setInsertionState,
|
||||||
template,
|
template,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
@ -266,6 +268,29 @@ describe('component', () => {
|
||||||
expect(spy).toHaveBeenCalledTimes(2)
|
expect(spy).toHaveBeenCalledTimes(2)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('properly mount child component when using setInsertionState', async () => {
|
||||||
|
const spy = vi.fn()
|
||||||
|
|
||||||
|
const { component: Comp } = define({
|
||||||
|
setup() {
|
||||||
|
onMounted(spy)
|
||||||
|
return template('<h1>hi</h1>')()
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { host } = define({
|
||||||
|
setup() {
|
||||||
|
const n2 = template('<div></div>', true)()
|
||||||
|
setInsertionState(n2 as any)
|
||||||
|
createComponent(Comp)
|
||||||
|
return n2
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(host.innerHTML).toBe('<div><h1>hi</h1></div>')
|
||||||
|
expect(spy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
it('unmount component', async () => {
|
it('unmount component', async () => {
|
||||||
const { host, app, instance } = define(() => {
|
const { host, app, instance } = define(() => {
|
||||||
const count = ref(0)
|
const count = ref(0)
|
||||||
|
|
|
@ -1,6 +1,15 @@
|
||||||
import { type Ref, nextTick, ref } from '@vue/runtime-dom'
|
import {
|
||||||
|
type Ref,
|
||||||
|
createApp,
|
||||||
|
defineComponent,
|
||||||
|
h,
|
||||||
|
nextTick,
|
||||||
|
ref,
|
||||||
|
} from '@vue/runtime-dom'
|
||||||
import {
|
import {
|
||||||
createComponent,
|
createComponent,
|
||||||
|
createDynamicComponent,
|
||||||
|
createSlot,
|
||||||
defineVaporComponent,
|
defineVaporComponent,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
setClass,
|
setClass,
|
||||||
|
@ -8,6 +17,7 @@ import {
|
||||||
setProp,
|
setProp,
|
||||||
setStyle,
|
setStyle,
|
||||||
template,
|
template,
|
||||||
|
vaporInteropPlugin,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
import { stringifyStyle } from '@vue/shared'
|
import { stringifyStyle } from '@vue/shared'
|
||||||
|
@ -277,7 +287,43 @@ describe('attribute fallthrough', () => {
|
||||||
expect(getCSS()).not.toContain('font-size:bold')
|
expect(getCSS()).not.toContain('font-size:bold')
|
||||||
})
|
})
|
||||||
|
|
||||||
test('parent value should take priority', async () => {
|
it('should fallthrough attrs to dynamic component', async () => {
|
||||||
|
const Comp = defineVaporComponent({
|
||||||
|
setup() {
|
||||||
|
const n1 = createDynamicComponent(
|
||||||
|
() => 'button',
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
default: () => {
|
||||||
|
const n0 = createSlot('default', null)
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
return n1
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { html } = define({
|
||||||
|
setup() {
|
||||||
|
return createComponent(
|
||||||
|
Comp,
|
||||||
|
{
|
||||||
|
class: () => 'foo',
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(html()).toBe(
|
||||||
|
'<button class="foo"><!--slot--></button><!--dynamic-component-->',
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('parent value should take priority', async () => {
|
||||||
const parentVal = ref('parent')
|
const parentVal = ref('parent')
|
||||||
const childVal = ref('child')
|
const childVal = ref('child')
|
||||||
|
|
||||||
|
@ -322,4 +368,81 @@ describe('attribute fallthrough', () => {
|
||||||
expect(el.getAttribute('aria-x')).toBe(parentVal.value)
|
expect(el.getAttribute('aria-x')).toBe(parentVal.value)
|
||||||
expect(el.getAttribute('aria-y')).toBe(parentVal.value)
|
expect(el.getAttribute('aria-y')).toBe(parentVal.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('empty string should not be passed to classList.add', async () => {
|
||||||
|
const t0 = template('<div>', true /* root */)
|
||||||
|
const Child = defineVaporComponent({
|
||||||
|
setup() {
|
||||||
|
const n = t0() as Element
|
||||||
|
renderEffect(() => {
|
||||||
|
setClass(n, {
|
||||||
|
foo: false,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
return n
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const Parent = defineVaporComponent({
|
||||||
|
setup() {
|
||||||
|
return createComponent(
|
||||||
|
Child,
|
||||||
|
{
|
||||||
|
class: () => ({
|
||||||
|
bar: false,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const { host } = define({
|
||||||
|
setup() {
|
||||||
|
return createComponent(Parent)
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
const el = host.children[0]
|
||||||
|
expect(el.classList.length).toBe(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should not fallthrough emit handlers to vdom child', () => {
|
||||||
|
const VDomChild = defineComponent({
|
||||||
|
emits: ['click'],
|
||||||
|
setup(_, { emit }) {
|
||||||
|
return () => h('button', { onClick: () => emit('click') }, 'click me')
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const fn = vi.fn()
|
||||||
|
const VaporChild = defineVaporComponent({
|
||||||
|
emits: ['click'],
|
||||||
|
setup() {
|
||||||
|
return createComponent(
|
||||||
|
VDomChild as any,
|
||||||
|
{ onClick: () => fn },
|
||||||
|
null,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const App = {
|
||||||
|
setup() {
|
||||||
|
return () => h(VaporChild as any)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
createApp(App).use(vaporInteropPlugin).mount(root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toBe('<button>click me</button>')
|
||||||
|
const button = root.querySelector('button')!
|
||||||
|
button.dispatchEvent(new Event('click'))
|
||||||
|
|
||||||
|
// fn should be called once
|
||||||
|
expect(fn).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -497,6 +497,36 @@ describe('component: props', () => {
|
||||||
expect(changeSpy).toHaveBeenCalledTimes(1)
|
expect(changeSpy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('should not warn invalid watch source when directly watching props', async () => {
|
||||||
|
const changeSpy = vi.fn()
|
||||||
|
const { render, html } = define({
|
||||||
|
props: {
|
||||||
|
foo: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props: any) {
|
||||||
|
watch(props, changeSpy)
|
||||||
|
const t0 = template('<h1></h1>')
|
||||||
|
const n0 = t0()
|
||||||
|
renderEffect(() => {
|
||||||
|
setElementText(n0, String(props.foo))
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const foo = ref('foo')
|
||||||
|
render({ foo: () => foo.value })
|
||||||
|
expect(html()).toBe(`<h1>foo</h1>`)
|
||||||
|
expect('Invalid watch source').not.toHaveBeenWarned()
|
||||||
|
|
||||||
|
foo.value = 'bar'
|
||||||
|
await nextTick()
|
||||||
|
expect(html()).toBe(`<h1>bar</h1>`)
|
||||||
|
expect(changeSpy).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
|
||||||
test('support null in required + multiple-type declarations', () => {
|
test('support null in required + multiple-type declarations', () => {
|
||||||
const { render } = define({
|
const { render } = define({
|
||||||
props: {
|
props: {
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
import type { NodeRef } from '../../src/apiTemplateRef'
|
import type { NodeRef } from '../../src/apiTemplateRef'
|
||||||
import {
|
import {
|
||||||
|
child,
|
||||||
createComponent,
|
createComponent,
|
||||||
|
createDynamicComponent,
|
||||||
createFor,
|
createFor,
|
||||||
createIf,
|
createIf,
|
||||||
createSlot,
|
createSlot,
|
||||||
createTemplateRefSetter,
|
createTemplateRefSetter,
|
||||||
|
defineVaporComponent,
|
||||||
insert,
|
insert,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
template,
|
template,
|
||||||
|
@ -19,7 +22,8 @@ import {
|
||||||
useTemplateRef,
|
useTemplateRef,
|
||||||
watchEffect,
|
watchEffect,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { setElementText } from '../../src/dom/prop'
|
import { setElementText, setText } from '../../src/dom/prop'
|
||||||
|
import type { VaporComponent } from '../../src/component'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
|
||||||
|
@ -676,6 +680,39 @@ describe('api: template ref', () => {
|
||||||
expect(r!.value).toBe(n)
|
expect(r!.value).toBe(n)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('work with dynamic component', async () => {
|
||||||
|
const Child = defineVaporComponent({
|
||||||
|
setup(_, { expose }) {
|
||||||
|
const msg = ref('one')
|
||||||
|
expose({ setMsg: (m: string) => (msg.value = m) })
|
||||||
|
const n0 = template(`<div> </div>`)() as any
|
||||||
|
const x0 = child(n0) as any
|
||||||
|
renderEffect(() => setText(x0, msg.value))
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const views: Record<string, VaporComponent> = { child: Child }
|
||||||
|
const view = ref('child')
|
||||||
|
const refKey = ref<any>(null)
|
||||||
|
|
||||||
|
const { html } = define({
|
||||||
|
setup() {
|
||||||
|
const setRef = createTemplateRefSetter()
|
||||||
|
const n0 = createDynamicComponent(() => views[view.value]) as any
|
||||||
|
setRef(n0, refKey)
|
||||||
|
return n0
|
||||||
|
},
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(html()).toBe('<div>one</div><!--dynamic-component-->')
|
||||||
|
expect(refKey.value).toBeDefined()
|
||||||
|
|
||||||
|
refKey.value.setMsg('changed')
|
||||||
|
await nextTick()
|
||||||
|
expect(html()).toBe('<div>changed</div><!--dynamic-component-->')
|
||||||
|
})
|
||||||
|
|
||||||
// TODO: can not reproduce in Vapor
|
// TODO: can not reproduce in Vapor
|
||||||
// // #2078
|
// // #2078
|
||||||
// test('handling multiple merged refs', async () => {
|
// test('handling multiple merged refs', async () => {
|
||||||
|
|
|
@ -4,7 +4,14 @@ import {
|
||||||
getRestElement,
|
getRestElement,
|
||||||
renderEffect,
|
renderEffect,
|
||||||
} from '../src'
|
} from '../src'
|
||||||
import { nextTick, ref, shallowRef, triggerRef } from '@vue/runtime-dom'
|
import {
|
||||||
|
nextTick,
|
||||||
|
reactive,
|
||||||
|
readonly,
|
||||||
|
ref,
|
||||||
|
shallowRef,
|
||||||
|
triggerRef,
|
||||||
|
} from '@vue/runtime-dom'
|
||||||
import { makeRender } from './_utils'
|
import { makeRender } from './_utils'
|
||||||
|
|
||||||
const define = makeRender()
|
const define = makeRender()
|
||||||
|
@ -674,4 +681,57 @@ describe('createFor', () => {
|
||||||
await nextTick()
|
await nextTick()
|
||||||
expectCalledTimesToBe('Clear rows', 1, 0, 0, 0)
|
expectCalledTimesToBe('Clear rows', 1, 0, 0, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('readonly source', () => {
|
||||||
|
test('should not allow mutation', () => {
|
||||||
|
const arr = readonly(reactive([{ foo: 1 }]))
|
||||||
|
|
||||||
|
const { host } = define(() => {
|
||||||
|
const n1 = createFor(
|
||||||
|
() => arr,
|
||||||
|
(item, key, index) => {
|
||||||
|
const span = document.createElement('li')
|
||||||
|
renderEffect(() => {
|
||||||
|
item.value.foo = 0
|
||||||
|
span.innerHTML = `${item.value.foo}`
|
||||||
|
})
|
||||||
|
return span
|
||||||
|
},
|
||||||
|
idx => idx,
|
||||||
|
)
|
||||||
|
return n1
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(host.innerHTML).toBe('<li>1</li><!--for-->')
|
||||||
|
expect(
|
||||||
|
`Set operation on key "foo" failed: target is readonly.`,
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('should trigger effect for deep mutations', async () => {
|
||||||
|
const arr = reactive([{ foo: 1 }])
|
||||||
|
const readonlyArr = readonly(arr)
|
||||||
|
|
||||||
|
const { host } = define(() => {
|
||||||
|
const n1 = createFor(
|
||||||
|
() => readonlyArr,
|
||||||
|
(item, key, index) => {
|
||||||
|
const span = document.createElement('li')
|
||||||
|
renderEffect(() => {
|
||||||
|
span.innerHTML = `${item.value.foo}`
|
||||||
|
})
|
||||||
|
return span
|
||||||
|
},
|
||||||
|
idx => idx,
|
||||||
|
)
|
||||||
|
return n1
|
||||||
|
}).render()
|
||||||
|
|
||||||
|
expect(host.innerHTML).toBe('<li>1</li><!--for-->')
|
||||||
|
|
||||||
|
arr[0].foo = 2
|
||||||
|
await nextTick()
|
||||||
|
expect(host.innerHTML).toBe('<li>2</li><!--for-->')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import { resolveDynamicComponent } from '@vue/runtime-dom'
|
import { resolveDynamicComponent } from '@vue/runtime-dom'
|
||||||
import { DynamicFragment, type VaporFragment } from './block'
|
import { DynamicFragment, type VaporFragment, insert } from './block'
|
||||||
import { createComponentWithFallback } from './component'
|
import { createComponentWithFallback } from './component'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
import type { RawProps } from './componentProps'
|
import type { RawProps } from './componentProps'
|
||||||
import type { RawSlots } from './componentSlots'
|
import type { RawSlots } from './componentSlots'
|
||||||
|
import {
|
||||||
|
insertionAnchor,
|
||||||
|
insertionParent,
|
||||||
|
resetInsertionState,
|
||||||
|
} from './insertionState'
|
||||||
|
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||||
|
|
||||||
export function createDynamicComponent(
|
export function createDynamicComponent(
|
||||||
getter: () => any,
|
getter: () => any,
|
||||||
|
@ -11,9 +17,18 @@ export function createDynamicComponent(
|
||||||
rawSlots?: RawSlots | null,
|
rawSlots?: RawSlots | null,
|
||||||
isSingleRoot?: boolean,
|
isSingleRoot?: boolean,
|
||||||
): VaporFragment {
|
): VaporFragment {
|
||||||
|
const _insertionParent = insertionParent
|
||||||
|
const _insertionAnchor = insertionAnchor
|
||||||
|
if (isHydrating) {
|
||||||
|
locateHydrationNode()
|
||||||
|
} else {
|
||||||
|
resetInsertionState()
|
||||||
|
}
|
||||||
|
|
||||||
const frag = __DEV__
|
const frag = __DEV__
|
||||||
? new DynamicFragment('dynamic-component')
|
? new DynamicFragment('dynamic-component')
|
||||||
: new DynamicFragment()
|
: new DynamicFragment()
|
||||||
|
|
||||||
renderEffect(() => {
|
renderEffect(() => {
|
||||||
const value = getter()
|
const value = getter()
|
||||||
frag.update(
|
frag.update(
|
||||||
|
@ -27,5 +42,10 @@ export function createDynamicComponent(
|
||||||
value,
|
value,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (!isHydrating && _insertionParent) {
|
||||||
|
insert(frag, _insertionParent, _insertionAnchor)
|
||||||
|
}
|
||||||
|
|
||||||
return frag
|
return frag
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@ import {
|
||||||
EffectScope,
|
EffectScope,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
isReactive,
|
isReactive,
|
||||||
|
isReadonly,
|
||||||
isShallow,
|
isShallow,
|
||||||
pauseTracking,
|
pauseTracking,
|
||||||
resetTracking,
|
resetTracking,
|
||||||
shallowReadArray,
|
shallowReadArray,
|
||||||
shallowRef,
|
shallowRef,
|
||||||
toReactive,
|
toReactive,
|
||||||
|
toReadonly,
|
||||||
} from '@vue/reactivity'
|
} from '@vue/reactivity'
|
||||||
import { getSequence, isArray, isObject, isString } from '@vue/shared'
|
import { getSequence, isArray, isObject, isString } from '@vue/shared'
|
||||||
import { createComment, createTextNode } from './dom/node'
|
import { createComment, createTextNode } from './dom/node'
|
||||||
|
@ -23,7 +25,11 @@ import type { DynamicSlot } from './componentSlots'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
import { VaporVForFlags } from '../../shared/src/vaporFlags'
|
||||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||||
import { insertionAnchor, insertionParent } from './insertionState'
|
import {
|
||||||
|
insertionAnchor,
|
||||||
|
insertionParent,
|
||||||
|
resetInsertionState,
|
||||||
|
} from './insertionState'
|
||||||
|
|
||||||
class ForBlock extends VaporFragment {
|
class ForBlock extends VaporFragment {
|
||||||
scope: EffectScope | undefined
|
scope: EffectScope | undefined
|
||||||
|
@ -55,6 +61,7 @@ type Source = any[] | Record<any, any> | number | Set<any> | Map<any, any>
|
||||||
type ResolvedSource = {
|
type ResolvedSource = {
|
||||||
values: any[]
|
values: any[]
|
||||||
needsWrap: boolean
|
needsWrap: boolean
|
||||||
|
isReadonlySource: boolean
|
||||||
keys?: string[]
|
keys?: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -72,6 +79,8 @@ export const createFor = (
|
||||||
const _insertionAnchor = insertionAnchor
|
const _insertionAnchor = insertionAnchor
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
locateHydrationNode()
|
locateHydrationNode()
|
||||||
|
} else {
|
||||||
|
resetInsertionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
let isMounted = false
|
let isMounted = false
|
||||||
|
@ -387,11 +396,13 @@ export function createForSlots(
|
||||||
function normalizeSource(source: any): ResolvedSource {
|
function normalizeSource(source: any): ResolvedSource {
|
||||||
let values = source
|
let values = source
|
||||||
let needsWrap = false
|
let needsWrap = false
|
||||||
|
let isReadonlySource = false
|
||||||
let keys
|
let keys
|
||||||
if (isArray(source)) {
|
if (isArray(source)) {
|
||||||
if (isReactive(source)) {
|
if (isReactive(source)) {
|
||||||
needsWrap = !isShallow(source)
|
needsWrap = !isShallow(source)
|
||||||
values = shallowReadArray(source)
|
values = shallowReadArray(source)
|
||||||
|
isReadonlySource = isReadonly(source)
|
||||||
}
|
}
|
||||||
} else if (isString(source)) {
|
} else if (isString(source)) {
|
||||||
values = source.split('')
|
values = source.split('')
|
||||||
|
@ -412,14 +423,23 @@ function normalizeSource(source: any): ResolvedSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return { values, needsWrap, keys }
|
return {
|
||||||
|
values,
|
||||||
|
needsWrap,
|
||||||
|
isReadonlySource,
|
||||||
|
keys,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getItem(
|
function getItem(
|
||||||
{ keys, values, needsWrap }: ResolvedSource,
|
{ keys, values, needsWrap, isReadonlySource }: ResolvedSource,
|
||||||
idx: number,
|
idx: number,
|
||||||
): [item: any, key: any, index?: number] {
|
): [item: any, key: any, index?: number] {
|
||||||
const value = needsWrap ? toReactive(values[idx]) : values[idx]
|
const value = needsWrap
|
||||||
|
? isReadonlySource
|
||||||
|
? toReadonly(toReactive(values[idx]))
|
||||||
|
: toReactive(values[idx])
|
||||||
|
: values[idx]
|
||||||
if (keys) {
|
if (keys) {
|
||||||
return [value, keys[idx], idx]
|
return [value, keys[idx], idx]
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
import { type Block, type BlockFn, DynamicFragment, insert } from './block'
|
import { type Block, type BlockFn, DynamicFragment, insert } from './block'
|
||||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||||
import { insertionAnchor, insertionParent } from './insertionState'
|
import {
|
||||||
|
insertionAnchor,
|
||||||
|
insertionParent,
|
||||||
|
resetInsertionState,
|
||||||
|
} from './insertionState'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
|
|
||||||
export function createIf(
|
export function createIf(
|
||||||
|
@ -13,6 +17,8 @@ export function createIf(
|
||||||
const _insertionAnchor = insertionAnchor
|
const _insertionAnchor = insertionAnchor
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
locateHydrationNode()
|
locateHydrationNode()
|
||||||
|
} else {
|
||||||
|
resetInsertionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
let frag: Block
|
let frag: Block
|
||||||
|
|
|
@ -26,7 +26,7 @@ import {
|
||||||
unregisterHMR,
|
unregisterHMR,
|
||||||
warn,
|
warn,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
import { type Block, insert, isBlock, remove } from './block'
|
import { type Block, DynamicFragment, insert, isBlock, remove } from './block'
|
||||||
import {
|
import {
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
markRaw,
|
markRaw,
|
||||||
|
@ -66,8 +66,12 @@ import {
|
||||||
} from './componentSlots'
|
} from './componentSlots'
|
||||||
import { hmrReload, hmrRerender } from './hmr'
|
import { hmrReload, hmrRerender } from './hmr'
|
||||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||||
import { insertionAnchor, insertionParent } from './insertionState'
|
|
||||||
import type { KeepAliveInstance } from './components/KeepAlive'
|
import type { KeepAliveInstance } from './components/KeepAlive'
|
||||||
|
import {
|
||||||
|
insertionAnchor,
|
||||||
|
insertionParent,
|
||||||
|
resetInsertionState,
|
||||||
|
} from './insertionState'
|
||||||
|
|
||||||
export { currentInstance } from '@vue/runtime-dom'
|
export { currentInstance } from '@vue/runtime-dom'
|
||||||
|
|
||||||
|
@ -150,6 +154,8 @@ export function createComponent(
|
||||||
const _insertionAnchor = insertionAnchor
|
const _insertionAnchor = insertionAnchor
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
locateHydrationNode()
|
locateHydrationNode()
|
||||||
|
} else {
|
||||||
|
resetInsertionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
// keep-alive
|
// keep-alive
|
||||||
|
@ -172,9 +178,8 @@ export function createComponent(
|
||||||
rawProps,
|
rawProps,
|
||||||
rawSlots,
|
rawSlots,
|
||||||
)
|
)
|
||||||
// TODO: problem is `frag.insert` will be called multiple times
|
|
||||||
// if used in v-if
|
if (!isHydrating && _insertionParent) {
|
||||||
if (!isHydrating && _insertionParent && !isKeepAlive(currentInstance)) {
|
|
||||||
insert(frag, _insertionParent, _insertionAnchor)
|
insert(frag, _insertionParent, _insertionAnchor)
|
||||||
}
|
}
|
||||||
return frag
|
return frag
|
||||||
|
@ -272,14 +277,16 @@ export function createComponent(
|
||||||
if (
|
if (
|
||||||
instance.hasFallthrough &&
|
instance.hasFallthrough &&
|
||||||
component.inheritAttrs !== false &&
|
component.inheritAttrs !== false &&
|
||||||
instance.block instanceof Element &&
|
|
||||||
Object.keys(instance.attrs).length
|
Object.keys(instance.attrs).length
|
||||||
) {
|
) {
|
||||||
renderEffect(() => {
|
const el = getRootElement(instance)
|
||||||
isApplyingFallthroughProps = true
|
if (el) {
|
||||||
setDynamicProps(instance.block as Element, [instance.attrs])
|
renderEffect(() => {
|
||||||
isApplyingFallthroughProps = false
|
isApplyingFallthroughProps = true
|
||||||
})
|
setDynamicProps(el, [instance.attrs])
|
||||||
|
isApplyingFallthroughProps = false
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resetTracking()
|
resetTracking()
|
||||||
|
@ -494,6 +501,14 @@ export function createComponentWithFallback(
|
||||||
return createComponent(comp, rawProps, rawSlots, isSingleRoot)
|
return createComponent(comp, rawProps, rawSlots, isSingleRoot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _insertionParent = insertionParent
|
||||||
|
const _insertionAnchor = insertionAnchor
|
||||||
|
if (isHydrating) {
|
||||||
|
locateHydrationNode()
|
||||||
|
} else {
|
||||||
|
resetInsertionState()
|
||||||
|
}
|
||||||
|
|
||||||
const el = document.createElement(comp)
|
const el = document.createElement(comp)
|
||||||
// mark single root
|
// mark single root
|
||||||
;(el as any).$root = isSingleRoot
|
;(el as any).$root = isSingleRoot
|
||||||
|
@ -512,6 +527,10 @@ export function createComponentWithFallback(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isHydrating && _insertionParent) {
|
||||||
|
insert(el, _insertionParent, _insertionAnchor)
|
||||||
|
}
|
||||||
|
|
||||||
return el
|
return el
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -594,3 +613,18 @@ export function getExposed(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getRootElement({
|
||||||
|
block,
|
||||||
|
}: VaporComponentInstance): Element | undefined {
|
||||||
|
if (block instanceof Element) {
|
||||||
|
return block
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block instanceof DynamicFragment) {
|
||||||
|
const { nodes } = block
|
||||||
|
if (nodes instanceof Element && (nodes as any).$root) {
|
||||||
|
return nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -21,6 +21,7 @@ import {
|
||||||
validateProps,
|
validateProps,
|
||||||
warn,
|
warn,
|
||||||
} from '@vue/runtime-dom'
|
} from '@vue/runtime-dom'
|
||||||
|
import { ReactiveFlags } from '@vue/reactivity'
|
||||||
import { normalizeEmitsOptions } from './componentEmits'
|
import { normalizeEmitsOptions } from './componentEmits'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
|
|
||||||
|
@ -63,6 +64,9 @@ export function getPropsProxyHandlers(
|
||||||
: YES
|
: YES
|
||||||
|
|
||||||
const getProp = (instance: VaporComponentInstance, key: string | symbol) => {
|
const getProp = (instance: VaporComponentInstance, key: string | symbol) => {
|
||||||
|
// this enables direct watching of props and prevents `Invalid watch source` DEV warnings.
|
||||||
|
if (key === ReactiveFlags.IS_REACTIVE) return true
|
||||||
|
|
||||||
if (!isProp(key)) return
|
if (!isProp(key)) return
|
||||||
const rawProps = instance.rawProps
|
const rawProps = instance.rawProps
|
||||||
const dynamicSources = rawProps.$
|
const dynamicSources = rawProps.$
|
||||||
|
@ -210,7 +214,8 @@ export function hasAttrFromRawProps(rawProps: RawProps, key: string): boolean {
|
||||||
if (dynamicSources) {
|
if (dynamicSources) {
|
||||||
let i = dynamicSources.length
|
let i = dynamicSources.length
|
||||||
while (i--) {
|
while (i--) {
|
||||||
if (hasOwn(resolveSource(dynamicSources[i]), key)) {
|
const source = resolveSource(dynamicSources[i])
|
||||||
|
if (source && hasOwn(source, key)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,11 @@ import { rawPropsProxyHandlers } from './componentProps'
|
||||||
import { currentInstance, isRef } from '@vue/runtime-dom'
|
import { currentInstance, isRef } from '@vue/runtime-dom'
|
||||||
import type { LooseRawProps, VaporComponentInstance } from './component'
|
import type { LooseRawProps, VaporComponentInstance } from './component'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
import { insertionAnchor, insertionParent } from './insertionState'
|
import {
|
||||||
|
insertionAnchor,
|
||||||
|
insertionParent,
|
||||||
|
resetInsertionState,
|
||||||
|
} from './insertionState'
|
||||||
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
import { isHydrating, locateHydrationNode } from './dom/hydration'
|
||||||
|
|
||||||
export type RawSlots = Record<string, VaporSlot> & {
|
export type RawSlots = Record<string, VaporSlot> & {
|
||||||
|
@ -96,6 +100,8 @@ export function createSlot(
|
||||||
const _insertionAnchor = insertionAnchor
|
const _insertionAnchor = insertionAnchor
|
||||||
if (isHydrating) {
|
if (isHydrating) {
|
||||||
locateHydrationNode()
|
locateHydrationNode()
|
||||||
|
} else {
|
||||||
|
resetInsertionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = currentInstance as VaporComponentInstance
|
const instance = currentInstance as VaporComponentInstance
|
||||||
|
|
|
@ -122,7 +122,9 @@ function setClassIncremental(el: any, value: any): void {
|
||||||
const prev = el[cacheKey]
|
const prev = el[cacheKey]
|
||||||
if ((value = el[cacheKey] = normalizeClass(value)) !== prev) {
|
if ((value = el[cacheKey] = normalizeClass(value)) !== prev) {
|
||||||
const nextList = value.split(/\s+/)
|
const nextList = value.split(/\s+/)
|
||||||
el.classList.add(...nextList)
|
if (value) {
|
||||||
|
el.classList.add(...nextList)
|
||||||
|
}
|
||||||
if (prev) {
|
if (prev) {
|
||||||
for (const cls of prev.split(/\s+/)) {
|
for (const cls of prev.split(/\s+/)) {
|
||||||
if (!nextList.includes(cls)) el.classList.remove(cls)
|
if (!nextList.includes(cls)) el.classList.remove(cls)
|
||||||
|
|
|
@ -9,9 +9,11 @@ import {
|
||||||
type Slots,
|
type Slots,
|
||||||
type VNode,
|
type VNode,
|
||||||
type VaporInteropInterface,
|
type VaporInteropInterface,
|
||||||
|
createInternalObject,
|
||||||
createVNode,
|
createVNode,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
ensureRenderer,
|
ensureRenderer,
|
||||||
|
isEmitListener,
|
||||||
isKeepAlive,
|
isKeepAlive,
|
||||||
onScopeDispose,
|
onScopeDispose,
|
||||||
renderSlot,
|
renderSlot,
|
||||||
|
@ -204,7 +206,14 @@ function createVDOMComponent(
|
||||||
// overwrite how the vdom instance handles props
|
// overwrite how the vdom instance handles props
|
||||||
vnode.vi = (instance: ComponentInternalInstance) => {
|
vnode.vi = (instance: ComponentInternalInstance) => {
|
||||||
instance.props = wrapper.props
|
instance.props = wrapper.props
|
||||||
instance.attrs = wrapper.attrs
|
|
||||||
|
const attrs = (instance.attrs = createInternalObject())
|
||||||
|
for (const key in wrapper.attrs) {
|
||||||
|
if (!isEmitListener(instance.emitsOptions, key)) {
|
||||||
|
attrs[key] = wrapper.attrs[key]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
instance.slots =
|
instance.slots =
|
||||||
wrapper.slots === EMPTY_OBJ
|
wrapper.slots === EMPTY_OBJ
|
||||||
? EMPTY_OBJ
|
? EMPTY_OBJ
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { createApp } from 'vue'
|
import { createApp, defineAsyncComponent, h } from 'vue'
|
||||||
import { renderToString } from '../src/renderToString'
|
import { renderToString } from '../src/renderToString'
|
||||||
|
|
||||||
const components = {
|
const components = {
|
||||||
|
@ -154,6 +154,38 @@ describe('ssr: slot', () => {
|
||||||
).toBe(`<div><p>1</p><p>2</p></div>`)
|
).toBe(`<div><p>1</p><p>2</p></div>`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// #12438
|
||||||
|
test('async component slot with v-if true', async () => {
|
||||||
|
const Layout = defineAsyncComponent(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
template: `<div><slot name="header">default header</slot></div>`,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
const LayoutLoader = {
|
||||||
|
setup(_: any, context: any) {
|
||||||
|
return () => h(Layout, {}, context.slots)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
expect(
|
||||||
|
await renderToString(
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
LayoutLoader,
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<Suspense>
|
||||||
|
<LayoutLoader>
|
||||||
|
<template v-if="true" #header>
|
||||||
|
new header
|
||||||
|
</template>
|
||||||
|
</LayoutLoader>
|
||||||
|
</Suspense>
|
||||||
|
`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toBe(`<div><!--[--> new header <!--]--></div>`)
|
||||||
|
})
|
||||||
|
|
||||||
// #11326
|
// #11326
|
||||||
test('dynamic component slot', async () => {
|
test('dynamic component slot', async () => {
|
||||||
expect(
|
expect(
|
||||||
|
|
|
@ -49,7 +49,7 @@ test('pipeToWebWritable', async () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const { readable, writable } = new TransformStream()
|
const { readable, writable } = new TransformStream()
|
||||||
pipeToWebWritable(createApp(App), {}, writable)
|
pipeToWebWritable(createApp(App), {}, writable as any)
|
||||||
|
|
||||||
const reader = readable.getReader()
|
const reader = readable.getReader()
|
||||||
const decoder = new TextDecoder()
|
const decoder = new TextDecoder()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/server-renderer",
|
"name": "@vue/server-renderer",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "@vue/server-renderer",
|
"description": "@vue/server-renderer",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/server-renderer.esm-bundler.js",
|
"module": "dist/server-renderer.esm-bundler.js",
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/shared",
|
"name": "@vue/shared",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "internal utils shared across @vue packages",
|
"description": "internal utils shared across @vue packages",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/shared.esm-bundler.js",
|
"module": "dist/shared.esm-bundler.js",
|
||||||
|
|
|
@ -163,7 +163,12 @@ export function shouldSetAsAttr(tagName: string, key: string): boolean {
|
||||||
// them as attributes.
|
// them as attributes.
|
||||||
// Note that `contentEditable` doesn't have this problem: its DOM
|
// Note that `contentEditable` doesn't have this problem: its DOM
|
||||||
// property is also enumerated string values.
|
// property is also enumerated string values.
|
||||||
if (key === 'spellcheck' || key === 'draggable' || key === 'translate') {
|
if (
|
||||||
|
key === 'spellcheck' ||
|
||||||
|
key === 'draggable' ||
|
||||||
|
key === 'translate' ||
|
||||||
|
key === 'autocorrect'
|
||||||
|
) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@vue/compat",
|
"name": "@vue/compat",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "Vue 3 compatibility build for Vue 2",
|
"description": "Vue 3 compatibility build for Vue 2",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/vue.runtime.esm-bundler.js",
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
|
|
|
@ -647,4 +647,55 @@ describe('e2e: TransitionGroup', () => {
|
||||||
},
|
},
|
||||||
E2E_TIMEOUT,
|
E2E_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
test(
|
||||||
|
'not leaking after children unmounted',
|
||||||
|
async () => {
|
||||||
|
const client = await page().createCDPSession()
|
||||||
|
await page().evaluate(async () => {
|
||||||
|
const { createApp, ref, nextTick } = (window as any).Vue
|
||||||
|
const show = ref(true)
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
components: {
|
||||||
|
Child: {
|
||||||
|
setup: () => {
|
||||||
|
// Big arrays kick GC earlier
|
||||||
|
const test = ref([...Array(3000)].map((_, i) => ({ i })))
|
||||||
|
// @ts-expect-error - Custom property and same lib as runtime is used
|
||||||
|
window.__REF__ = new WeakRef(test)
|
||||||
|
|
||||||
|
return { test }
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<p>{{ test.length }}</p>
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
template: `
|
||||||
|
<transition-group>
|
||||||
|
<Child v-if="show" />
|
||||||
|
</transition-group>
|
||||||
|
`,
|
||||||
|
setup() {
|
||||||
|
return { show }
|
||||||
|
},
|
||||||
|
}).mount('#app')
|
||||||
|
|
||||||
|
show.value = false
|
||||||
|
await nextTick()
|
||||||
|
})
|
||||||
|
|
||||||
|
const isCollected = async () =>
|
||||||
|
// @ts-expect-error - Custom property
|
||||||
|
await page().evaluate(() => window.__REF__.deref() === undefined)
|
||||||
|
|
||||||
|
while ((await isCollected()) === false) {
|
||||||
|
await client.send('HeapProfiler.collectGarbage')
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(await isCollected()).toBe(true)
|
||||||
|
},
|
||||||
|
E2E_TIMEOUT,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "vue",
|
"name": "vue",
|
||||||
"version": "3.5.13",
|
"version": "3.5.14",
|
||||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"module": "dist/vue.runtime.esm-bundler.js",
|
"module": "dist/vue.runtime.esm-bundler.js",
|
||||||
|
|
3291
pnpm-lock.yaml
3291
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue