mirror of https://github.com/vuejs/core.git
Merge remote-tracking branch 'upstream/minor'
This commit is contained in:
commit
597eae423b
|
@ -60,3 +60,28 @@ jobs:
|
|||
|
||||
- name: Run type declaration tests
|
||||
run: pnpm run test-dts
|
||||
|
||||
# benchmarks:
|
||||
# runs-on: ubuntu-latest
|
||||
# if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository
|
||||
# env:
|
||||
# PUPPETEER_SKIP_DOWNLOAD: 'true'
|
||||
# steps:
|
||||
# - uses: actions/checkout@v4
|
||||
|
||||
# - name: Install pnpm
|
||||
# uses: pnpm/action-setup@v2
|
||||
|
||||
# - name: Install Node.js
|
||||
# uses: actions/setup-node@v4
|
||||
# with:
|
||||
# node-version-file: '.node-version'
|
||||
# cache: 'pnpm'
|
||||
|
||||
# - run: pnpm install
|
||||
|
||||
# - name: Run benchmarks
|
||||
# uses: CodSpeedHQ/action@v2
|
||||
# with:
|
||||
# run: pnpm vitest bench --run
|
||||
# token: ${{ secrets.CODSPEED_TOKEN }}
|
||||
|
|
168
CHANGELOG.md
168
CHANGELOG.md
|
@ -1,3 +1,103 @@
|
|||
# [3.4.0-beta.3](https://github.com/vuejs/core/compare/v3.3.12...v3.4.0-beta.3) (2023-12-16)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** vnode hooks error message ([#9842](https://github.com/vuejs/core/issues/9842)) ([7bc3c9e](https://github.com/vuejs/core/commit/7bc3c9e205c5158230772d9fcd25bf300809342e))
|
||||
* **defineModel:** ensure trigger effect when prop changed ([#9841](https://github.com/vuejs/core/issues/9841)) ([eb12f21](https://github.com/vuejs/core/commit/eb12f211b8e312fd64d91ef1a58b2c2db618bdee)), closes [#9838](https://github.com/vuejs/core/issues/9838)
|
||||
* **mathml:** update known mathML tags ([#9829](https://github.com/vuejs/core/issues/9829)) ([ebd78d2](https://github.com/vuejs/core/commit/ebd78d2c99d9587307e444e6b7baa7bc920d42e7))
|
||||
* **Suspense:** fix edge case of Suspense being patched during async HOC child remount ([f0f6f7c](https://github.com/vuejs/core/commit/f0f6f7cea6e16650181e71dcfccbee405a1db503))
|
||||
|
||||
|
||||
|
||||
## [3.3.12](https://github.com/vuejs/core/compare/v3.3.11...v3.3.12) (2023-12-16)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **hydration:** handle appear transition before patch props ([#9837](https://github.com/vuejs/core/issues/9837)) ([e70f4c4](https://github.com/vuejs/core/commit/e70f4c47c553b6e16d8fad70743271ca23802fe7)), closes [#9832](https://github.com/vuejs/core/issues/9832)
|
||||
* **sfc/cssVars:** fix loss of CSS v-bind variables when setting inline style with string value ([#9824](https://github.com/vuejs/core/issues/9824)) ([0a387df](https://github.com/vuejs/core/commit/0a387dfb1d04afb6eae4296b6da76dfdaca77af4)), closes [#9821](https://github.com/vuejs/core/issues/9821)
|
||||
* **ssr:** fix suspense hydration of fallback content ([#7188](https://github.com/vuejs/core/issues/7188)) ([60415b5](https://github.com/vuejs/core/commit/60415b5d67df55f1fd6b176615299c08640fa142))
|
||||
* **types:** add `xmlns:xlink` to `SVGAttributes` ([#9300](https://github.com/vuejs/core/issues/9300)) ([0d61b42](https://github.com/vuejs/core/commit/0d61b429ecf63591d31e09702058fa4c7132e1a7)), closes [#9299](https://github.com/vuejs/core/issues/9299)
|
||||
* **types:** fix `shallowRef` type error ([#9839](https://github.com/vuejs/core/issues/9839)) ([9a57158](https://github.com/vuejs/core/commit/9a571582b53220270e498d8712ea59312c0bef3a))
|
||||
* **types:** support for generic keyof slots ([#8374](https://github.com/vuejs/core/issues/8374)) ([213eba4](https://github.com/vuejs/core/commit/213eba479ce080efc1053fe636f6be4a4c889b44))
|
||||
|
||||
|
||||
|
||||
# [3.4.0-beta.2](https://github.com/vuejs/core/compare/v3.4.0-beta.1...v3.4.0-beta.2) (2023-12-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **types:** remove default jsx global registration ([92b8d9c](https://github.com/vuejs/core/commit/92b8d9cef69146540db2bf7f2a5632ab5d38f672))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* **types:** Vue no longer registers the global `JSX` namespace by
|
||||
default.
|
||||
|
||||
|
||||
|
||||
# [3.4.0-beta.1](https://github.com/vuejs/core/compare/v3.3.11...v3.4.0-beta.1) (2023-12-13)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **compiler-core:** use the same resolved options for all compile stages ([#9760](https://github.com/vuejs/core/issues/9760)) ([0dc875d](https://github.com/vuejs/core/commit/0dc875d53e5d869b44d0c1a70736ec859337b58f))
|
||||
* **hydration:** should not warn mismatch for nullish prop ([33159a5](https://github.com/vuejs/core/commit/33159a5916bf7686fe53517befa59b450b34e974))
|
||||
* **hydration:** swap client/server labels for hydration mismatch warnings ([f41fd86](https://github.com/vuejs/core/commit/f41fd86d5f26bd0009b4ca285ddc3cefaafa9f7c)), closes [#9098](https://github.com/vuejs/core/issues/9098) [#5953](https://github.com/vuejs/core/issues/5953)
|
||||
* **runtime-core:** fix suspense crash when patching non-resolved async setup component ([#7290](https://github.com/vuejs/core/issues/7290)) ([bb0c889](https://github.com/vuejs/core/commit/bb0c8899cadd03af22e23c0383aaab363635c5b4)), closes [#5993](https://github.com/vuejs/core/issues/5993) [#6463](https://github.com/vuejs/core/issues/6463) [#6949](https://github.com/vuejs/core/issues/6949) [#6095](https://github.com/vuejs/core/issues/6095) [#8121](https://github.com/vuejs/core/issues/8121)
|
||||
* **runtime-core:** properly pop warning context when mounting components with async setup ([69a2acc](https://github.com/vuejs/core/commit/69a2acc6ea159da8300a68ecc8953f19932c251b))
|
||||
* **ssr:** fix suspense hydration of fallback content ([#7188](https://github.com/vuejs/core/issues/7188)) ([60415b5](https://github.com/vuejs/core/commit/60415b5d67df55f1fd6b176615299c08640fa142))
|
||||
* **ssr:** make isInSSRComponentSetup state sharable across copies of Vue ([e04d821](https://github.com/vuejs/core/commit/e04d821422102446704e223c03e50d26cbb1fe69))
|
||||
* **Suspense:** handle switching away from kept-alive component before resolve ([aa0c13f](https://github.com/vuejs/core/commit/aa0c13f637df7eb27faa2545ee731f543c0813ec)), closes [#6416](https://github.com/vuejs/core/issues/6416) [#6467](https://github.com/vuejs/core/issues/6467)
|
||||
* **Suspense:** properly fix [#6416](https://github.com/vuejs/core/issues/6416) ([0db336f](https://github.com/vuejs/core/commit/0db336ff6c640fb9d3e48943c69f4c1737412be4))
|
||||
* **types:** add `xmlns:xlink` to `SVGAttributes` ([#9300](https://github.com/vuejs/core/issues/9300)) ([0d61b42](https://github.com/vuejs/core/commit/0d61b429ecf63591d31e09702058fa4c7132e1a7)), closes [#9299](https://github.com/vuejs/core/issues/9299)
|
||||
* **types:** support for generic keyof slots ([#8374](https://github.com/vuejs/core/issues/8374)) ([213eba4](https://github.com/vuejs/core/commit/213eba479ce080efc1053fe636f6be4a4c889b44))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** add current filename to TransformContext ([#8950](https://github.com/vuejs/core/issues/8950)) ([638f1ab](https://github.com/vuejs/core/commit/638f1abbb632000553e2b7d75e87c95d8ca192d6))
|
||||
* **compiler-sfc:** promote defineModel stable ([#9598](https://github.com/vuejs/core/issues/9598)) ([ef688ba](https://github.com/vuejs/core/commit/ef688ba92bfccbc8b7ea3997eb297665d13e5249))
|
||||
* **compiler-sfc:** support import attributes and `using` syntax ([#8786](https://github.com/vuejs/core/issues/8786)) ([5b2bd1d](https://github.com/vuejs/core/commit/5b2bd1df78e8ff524c3a184adaa284681aba6574))
|
||||
* **defineModel:** support local mutation by default, remove local option ([f74785b](https://github.com/vuejs/core/commit/f74785bc4ad351102dde17fdfd2c7276b823111f)), closes [/github.com/vuejs/rfcs/discussions/503#discussioncomment-7566278](https://github.com//github.com/vuejs/rfcs/discussions/503/issues/discussioncomment-7566278)
|
||||
* MathML support ([#7836](https://github.com/vuejs/core/issues/7836)) ([d42b6ba](https://github.com/vuejs/core/commit/d42b6ba3f530746eb1221eb7a4be0f44eb56f7d3)), closes [#7820](https://github.com/vuejs/core/issues/7820)
|
||||
* **runtime-core:** provide full props to props validator functions ([#3258](https://github.com/vuejs/core/issues/3258)) ([8e27692](https://github.com/vuejs/core/commit/8e27692029a4645cd54287f776c0420f2b82740b))
|
||||
* **ssr:** add `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__` feature flag ([#9550](https://github.com/vuejs/core/issues/9550)) ([bc7698d](https://github.com/vuejs/core/commit/bc7698dbfed9b5327a93565f9df336ae5a94d605))
|
||||
* **ssr:** improve ssr hydration mismatch checks ([#5953](https://github.com/vuejs/core/issues/5953)) ([2ffc1e8](https://github.com/vuejs/core/commit/2ffc1e8cfdc6ec9c45c4a4dd8e3081b2aa138f1e)), closes [#5063](https://github.com/vuejs/core/issues/5063)
|
||||
* **types:** add emits and slots type to `FunctionalComponent` ([#8644](https://github.com/vuejs/core/issues/8644)) ([927ab17](https://github.com/vuejs/core/commit/927ab17cfc645e82d061fdf227c34689491268e1))
|
||||
* **types:** export AriaAttributes type ([#8909](https://github.com/vuejs/core/issues/8909)) ([fd0b6ba](https://github.com/vuejs/core/commit/fd0b6ba01660499fa07b0cf360eefaac8cca8287))
|
||||
* **types:** export ObjectPlugin and FunctionPlugin types ([#8946](https://github.com/vuejs/core/issues/8946)) ([fa4969e](https://github.com/vuejs/core/commit/fa4969e7a3aefa6863203f9294fc5e769ddf6d8f)), closes [#8577](https://github.com/vuejs/core/issues/8577)
|
||||
* **types:** expose `DefineProps` type ([096ba81](https://github.com/vuejs/core/commit/096ba81817b7da15f61bc55fc1a93f72ac9586e0))
|
||||
* **types:** expose `PublicProps` type ([#2403](https://github.com/vuejs/core/issues/2403)) ([44135dc](https://github.com/vuejs/core/commit/44135dc95fb8fea26b84d1433839d28b8c21f708))
|
||||
* **types:** improve event type inference when using `h` with native elements ([#9756](https://github.com/vuejs/core/issues/9756)) ([a625376](https://github.com/vuejs/core/commit/a625376ac8901eea81bf3c66cb531f2157f073ef))
|
||||
* **types:** provide ComponentInstance type ([#5408](https://github.com/vuejs/core/issues/5408)) ([bfb8565](https://github.com/vuejs/core/commit/bfb856565d3105db4b18991ae9e404e7cc989b25))
|
||||
* **types:** support passing generics when registering global directives ([#9660](https://github.com/vuejs/core/issues/9660)) ([a41409e](https://github.com/vuejs/core/commit/a41409ed02a8c7220e637f56caf6813edeb077f8))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* use sync watcher for defineModel local mode ([7e60d10](https://github.com/vuejs/core/commit/7e60d1058ff06e3d37c8608f3449453321220edc)), closes [/github.com/vuejs/rfcs/discussions/503#discussioncomment-7566278](https://github.com//github.com/vuejs/rfcs/discussions/503/issues/discussioncomment-7566278)
|
||||
|
||||
|
||||
|
||||
## [3.3.11](https://github.com/vuejs/core/compare/v3.3.10...v3.3.11) (2023-12-08)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **custom-element:** correctly handle number type props in prod ([#8989](https://github.com/vuejs/core/issues/8989)) ([d74d364](https://github.com/vuejs/core/commit/d74d364d62db8e48881af6b5a75ce4fb5f36cc35))
|
||||
* **reactivity:** fix mutation on user proxy of reactive Array ([6ecbd5c](https://github.com/vuejs/core/commit/6ecbd5ce2a7f59314a8326a1d193874b87f4d8c8)), closes [#9742](https://github.com/vuejs/core/issues/9742) [#9751](https://github.com/vuejs/core/issues/9751) [#9750](https://github.com/vuejs/core/issues/9750)
|
||||
* **runtime-dom:** fix width and height prop check condition ([5b00286](https://github.com/vuejs/core/commit/5b002869c533220706f9788b496b8ca8d8e98609)), closes [#9762](https://github.com/vuejs/core/issues/9762)
|
||||
* **shared:** handle Map with symbol keys in toDisplayString ([#9731](https://github.com/vuejs/core/issues/9731)) ([364821d](https://github.com/vuejs/core/commit/364821d6bdb1775e2f55a69bcfb9f40f7acf1506)), closes [#9727](https://github.com/vuejs/core/issues/9727)
|
||||
* **shared:** handle more Symbol cases in toDisplayString ([983d45d](https://github.com/vuejs/core/commit/983d45d4f8eb766b5a16b7ea93b86d3c51618fa6))
|
||||
* **Suspense:** properly get anchor when mount fallback vnode ([#9770](https://github.com/vuejs/core/issues/9770)) ([b700328](https://github.com/vuejs/core/commit/b700328342e17dc16b19316c2e134a26107139d2)), closes [#9769](https://github.com/vuejs/core/issues/9769)
|
||||
* **types:** ref() return type should not be any when initial value is any ([#9768](https://github.com/vuejs/core/issues/9768)) ([cdac121](https://github.com/vuejs/core/commit/cdac12161ec27b45ded48854c3d749664b6d4a6d))
|
||||
* **watch:** should not fire pre watcher on child component unmount ([#7181](https://github.com/vuejs/core/issues/7181)) ([6784f0b](https://github.com/vuejs/core/commit/6784f0b1f8501746ea70d87d18ed63a62cf6b76d)), closes [#7030](https://github.com/vuejs/core/issues/7030)
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.4](https://github.com/vuejs/core/compare/v3.3.10...v3.4.0-alpha.4) (2023-12-04)
|
||||
|
||||
|
||||
|
@ -19,74 +119,6 @@
|
|||
|
||||
|
||||
|
||||
# [3.4.0-alpha.3](https://github.com/vuejs/core/compare/v3.4.0-alpha.2...v3.4.0-alpha.3) (2023-11-28)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **parser:** directive arg should be undefined on shorthands with no arg ([e49dffc](https://github.com/vuejs/core/commit/e49dffc9ece86bddf094b9ad4ad15eb4856d6277))
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **dx:** link errors to docs in prod build ([#9165](https://github.com/vuejs/core/issues/9165)) ([9f8ba98](https://github.com/vuejs/core/commit/9f8ba9821fe166f77e63fa940e9e7e13ec3344fa))
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.2](https://github.com/vuejs/core/compare/v3.3.9...v3.4.0-alpha.2) (2023-11-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* avoid confusing breakage in @vitejs/plugin-vue ([ceec69c](https://github.com/vuejs/core/commit/ceec69c8ccb96c433a4a506ad2e85e276998bade))
|
||||
* **compiler-core:** fix line/column tracking when fast forwarding ([2e65ea4](https://github.com/vuejs/core/commit/2e65ea481f74db8649df8110a031cbdc98f98c84))
|
||||
* **compiler-sfc:** fix ast reuse for ssr ([fb619cf](https://github.com/vuejs/core/commit/fb619cf9a440239f0ba88e327d10001a6a3c8171))
|
||||
* **compiler-sfc:** support `:is` and `:where` selector in scoped css rewrite ([#8929](https://github.com/vuejs/core/issues/8929)) ([c6083dc](https://github.com/vuejs/core/commit/c6083dcad31f3e9292c687fada9e32f287e2317f))
|
||||
* **compiler-sfc:** use correct compiler when re-parsing in ssr mode ([678378a](https://github.com/vuejs/core/commit/678378afd559481badb486b243722b6287862e09))
|
||||
|
||||
|
||||
* feat!: remove reactivity transform (#9321) ([79b8a09](https://github.com/vuejs/core/commit/79b8a0905bf363bf82edd2096fef10c3db6d9c3c)), closes [#9321](https://github.com/vuejs/core/issues/9321)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** support specifying root namespace when parsing ([40f72d5](https://github.com/vuejs/core/commit/40f72d5e50b389cb11b7ca13461aa2a75ddacdb4))
|
||||
* **compiler-core:** support v-bind shorthand for key and value with the same name ([#9451](https://github.com/vuejs/core/issues/9451)) ([26399aa](https://github.com/vuejs/core/commit/26399aa6fac1596b294ffeba06bb498d86f5508c))
|
||||
* **compiler:** improve parsing tolerance for language-tools ([41ff68e](https://github.com/vuejs/core/commit/41ff68ea579d933333392146625560359acb728a))
|
||||
* **reactivity:** expose last result for computed getter ([#9497](https://github.com/vuejs/core/issues/9497)) ([48b47a1](https://github.com/vuejs/core/commit/48b47a1ab63577e2dbd91947eea544e3ef185b85))
|
||||
|
||||
|
||||
### Performance Improvements
|
||||
|
||||
* avoid sfc source map unnecessary serialization and parsing ([f15d2f6](https://github.com/vuejs/core/commit/f15d2f6cf69c0c39f8dfb5c33122790c68bf92e2))
|
||||
* **codegen:** optimize line / column calculation during codegen ([3be53d9](https://github.com/vuejs/core/commit/3be53d9b974dae1a10eb795cade71ae765e17574))
|
||||
* **codegen:** optimize source map generation ([c11002f](https://github.com/vuejs/core/commit/c11002f16afd243a2b15b546816e73882eea9e4d))
|
||||
* **compiler-sfc:** remove magic-string trim on script ([e8e3ec6](https://github.com/vuejs/core/commit/e8e3ec6ca7392e43975c75b56eaaa711d5ea9410))
|
||||
* **compiler-sfc:** use faster source map addMapping ([50cde7c](https://github.com/vuejs/core/commit/50cde7cfbcc49022ba88f5f69fa9b930b483c282))
|
||||
* optimize away isBuiltInType ([66c0ed0](https://github.com/vuejs/core/commit/66c0ed0a3c1c6f37dafc6b1c52b75c6bf60e3136))
|
||||
* optimize makeMap ([ae6fba9](https://github.com/vuejs/core/commit/ae6fba94954bac6430902f77b0d1113a98a75b18))
|
||||
* optimize position cloning ([2073236](https://github.com/vuejs/core/commit/20732366b9b3530d33b842cf1fc985919afb9317))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* Reactivity Transform was marked deprecated in 3.3 and is now removed in 3.4. This change does not require a major due to the feature being experimental. Users who wish to continue using the feature can do so via the external plugin at https://vue-macros.dev/features/reactivity-transform.html
|
||||
|
||||
|
||||
|
||||
# [3.4.0-alpha.1](https://github.com/vuejs/core/compare/v3.3.7...v3.4.0-alpha.1) (2023-10-28)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **compiler-core:** export error message ([#8729](https://github.com/vuejs/core/issues/8729)) ([f7e80ee](https://github.com/vuejs/core/commit/f7e80ee4a065a9eaba98720abf415d9e87756cbd))
|
||||
* **compiler-sfc:** expose resolve type-based props and emits ([#8874](https://github.com/vuejs/core/issues/8874)) ([9e77580](https://github.com/vuejs/core/commit/9e77580c0c2f0d977bd0031a1d43cc334769d433))
|
||||
* export runtime error strings ([#9301](https://github.com/vuejs/core/issues/9301)) ([feb2f2e](https://github.com/vuejs/core/commit/feb2f2edce2d91218a5e9a52c81e322e4033296b))
|
||||
* **reactivity:** more efficient reactivity system ([#5912](https://github.com/vuejs/core/issues/5912)) ([16e06ca](https://github.com/vuejs/core/commit/16e06ca08f5a1e2af3fc7fb35de153dbe0c3087d)), closes [#311](https://github.com/vuejs/core/issues/311) [#1811](https://github.com/vuejs/core/issues/1811) [#6018](https://github.com/vuejs/core/issues/6018) [#7160](https://github.com/vuejs/core/issues/7160) [#8714](https://github.com/vuejs/core/issues/8714) [#9149](https://github.com/vuejs/core/issues/9149) [#9419](https://github.com/vuejs/core/issues/9419) [#9464](https://github.com/vuejs/core/issues/9464)
|
||||
* **runtime-core:** add `once` option to watch ([#9034](https://github.com/vuejs/core/issues/9034)) ([a645e7a](https://github.com/vuejs/core/commit/a645e7aa51006516ba668b3a4365d296eb92ee7d))
|
||||
|
||||
|
||||
|
||||
## [3.3.10](https://github.com/vuejs/core/compare/v3.3.9...v3.3.10) (2023-12-04)
|
||||
|
||||
|
||||
|
|
26
package.json
26
package.json
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"private": true,
|
||||
"version": "0.0.0-vapor",
|
||||
"packageManager": "pnpm@8.11.0",
|
||||
"packageManager": "pnpm@8.12.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "node scripts/dev.js vue vue-vapor",
|
||||
|
@ -22,6 +22,7 @@
|
|||
"test-dts": "run-s build-dts test-dts-only",
|
||||
"test-dts-only": "tsc -p ./packages/dts-test/tsconfig.test.json",
|
||||
"test-coverage": "vitest -c vitest.unit.config.ts --coverage",
|
||||
"test-bench": "vitest bench",
|
||||
"release": "node scripts/release.js",
|
||||
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
|
||||
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
|
||||
|
@ -61,6 +62,7 @@
|
|||
"devDependencies": {
|
||||
"@babel/parser": "^7.23.5",
|
||||
"@babel/types": "^7.23.5",
|
||||
"@codspeed/vitest-plugin": "^2.3.1",
|
||||
"@rollup/plugin-alias": "^5.0.1",
|
||||
"@rollup/plugin-commonjs": "^25.0.7",
|
||||
"@rollup/plugin-json": "^6.0.1",
|
||||
|
@ -69,33 +71,33 @@
|
|||
"@rollup/plugin-terser": "^0.4.4",
|
||||
"@types/hash-sum": "^1.0.2",
|
||||
"@types/minimist": "^1.2.5",
|
||||
"@types/node": "^20.10.3",
|
||||
"@types/node": "^20.10.4",
|
||||
"@types/semver": "^7.5.5",
|
||||
"@typescript-eslint/parser": "^6.13.0",
|
||||
"@vitest/coverage-istanbul": "^0.34.6",
|
||||
"@typescript-eslint/parser": "^6.13.2",
|
||||
"@vitest/coverage-istanbul": "^1.0.4",
|
||||
"@vue/consolidate": "0.17.3",
|
||||
"conventional-changelog-cli": "^4.1.0",
|
||||
"enquirer": "^2.4.1",
|
||||
"esbuild": "^0.19.5",
|
||||
"esbuild-plugin-polyfill-node": "^0.3.0",
|
||||
"eslint": "^8.54.0",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-define-config": "^1.24.1",
|
||||
"eslint-plugin-jest": "^27.6.0",
|
||||
"estree-walker": "^2.0.2",
|
||||
"execa": "^8.0.1",
|
||||
"jsdom": "^22.1.0",
|
||||
"lint-staged": "^15.1.0",
|
||||
"jsdom": "^23.0.1",
|
||||
"lint-staged": "^15.2.0",
|
||||
"lodash": "^4.17.21",
|
||||
"magic-string": "^0.30.5",
|
||||
"markdown-table": "^3.0.3",
|
||||
"marked": "^9.1.6",
|
||||
"marked": "^11.0.1",
|
||||
"minimist": "^1.2.8",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"picocolors": "^1.0.0",
|
||||
"prettier": "^3.1.0",
|
||||
"prettier": "^3.1.1",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pug": "^3.0.2",
|
||||
"puppeteer": "~21.5.2",
|
||||
"puppeteer": "~21.6.0",
|
||||
"rimraf": "^5.0.5",
|
||||
"rollup": "^4.1.4",
|
||||
"rollup-plugin-dts": "^6.1.0",
|
||||
|
@ -109,7 +111,7 @@
|
|||
"tslib": "^2.6.2",
|
||||
"tsx": "^4.6.2",
|
||||
"typescript": "^5.2.2",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^1.0.0"
|
||||
"vite": "^5.0.5",
|
||||
"vitest": "^1.0.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -201,6 +201,26 @@ describe('compiler: transform', () => {
|
|||
expect((ast as any).children[1].props[0].exp.content).toBe(`_hoisted_2`)
|
||||
})
|
||||
|
||||
test('context.filename and selfName', () => {
|
||||
const ast = baseParse(`<div />`)
|
||||
|
||||
const calls: any[] = []
|
||||
const plugin: NodeTransform = (node, context) => {
|
||||
calls.push({ ...context })
|
||||
}
|
||||
|
||||
transform(ast, {
|
||||
filename: '/the/fileName.vue',
|
||||
nodeTransforms: [plugin]
|
||||
})
|
||||
|
||||
expect(calls.length).toBe(2)
|
||||
expect(calls[1]).toMatchObject({
|
||||
filename: '/the/fileName.vue',
|
||||
selfName: 'FileName'
|
||||
})
|
||||
})
|
||||
|
||||
test('onError option', () => {
|
||||
const ast = baseParse(`<div/>`)
|
||||
const loc = ast.children[0].loc
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-core",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/compiler-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-core.esm-bundler.js",
|
||||
|
|
|
@ -441,3 +441,11 @@ export const TS_NODE_TYPES = [
|
|||
'TSInstantiationExpression', // foo<string>
|
||||
'TSSatisfiesExpression' // foo satisfies T
|
||||
]
|
||||
|
||||
export function unwrapTSNode(node: Node): Node {
|
||||
if (TS_NODE_TYPES.includes(node.type)) {
|
||||
return unwrapTSNode((node as any).expression)
|
||||
} else {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,13 +90,13 @@ export enum ErrorCodes {
|
|||
X_V_MODEL_ON_PROPS,
|
||||
X_INVALID_EXPRESSION,
|
||||
X_KEEP_ALIVE_INVALID_CHILDREN,
|
||||
X_VNODE_HOOKS,
|
||||
|
||||
// generic errors
|
||||
X_PREFIX_ID_NOT_SUPPORTED,
|
||||
X_MODULE_MODE_NOT_SUPPORTED,
|
||||
X_CACHE_HANDLER_NOT_SUPPORTED,
|
||||
X_SCOPE_ID_NOT_SUPPORTED,
|
||||
X_VNODE_HOOKS,
|
||||
|
||||
// Special value for higher-order compilers to pick up the last code
|
||||
// to avoid collision of error codes. This should always be kept as the last
|
||||
|
@ -173,7 +173,7 @@ export const errorMessages: Record<ErrorCodes, string> = {
|
|||
[ErrorCodes.X_V_MODEL_ON_PROPS]: `v-model cannot be used on a prop, because local prop bindings are not writable.\nUse a v-bind binding combined with a v-on listener that emits update:x event instead.`,
|
||||
[ErrorCodes.X_INVALID_EXPRESSION]: `Error parsing JavaScript expression: `,
|
||||
[ErrorCodes.X_KEEP_ALIVE_INVALID_CHILDREN]: `<KeepAlive> expects exactly one child component.`,
|
||||
[ErrorCodes.X_VNODE_HOOKS]: `@vnode-* hooks in templates are deprecated. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support will be removed in 3.4.`,
|
||||
[ErrorCodes.X_VNODE_HOOKS]: `@vnode-* hooks in templates are no longer supported. Use the vue: prefix instead. For example, @vnode-mounted should be changed to @vue:mounted. @vnode-* hooks support has been removed in 3.4.`,
|
||||
|
||||
// generic errors
|
||||
[ErrorCodes.X_PREFIX_ID_NOT_SUPPORTED]: `"prefixIdentifiers" option is not supported in this build of compiler.`,
|
||||
|
|
|
@ -83,9 +83,7 @@ export interface ImportItem {
|
|||
}
|
||||
|
||||
export interface TransformContext
|
||||
extends Required<
|
||||
Omit<TransformOptions, 'filename' | keyof CompilerCompatOptions>
|
||||
>,
|
||||
extends Required<Omit<TransformOptions, keyof CompilerCompatOptions>>,
|
||||
CompilerCompatOptions {
|
||||
selfName: string | null
|
||||
root: RootNode
|
||||
|
@ -153,6 +151,7 @@ export function createTransformContext(
|
|||
const nameMatch = filename.replace(/\?.*$/, '').match(/([^/\\]+)\.\w+$/)
|
||||
const context: TransformContext = {
|
||||
// options
|
||||
filename,
|
||||
selfName: nameMatch && capitalize(camelize(nameMatch[1])),
|
||||
prefixIdentifiers,
|
||||
hoistStatic,
|
||||
|
|
|
@ -40,6 +40,7 @@ import { isString, isObject, NOOP } from '@vue/shared'
|
|||
import { PropsExpression } from './transforms/transformElement'
|
||||
import { parseExpression } from '@babel/parser'
|
||||
import { Expression } from '@babel/types'
|
||||
import { unwrapTSNode } from './babelUtils'
|
||||
|
||||
export const isStaticExp = (p: JSChildNode): p is SimpleExpressionNode =>
|
||||
p.type === NodeTypes.SIMPLE_EXPRESSION && p.isStatic
|
||||
|
@ -164,9 +165,7 @@ export const isMemberExpressionNode = __BROWSER__
|
|||
let ret: Expression = parseExpression(path, {
|
||||
plugins: options.expressionPlugins
|
||||
})
|
||||
if (ret.type === 'TSAsExpression' || ret.type === 'TSTypeAssertion') {
|
||||
ret = ret.expression
|
||||
}
|
||||
ret = unwrapTSNode(ret) as Expression
|
||||
return (
|
||||
ret.type === 'MemberExpression' ||
|
||||
ret.type === 'OptionalMemberExpression' ||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-dom",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/compiler-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/compiler-dom.esm-bundler.js",
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { ParserOptions, NodeTypes, Namespaces } from '@vue/compiler-core'
|
||||
import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
|
||||
import { isVoidTag, isHTMLTag, isSVGTag, isMathMLTag } from '@vue/shared'
|
||||
import { TRANSITION, TRANSITION_GROUP } from './runtimeHelpers'
|
||||
import { decodeHtmlBrowser } from './decodeHtmlBrowser'
|
||||
|
||||
export const parserOptions: ParserOptions = {
|
||||
parseMode: 'html',
|
||||
isVoidTag,
|
||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
|
||||
isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),
|
||||
isPreTag: tag => tag === 'pre',
|
||||
decodeEntities: __BROWSER__ ? decodeHtmlBrowser : undefined,
|
||||
|
||||
|
|
|
@ -1483,3 +1483,31 @@ _sfc_.setup = __setup__
|
|||
: __injectCSSVars__
|
||||
"
|
||||
`;
|
||||
|
||||
exports[`SFC genDefaultAs > parser plugins > import attributes (user override for deprecated syntax) 1`] = `
|
||||
"import { foo } from './foo.js' assert { type: 'foobar' }
|
||||
|
||||
export default {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
return { get foo() { return foo } }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`SFC genDefaultAs > parser plugins > import attributes 1`] = `
|
||||
"import { foo } from './foo.js' with { type: 'foobar' }
|
||||
|
||||
export default {
|
||||
setup(__props, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
|
||||
return { get foo() { return foo } }
|
||||
}
|
||||
|
||||
}"
|
||||
`;
|
||||
|
|
|
@ -1600,4 +1600,38 @@ describe('SFC genDefaultAs', () => {
|
|||
foo: BindingTypes.SETUP_REF
|
||||
})
|
||||
})
|
||||
|
||||
describe('parser plugins', () => {
|
||||
test('import attributes', () => {
|
||||
const { content } = compile(`
|
||||
<script setup>
|
||||
import { foo } from './foo.js' with { type: 'foobar' }
|
||||
</script>
|
||||
`)
|
||||
assertCode(content)
|
||||
|
||||
expect(() =>
|
||||
compile(`
|
||||
<script setup>
|
||||
import { foo } from './foo.js' assert { type: 'foobar' }
|
||||
</script>`)
|
||||
).toThrow()
|
||||
})
|
||||
|
||||
test('import attributes (user override for deprecated syntax)', () => {
|
||||
const { content } = compile(
|
||||
`
|
||||
<script setup>
|
||||
import { foo } from './foo.js' assert { type: 'foobar' }
|
||||
</script>
|
||||
`,
|
||||
{
|
||||
babelParserPlugins: [
|
||||
['importAttributes', { deprecatedAssertSyntax: true }]
|
||||
]
|
||||
}
|
||||
)
|
||||
assertCode(content)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -18,6 +18,47 @@ return { props, bar }
|
|||
}"
|
||||
`;
|
||||
|
||||
exports[`defineProps > custom element retains the props type & default value & production mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
interface Props {
|
||||
foo?: number;
|
||||
}
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
__name: 'app.ce',
|
||||
props: {
|
||||
foo: { default: 5.5, type: Number }
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props;
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > custom element retains the props type & production mode 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
export default /*#__PURE__*/_defineComponent({
|
||||
__name: 'app.ce',
|
||||
props: {
|
||||
foo: {type: Number}
|
||||
},
|
||||
setup(__props: any, { expose: __expose }) {
|
||||
__expose();
|
||||
|
||||
const props = __props
|
||||
|
||||
return { props }
|
||||
}
|
||||
|
||||
})"
|
||||
`;
|
||||
|
||||
exports[`defineProps > defineProps w/ runtime options 1`] = `
|
||||
"import { defineComponent as _defineComponent } from 'vue'
|
||||
|
||||
|
|
|
@ -10,8 +10,7 @@ describe('defineModel()', () => {
|
|||
const c = defineModel('count')
|
||||
const toString = defineModel('toString', { type: Function })
|
||||
</script>
|
||||
`,
|
||||
{ defineModel: true }
|
||||
`
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch('props: {')
|
||||
|
@ -44,8 +43,7 @@ describe('defineModel()', () => {
|
|||
defineEmits(['change'])
|
||||
const count = defineModel({ default: 0 })
|
||||
</script>
|
||||
`,
|
||||
{ defineModel: true }
|
||||
`
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels({ foo: String }`)
|
||||
|
@ -66,8 +64,7 @@ describe('defineModel()', () => {
|
|||
defineProps(['foo', 'bar'])
|
||||
const count = defineModel('count')
|
||||
</script>
|
||||
`,
|
||||
{ defineModel: true }
|
||||
`
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`props: /*#__PURE__*/_mergeModels(['foo', 'bar'], {
|
||||
|
@ -94,8 +91,7 @@ describe('defineModel()', () => {
|
|||
|
||||
const local = true
|
||||
const hoist = defineModel('hoist', { local })
|
||||
</script>`,
|
||||
{ defineModel: true }
|
||||
</script>`
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch(`_useModel(__props, "modelValue", { local: true })`)
|
||||
|
@ -115,8 +111,7 @@ describe('defineModel()', () => {
|
|||
const disabled = defineModel<number>('disabled', { required: false })
|
||||
const any = defineModel<any | boolean>('any')
|
||||
</script>
|
||||
`,
|
||||
{ defineModel: true }
|
||||
`
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch('"modelValue": { type: [Boolean, String] }')
|
||||
|
@ -155,7 +150,7 @@ describe('defineModel()', () => {
|
|||
const optional = defineModel<string>('optional', { required: false })
|
||||
</script>
|
||||
`,
|
||||
{ defineModel: true, isProd: true }
|
||||
{ isProd: true }
|
||||
)
|
||||
assertCode(content)
|
||||
expect(content).toMatch('"modelValue": { type: Boolean }')
|
||||
|
|
|
@ -710,4 +710,35 @@ const props = defineProps({ foo: String })
|
|||
'da-sh': BindingTypes.PROPS
|
||||
})
|
||||
})
|
||||
|
||||
// #8989
|
||||
test('custom element retains the props type & production mode', () => {
|
||||
const { content } = compile(
|
||||
`<script setup lang="ts">
|
||||
const props = defineProps<{ foo: number}>()
|
||||
</script>`,
|
||||
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
|
||||
{ filename: 'app.ce.vue' }
|
||||
)
|
||||
|
||||
expect(content).toMatch(`foo: {type: Number}`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
test('custom element retains the props type & default value & production mode', () => {
|
||||
const { content } = compile(
|
||||
`<script setup lang="ts">
|
||||
interface Props {
|
||||
foo?: number;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
foo: 5.5,
|
||||
});
|
||||
</script>`,
|
||||
{ isProd: true, customElement: filename => /\.ce\.vue$/.test(filename) },
|
||||
{ filename: 'app.ce.vue' }
|
||||
)
|
||||
expect(content).toMatch(`foo: { default: 5.5, type: Number }`)
|
||||
assertCode(content)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -28,7 +28,10 @@ export function assertCode(code: string) {
|
|||
try {
|
||||
babelParse(code, {
|
||||
sourceType: 'module',
|
||||
plugins: ['typescript']
|
||||
plugins: [
|
||||
'typescript',
|
||||
['importAttributes', { deprecatedAssertSyntax: true }]
|
||||
]
|
||||
})
|
||||
} catch (e: any) {
|
||||
console.log(code)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-sfc",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/compiler-sfc",
|
||||
"main": "dist/compiler-sfc.cjs.js",
|
||||
"module": "dist/compiler-sfc.esm-browser.js",
|
||||
|
|
|
@ -2,7 +2,8 @@ import {
|
|||
BindingTypes,
|
||||
UNREF,
|
||||
isFunctionType,
|
||||
walkIdentifiers
|
||||
walkIdentifiers,
|
||||
unwrapTSNode
|
||||
} from '@vue/compiler-dom'
|
||||
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
|
||||
import { ParserPlugin } from '@babel/parser'
|
||||
|
@ -43,12 +44,7 @@ import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
|
|||
import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
|
||||
import { processDefineSlots } from './script/defineSlots'
|
||||
import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
|
||||
import {
|
||||
isLiteralNode,
|
||||
unwrapTSNode,
|
||||
isCallOf,
|
||||
getImportedName
|
||||
} from './script/utils'
|
||||
import { isLiteralNode, isCallOf, getImportedName } from './script/utils'
|
||||
import { analyzeScriptBindings } from './script/analyzeScriptBindings'
|
||||
import { isImportUsed } from './script/importUsageCheck'
|
||||
import { processAwait } from './script/topLevelAwait'
|
||||
|
@ -102,11 +98,6 @@ export interface SFCScriptCompileOptions {
|
|||
* @default true
|
||||
*/
|
||||
hoistStatic?: boolean
|
||||
/**
|
||||
* (**Experimental**) Enable macro `defineModel`
|
||||
* @default false
|
||||
*/
|
||||
defineModel?: boolean
|
||||
/**
|
||||
* (**Experimental**) Enable reactive destructure for `defineProps`
|
||||
* @default false
|
||||
|
@ -121,6 +112,10 @@ export interface SFCScriptCompileOptions {
|
|||
fileExists(file: string): boolean
|
||||
readFile(file: string): string | undefined
|
||||
}
|
||||
/**
|
||||
* Transform Vue SFCs into custom elements.
|
||||
*/
|
||||
customElement?: boolean | ((filename: string) => boolean)
|
||||
}
|
||||
|
||||
export interface ImportBinding {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { parse } from '@babel/parser'
|
|||
import MagicString from 'magic-string'
|
||||
import type { ParserPlugin } from '@babel/parser'
|
||||
import type { Identifier, Statement } from '@babel/types'
|
||||
import { resolveParserPlugins } from './script/context'
|
||||
|
||||
export function rewriteDefault(
|
||||
input: string,
|
||||
|
@ -10,7 +11,7 @@ export function rewriteDefault(
|
|||
): string {
|
||||
const ast = parse(input, {
|
||||
sourceType: 'module',
|
||||
plugins: parserPlugins
|
||||
plugins: resolveParserPlugins('js', parserPlugins)
|
||||
}).program.body
|
||||
const s = new MagicString(input)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { CallExpression, Node, ObjectPattern, Program } from '@babel/types'
|
||||
import { SFCDescriptor } from '../parse'
|
||||
import { generateCodeFrame } from '@vue/shared'
|
||||
import { generateCodeFrame, isArray } from '@vue/shared'
|
||||
import { parse as babelParse, ParserPlugin } from '@babel/parser'
|
||||
import { ImportBinding, SFCScriptCompileOptions } from '../compileScript'
|
||||
import { PropsDestructureBindings } from './defineProps'
|
||||
|
@ -12,6 +12,7 @@ import { TypeScope } from './resolveType'
|
|||
export class ScriptCompileContext {
|
||||
isJS: boolean
|
||||
isTS: boolean
|
||||
isCE = false
|
||||
|
||||
scriptAst: Program | null
|
||||
scriptSetupAst: Program | null
|
||||
|
@ -95,6 +96,14 @@ export class ScriptCompileContext {
|
|||
scriptSetupLang === 'ts' ||
|
||||
scriptSetupLang === 'tsx'
|
||||
|
||||
const customElement = options.customElement
|
||||
const filename = this.descriptor.filename
|
||||
if (customElement) {
|
||||
this.isCE =
|
||||
typeof customElement === 'boolean'
|
||||
? customElement
|
||||
: customElement(filename)
|
||||
}
|
||||
// resolve parser plugins
|
||||
const plugins: ParserPlugin[] = resolveParserPlugins(
|
||||
(scriptLang || scriptSetupLang)!,
|
||||
|
@ -155,6 +164,17 @@ export function resolveParserPlugins(
|
|||
dts = false
|
||||
) {
|
||||
const plugins: ParserPlugin[] = []
|
||||
if (
|
||||
!userPlugins ||
|
||||
!userPlugins.some(
|
||||
p =>
|
||||
p === 'importAssertions' ||
|
||||
p === 'importAttributes' ||
|
||||
(isArray(p) && p[0] === 'importAttributes')
|
||||
)
|
||||
) {
|
||||
plugins.push('importAttributes')
|
||||
}
|
||||
if (lang === 'jsx' || lang === 'tsx') {
|
||||
plugins.push('jsx')
|
||||
} else if (userPlugins) {
|
||||
|
@ -163,7 +183,7 @@ export function resolveParserPlugins(
|
|||
userPlugins = userPlugins.filter(p => p !== 'jsx')
|
||||
}
|
||||
if (lang === 'ts' || lang === 'tsx') {
|
||||
plugins.push(['typescript', { dts }])
|
||||
plugins.push(['typescript', { dts }], 'explicitResourceManagement')
|
||||
if (!userPlugins || !userPlugins.includes('decorators')) {
|
||||
plugins.push('decorators-legacy')
|
||||
}
|
||||
|
|
|
@ -5,11 +5,9 @@ import {
|
|||
UNKNOWN_TYPE,
|
||||
concatStrings,
|
||||
isCallOf,
|
||||
toRuntimeTypeString,
|
||||
unwrapTSNode
|
||||
toRuntimeTypeString
|
||||
} from './utils'
|
||||
import { BindingTypes } from '@vue/compiler-dom'
|
||||
import { warnOnce } from '../warn'
|
||||
import { BindingTypes, unwrapTSNode } from '@vue/compiler-dom'
|
||||
|
||||
export const DEFINE_MODEL = 'defineModel'
|
||||
|
||||
|
@ -28,21 +26,6 @@ export function processDefineModel(
|
|||
return false
|
||||
}
|
||||
|
||||
if (!ctx.options.defineModel) {
|
||||
warnOnce(
|
||||
`defineModel() is an experimental feature and disabled by default.\n` +
|
||||
`To enable it, follow the RFC at https://github.com/vuejs/rfcs/discussions/503.`
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
warnOnce(
|
||||
`This project is using defineModel(), which is an experimental ` +
|
||||
`feature. It may receive breaking changes or be removed in the future, so ` +
|
||||
`use at your own risk.\n` +
|
||||
`To stay updated, follow the RFC at https://github.com/vuejs/rfcs/discussions/503.`
|
||||
)
|
||||
|
||||
ctx.hasDefineModelCall = true
|
||||
|
||||
const type =
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { Node } from '@babel/types'
|
||||
import { unwrapTSNode } from '@vue/compiler-dom'
|
||||
import { ScriptCompileContext } from './context'
|
||||
import { isCallOf, unwrapTSNode } from './utils'
|
||||
import { isCallOf } from './utils'
|
||||
import { DEFINE_PROPS } from './defineProps'
|
||||
import { DEFINE_EMITS } from './defineEmits'
|
||||
import { DEFINE_EXPOSE } from './defineExpose'
|
||||
|
|
|
@ -6,7 +6,7 @@ import {
|
|||
ObjectExpression,
|
||||
Expression
|
||||
} from '@babel/types'
|
||||
import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
|
||||
import { BindingTypes, isFunctionType, unwrapTSNode } from '@vue/compiler-dom'
|
||||
import { ScriptCompileContext } from './context'
|
||||
import {
|
||||
TypeResolveContext,
|
||||
|
@ -19,7 +19,6 @@ import {
|
|||
concatStrings,
|
||||
isLiteralNode,
|
||||
isCallOf,
|
||||
unwrapTSNode,
|
||||
toRuntimeTypeString,
|
||||
getEscapedPropName
|
||||
} from './utils'
|
||||
|
@ -282,6 +281,17 @@ function genRuntimePropFromType(
|
|||
defaultString
|
||||
])} }`
|
||||
} else {
|
||||
// #8989 for custom element, should keep the type
|
||||
if (ctx.isCE) {
|
||||
if (defaultString) {
|
||||
return `${finalKey}: ${`{ ${defaultString}, type: ${toRuntimeTypeString(
|
||||
type
|
||||
)} }`}`
|
||||
} else {
|
||||
return `${finalKey}: {type: ${toRuntimeTypeString(type)}}`
|
||||
}
|
||||
}
|
||||
|
||||
// production: checks are useless
|
||||
return `${finalKey}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
|
||||
}
|
||||
|
|
|
@ -15,10 +15,11 @@ import {
|
|||
isInDestructureAssignment,
|
||||
isReferencedIdentifier,
|
||||
isStaticProperty,
|
||||
walkFunctionParams
|
||||
walkFunctionParams,
|
||||
unwrapTSNode
|
||||
} from '@vue/compiler-dom'
|
||||
import { genPropsAccessExp } from '@vue/shared'
|
||||
import { isCallOf, resolveObjectKey, unwrapTSNode } from './utils'
|
||||
import { isCallOf, resolveObjectKey } from './utils'
|
||||
import { ScriptCompileContext } from './context'
|
||||
import { DEFINE_PROPS } from './defineProps'
|
||||
import { warnOnce } from '../warn'
|
||||
|
|
|
@ -83,6 +83,9 @@ export type SimpleTypeResolveContext = Pick<
|
|||
|
||||
// emits
|
||||
| 'emitsTypeDecl'
|
||||
|
||||
// customElement
|
||||
| 'isCE'
|
||||
> &
|
||||
Partial<
|
||||
Pick<ScriptCompileContext, 'scope' | 'globalScopes' | 'deps' | 'fs'>
|
||||
|
@ -1475,6 +1478,7 @@ export function inferRuntimeType(
|
|||
scope
|
||||
)
|
||||
}
|
||||
break
|
||||
case 'TSMethodSignature':
|
||||
case 'TSFunctionType':
|
||||
return ['Function']
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
StringLiteral
|
||||
} from '@babel/types'
|
||||
import path from 'path'
|
||||
import { TS_NODE_TYPES } from '@vue/compiler-dom'
|
||||
|
||||
export const UNKNOWN_TYPE = 'Unknown'
|
||||
|
||||
|
@ -32,14 +31,6 @@ export function isLiteralNode(node: Node) {
|
|||
return node.type.endsWith('Literal')
|
||||
}
|
||||
|
||||
export function unwrapTSNode(node: Node): Node {
|
||||
if (TS_NODE_TYPES.includes(node.type)) {
|
||||
return unwrapTSNode((node as any).expression)
|
||||
} else {
|
||||
return node
|
||||
}
|
||||
}
|
||||
|
||||
export function isCallOf(
|
||||
node: Node | null | undefined,
|
||||
test: string | ((id: string) => boolean) | null | undefined
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compiler-ssr",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/compiler-ssr",
|
||||
"main": "dist/compiler-ssr.cjs.js",
|
||||
"types": "dist/compiler-ssr.d.ts",
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { createApp } from 'vue'
|
||||
import { expectType } from './utils'
|
||||
|
||||
const app = createApp({})
|
||||
|
||||
app.directive<HTMLElement, string>('custom', {
|
||||
mounted(el, binding) {
|
||||
expectType<HTMLElement>(el)
|
||||
expectType<string>(binding.value)
|
||||
|
||||
// @ts-expect-error not any
|
||||
expectType<number>(binding.value)
|
||||
}
|
||||
})
|
|
@ -8,14 +8,22 @@ import {
|
|||
FunctionalComponent,
|
||||
ComponentPublicInstance,
|
||||
toRefs,
|
||||
SetupContext
|
||||
SetupContext,
|
||||
EmitsOptions
|
||||
} from 'vue'
|
||||
import { describe, expectAssignable, expectType, IsAny } from './utils'
|
||||
|
||||
declare function extractComponentOptions<Props, RawBindings>(
|
||||
obj: Component<Props, RawBindings>
|
||||
declare function extractComponentOptions<
|
||||
Props,
|
||||
RawBindings,
|
||||
Emits extends EmitsOptions | Record<string, any[]>,
|
||||
Slots extends Record<string, any>
|
||||
>(
|
||||
obj: Component<Props, RawBindings, any, any, any, Emits, Slots>
|
||||
): {
|
||||
props: Props
|
||||
emits: Emits
|
||||
slots: Slots
|
||||
rawBindings: RawBindings
|
||||
setup: ShallowUnwrapRef<RawBindings>
|
||||
}
|
||||
|
@ -455,11 +463,27 @@ describe('functional', () => {
|
|||
})
|
||||
|
||||
describe('typed', () => {
|
||||
const MyComponent: FunctionalComponent<{ foo: number }> = (_, _2) => {}
|
||||
type Props = { foo: number }
|
||||
type Emits = { change: [value: string]; inc: [value: number] }
|
||||
type Slots = { default: (scope: { foo: string }) => any }
|
||||
|
||||
const { props } = extractComponentOptions(MyComponent)
|
||||
const MyComponent: FunctionalComponent<Props, Emits, Slots> = (
|
||||
props,
|
||||
{ emit, slots }
|
||||
) => {
|
||||
expectType<Props>(props)
|
||||
expectType<{
|
||||
(event: 'change', value: string): void
|
||||
(event: 'inc', value: number): void
|
||||
}>(emit)
|
||||
expectType<Slots>(slots)
|
||||
}
|
||||
|
||||
expectType<number>(props.foo)
|
||||
const { props, emits, slots } = extractComponentOptions(MyComponent)
|
||||
|
||||
expectType<Props>(props)
|
||||
expectType<Emits>(emits)
|
||||
expectType<Slots>(slots)
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -481,4 +505,18 @@ describe('SetupContext', () => {
|
|||
|
||||
expectAssignable<SetupContext<{ b: () => true }>>(wider)
|
||||
})
|
||||
|
||||
describe('short emits', () => {
|
||||
const {
|
||||
emit
|
||||
}: SetupContext<{
|
||||
a: [val: string]
|
||||
b: [val: number]
|
||||
}> = {} as any
|
||||
|
||||
expectType<{
|
||||
(event: 'a', val: string): void
|
||||
(event: 'b', val: number): void
|
||||
}>(emit)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -0,0 +1,139 @@
|
|||
import {
|
||||
defineComponent,
|
||||
FunctionalComponent,
|
||||
ComponentPublicInstance,
|
||||
ComponentInstance,
|
||||
ref
|
||||
} from 'vue'
|
||||
import { expectType, describe } from './utils'
|
||||
|
||||
describe('defineComponent', () => {
|
||||
const CompSetup = defineComponent({
|
||||
props: {
|
||||
test: String
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
a: 1
|
||||
}
|
||||
}
|
||||
})
|
||||
const compSetup: ComponentInstance<typeof CompSetup> = {} as any
|
||||
|
||||
expectType<string | undefined>(compSetup.test)
|
||||
expectType<number>(compSetup.a)
|
||||
expectType<ComponentPublicInstance>(compSetup)
|
||||
})
|
||||
describe('functional component', () => {
|
||||
// Functional
|
||||
const CompFunctional: FunctionalComponent<{ test?: string }> = {} as any
|
||||
const compFunctional: ComponentInstance<typeof CompFunctional> = {} as any
|
||||
|
||||
expectType<string | undefined>(compFunctional.test)
|
||||
expectType<ComponentPublicInstance>(compFunctional)
|
||||
|
||||
const CompFunction: (props: { test?: string }) => any = {} as any
|
||||
const compFunction: ComponentInstance<typeof CompFunction> = {} as any
|
||||
|
||||
expectType<string | undefined>(compFunction.test)
|
||||
expectType<ComponentPublicInstance>(compFunction)
|
||||
})
|
||||
|
||||
describe('options component', () => {
|
||||
// Options
|
||||
const CompOptions = defineComponent({
|
||||
props: {
|
||||
test: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
a: 1
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
b() {
|
||||
return 'test'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
func(a: string) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
})
|
||||
const compOptions: ComponentInstance<typeof CompOptions> = {} as any
|
||||
expectType<string | undefined>(compOptions.test)
|
||||
expectType<number>(compOptions.a)
|
||||
expectType<(a: string) => boolean>(compOptions.func)
|
||||
expectType<ComponentPublicInstance>(compOptions)
|
||||
})
|
||||
|
||||
describe('object no defineComponent', () => {
|
||||
// object - no defineComponent
|
||||
|
||||
const CompObjectSetup = {
|
||||
props: {
|
||||
test: String
|
||||
},
|
||||
setup() {
|
||||
return {
|
||||
a: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
const compObjectSetup: ComponentInstance<typeof CompObjectSetup> = {} as any
|
||||
expectType<string | undefined>(compObjectSetup.test)
|
||||
expectType<number>(compObjectSetup.a)
|
||||
expectType<ComponentPublicInstance>(compObjectSetup)
|
||||
|
||||
const CompObjectData = {
|
||||
props: {
|
||||
test: String
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
a: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
const compObjectData: ComponentInstance<typeof CompObjectData> = {} as any
|
||||
expectType<string | undefined>(compObjectData.test)
|
||||
expectType<number>(compObjectData.a)
|
||||
expectType<ComponentPublicInstance>(compObjectData)
|
||||
|
||||
const CompObjectNoProps = {
|
||||
data() {
|
||||
return {
|
||||
a: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
const compObjectNoProps: ComponentInstance<typeof CompObjectNoProps> =
|
||||
{} as any
|
||||
expectType<string | undefined>(compObjectNoProps.test)
|
||||
expectType<number>(compObjectNoProps.a)
|
||||
expectType<ComponentPublicInstance>(compObjectNoProps)
|
||||
})
|
||||
|
||||
describe('Generic component', () => {
|
||||
const Comp = defineComponent(
|
||||
// TODO: babel plugin to auto infer runtime props options from type
|
||||
// similar to defineProps<{...}>()
|
||||
<T extends string | number>(props: { msg: T; list: T[] }) => {
|
||||
// use Composition API here like in <script setup>
|
||||
const count = ref(0)
|
||||
|
||||
return () => (
|
||||
// return a render function (both JSX and h() works)
|
||||
<div>
|
||||
{props.msg} {count.value}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// defaults to known types since types are resolved on instantiation
|
||||
const comp: ComponentInstance<typeof Comp> = {} as any
|
||||
expectType<string | number>(comp.msg)
|
||||
expectType<Array<string | number>>(comp.list)
|
||||
})
|
|
@ -9,7 +9,7 @@ import {
|
|||
Component,
|
||||
resolveComponent
|
||||
} from 'vue'
|
||||
import { describe, expectAssignable } from './utils'
|
||||
import { describe, expectAssignable, expectType } from './utils'
|
||||
|
||||
describe('h inference w/ element', () => {
|
||||
// key
|
||||
|
@ -32,6 +32,17 @@ describe('h inference w/ element', () => {
|
|||
// slots
|
||||
const slots = { default: () => {} } // RawSlots
|
||||
h('div', {}, slots)
|
||||
// events
|
||||
h('div', {
|
||||
onClick: e => {
|
||||
expectType<MouseEvent>(e)
|
||||
}
|
||||
})
|
||||
h('input', {
|
||||
onFocus(e) {
|
||||
expectType<FocusEvent>(e)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('h inference w/ Fragment', () => {
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
computed,
|
||||
ShallowRef
|
||||
} from 'vue'
|
||||
import { expectType, describe, IsUnion } from './utils'
|
||||
import { expectType, describe, IsUnion, IsAny } from './utils'
|
||||
|
||||
function plainType(arg: number | Ref<number>) {
|
||||
// ref coercing
|
||||
|
@ -79,6 +79,10 @@ function plainType(arg: number | Ref<number>) {
|
|||
// should still unwrap in objects nested in arrays
|
||||
const arr2 = ref([{ a: ref(1) }]).value
|
||||
expectType<number>(arr2[0].a)
|
||||
|
||||
// any value should return Ref<any>, not any
|
||||
const a = ref(1 as any)
|
||||
expectType<IsAny<typeof a>>(false)
|
||||
}
|
||||
|
||||
plainType(1)
|
||||
|
@ -159,6 +163,17 @@ const state = reactive({
|
|||
|
||||
expectType<string>(state.foo.label)
|
||||
|
||||
describe('ref with generic', <T extends { name: string }>() => {
|
||||
const r = {} as T
|
||||
const s = ref(r)
|
||||
expectType<string>(s.value.name)
|
||||
|
||||
const rr = {} as MaybeRef<T>
|
||||
// should at least allow casting
|
||||
const ss = ref(rr) as Ref<T>
|
||||
expectType<string>(ss.value.name)
|
||||
})
|
||||
|
||||
// shallowRef
|
||||
type Status = 'initial' | 'ready' | 'invalidating'
|
||||
const shallowStatus = shallowRef<Status>('initial')
|
||||
|
@ -191,11 +206,34 @@ if (refStatus.value === 'initial') {
|
|||
expectType<IsUnion<typeof shallowUnionAsCast>>(false)
|
||||
}
|
||||
|
||||
describe('shallowRef with generic', <T>() => {
|
||||
const r = ref({}) as MaybeRef<T>
|
||||
expectType<ShallowRef<T> | Ref<T>>(shallowRef(r))
|
||||
{
|
||||
// any value should return Ref<any>, not any
|
||||
const a = shallowRef(1 as any)
|
||||
expectType<IsAny<typeof a>>(false)
|
||||
}
|
||||
|
||||
describe('shallowRef with generic', <T extends { name: string }>() => {
|
||||
const r = {} as T
|
||||
const s = shallowRef(r)
|
||||
expectType<string>(s.value.name)
|
||||
expectType<ShallowRef<T>>(shallowRef(r))
|
||||
|
||||
const rr = {} as MaybeRef<T>
|
||||
// should at least allow casting
|
||||
const ss = shallowRef(rr) as Ref<T> | ShallowRef<T>
|
||||
expectType<string>(ss.value.name)
|
||||
})
|
||||
|
||||
{
|
||||
// should return ShallowRef<T> | Ref<T>, not ShallowRef<T | Ref<T>>
|
||||
expectType<ShallowRef<{ name: string }> | Ref<{ name: string }>>(
|
||||
shallowRef({} as MaybeRef<{ name: string }>)
|
||||
)
|
||||
expectType<ShallowRef<number> | Ref<string[]> | ShallowRef<string>>(
|
||||
shallowRef('' as Ref<string[]> | string | number)
|
||||
)
|
||||
}
|
||||
|
||||
// proxyRefs: should return `reactive` directly
|
||||
const r1 = reactive({
|
||||
k: 'v'
|
||||
|
|
|
@ -260,6 +260,30 @@ describe('defineSlots', () => {
|
|||
expectType<Slots>(slotsUntype)
|
||||
})
|
||||
|
||||
describe('defineSlots generic', <T extends Record<string, any>>() => {
|
||||
const props = defineProps<{
|
||||
item: T
|
||||
}>()
|
||||
|
||||
const slots = defineSlots<
|
||||
{
|
||||
[K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
|
||||
} & {
|
||||
label?: (props: { item: T }) => any
|
||||
}
|
||||
>()
|
||||
|
||||
for (const key of Object.keys(props.item) as (keyof T & string)[]) {
|
||||
slots[`slot-${String(key)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
}
|
||||
slots.label?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error calling wrong slot
|
||||
slots.foo({})
|
||||
})
|
||||
|
||||
describe('defineModel', () => {
|
||||
// overload 1
|
||||
const modelValueRequired = defineModel<boolean>({ required: true })
|
||||
|
@ -294,10 +318,6 @@ describe('defineModel', () => {
|
|||
defineModel<string>({ default: 123 })
|
||||
// @ts-expect-error unknown props option
|
||||
defineModel({ foo: 123 })
|
||||
|
||||
// accept defineModel-only options
|
||||
defineModel({ local: true })
|
||||
defineModel('foo', { local: true })
|
||||
})
|
||||
|
||||
describe('useModel', () => {
|
||||
|
@ -336,6 +356,78 @@ describe('useSlots', () => {
|
|||
expectType<Slots>(slots)
|
||||
})
|
||||
|
||||
describe('defineSlots generic', <T extends Record<string, any>>() => {
|
||||
const props = defineProps<{
|
||||
item: T
|
||||
}>()
|
||||
|
||||
const slots = defineSlots<
|
||||
{
|
||||
[K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
|
||||
} & {
|
||||
label?: (props: { item: T }) => any
|
||||
}
|
||||
>()
|
||||
|
||||
// @ts-expect-error slots should be readonly
|
||||
slots.label = () => {}
|
||||
|
||||
// @ts-expect-error non existing slot
|
||||
slots['foo-asdas']?.({
|
||||
item: props.item
|
||||
})
|
||||
for (const key in props.item) {
|
||||
slots[`slot-${String(key)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
slots[`slot-${String(key as keyof T)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
}
|
||||
|
||||
for (const key of Object.keys(props.item) as (keyof T)[]) {
|
||||
slots[`slot-${String(key)}`]?.({
|
||||
item: props.item
|
||||
})
|
||||
}
|
||||
slots.label?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error calling wrong slot
|
||||
slots.foo({})
|
||||
})
|
||||
|
||||
describe('defineSlots generic strict', <T extends {
|
||||
foo: 'foo'
|
||||
bar: 'bar'
|
||||
}>() => {
|
||||
const props = defineProps<{
|
||||
item: T
|
||||
}>()
|
||||
|
||||
const slots = defineSlots<
|
||||
{
|
||||
[K in keyof T as `slot-${K & string}`]?: (props: { item: T }) => any
|
||||
} & {
|
||||
label?: (props: { item: T }) => any
|
||||
}
|
||||
>()
|
||||
|
||||
// slot-bar/foo should be automatically inferred
|
||||
slots['slot-bar']?.({ item: props.item })
|
||||
slots['slot-foo']?.({ item: props.item })
|
||||
|
||||
slots.label?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error not part of the extends
|
||||
slots['slot-RANDOM']?.({ item: props.item })
|
||||
|
||||
// @ts-expect-error slots should be readonly
|
||||
slots.label = () => {}
|
||||
|
||||
// @ts-expect-error calling wrong slot
|
||||
slots.foo({})
|
||||
})
|
||||
|
||||
// #6420
|
||||
describe('toRefs w/ type declaration', () => {
|
||||
const props = defineProps<{
|
||||
|
|
|
@ -112,3 +112,11 @@ expectType<JSX.Element>(
|
|||
)
|
||||
// @ts-expect-error
|
||||
;<Suspense onResolve={123} />
|
||||
|
||||
// svg
|
||||
expectType<JSX.Element>(
|
||||
<svg
|
||||
xmlnsXlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
/>
|
||||
)
|
||||
|
|
|
@ -17,6 +17,7 @@ declare var __COMPAT__: boolean
|
|||
declare var __FEATURE_OPTIONS_API__: boolean
|
||||
declare var __FEATURE_PROD_DEVTOOLS__: boolean
|
||||
declare var __FEATURE_SUSPENSE__: boolean
|
||||
declare var __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__: boolean
|
||||
|
||||
// for tests
|
||||
declare namespace jest {
|
||||
|
|
|
@ -0,0 +1,126 @@
|
|||
import { describe, bench } from 'vitest'
|
||||
import { ComputedRef, Ref, computed, ref } from '../src/index'
|
||||
|
||||
describe('computed', () => {
|
||||
bench('create computed', () => {
|
||||
computed(() => 100)
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const o = ref(100)
|
||||
bench('write independent ref dep', () => {
|
||||
o.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
computed(() => v.value * 2)
|
||||
let i = 0
|
||||
bench("write ref, don't read computed (never invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
let i = 0
|
||||
bench("write ref, don't read computed (never invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
c.value
|
||||
let i = 0
|
||||
bench("write ref, don't read computed (invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
let i = 0
|
||||
bench('write ref, read computed', () => {
|
||||
v.value = i++
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write ref, don't read 1000 computeds (never invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write ref, don't read 1000 computeds (invoked)", () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
const computeds: ComputedRef<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return v.value * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench('write ref, read 1000 computeds', () => {
|
||||
v.value = i++
|
||||
computeds.forEach(c => c.value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const refs: Ref<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
refs.push(ref(i))
|
||||
}
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
refs.forEach(ref => (total += ref.value))
|
||||
return total
|
||||
})
|
||||
let i = 0
|
||||
const n = refs.length
|
||||
bench('1000 refs, 1 computed', () => {
|
||||
refs[i++ % n].value++
|
||||
c.value
|
||||
})
|
||||
}
|
||||
})
|
|
@ -158,6 +158,21 @@ describe('reactivity/reactive', () => {
|
|||
expect(original.bar).toBe(original2)
|
||||
})
|
||||
|
||||
// #1246
|
||||
test('mutation on objects using reactive as prototype should not trigger', () => {
|
||||
const observed = reactive({ foo: 1 })
|
||||
const original = Object.create(observed)
|
||||
let dummy
|
||||
effect(() => (dummy = original.foo))
|
||||
expect(dummy).toBe(1)
|
||||
observed.foo = 2
|
||||
expect(dummy).toBe(2)
|
||||
original.foo = 3
|
||||
expect(dummy).toBe(2)
|
||||
original.foo = 4
|
||||
expect(dummy).toBe(2)
|
||||
})
|
||||
|
||||
test('toRaw', () => {
|
||||
const original = { foo: 1 }
|
||||
const observed = reactive(original)
|
||||
|
@ -166,11 +181,18 @@ describe('reactivity/reactive', () => {
|
|||
})
|
||||
|
||||
test('toRaw on object using reactive as prototype', () => {
|
||||
const original = reactive({})
|
||||
const obj = Object.create(original)
|
||||
const original = { foo: 1 }
|
||||
const observed = reactive(original)
|
||||
const inherted = Object.create(observed)
|
||||
expect(toRaw(inherted)).toBe(inherted)
|
||||
})
|
||||
|
||||
test('toRaw on user Proxy wrapping reactive', () => {
|
||||
const original = {}
|
||||
const re = reactive(original)
|
||||
const obj = new Proxy(re, {})
|
||||
const raw = toRaw(obj)
|
||||
expect(raw).toBe(obj)
|
||||
expect(raw).not.toBe(toRaw(original))
|
||||
expect(raw).toBe(original)
|
||||
})
|
||||
|
||||
test('should not unwrap Ref<T>', () => {
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
import { bench } from 'vitest'
|
||||
import { computed, reactive, readonly, shallowRef, triggerRef } from '../src'
|
||||
|
||||
for (let amount = 1e1; amount < 1e4; amount *= 10) {
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = reactive(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *reactive* array, ${amount} elements`, () => {
|
||||
for (let i = 0, n = r.length; i < n; i++) {
|
||||
r[i]++
|
||||
}
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = reactive(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(
|
||||
`reduce *reactive* array, ${amount} elements, only change first value`,
|
||||
() => {
|
||||
r[0]++
|
||||
c.value
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = reactive({ arr: readonly(rawArray) })
|
||||
const c = computed(() => {
|
||||
return r.arr.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *readonly* array, ${amount} elements`, () => {
|
||||
r.arr = r.arr.map(v => v + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = shallowRef(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.value.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *raw* array, copied, ${amount} elements`, () => {
|
||||
r.value = r.value.map(v => v + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const rawArray: number[] = []
|
||||
for (let i = 0, n = amount; i < n; i++) {
|
||||
rawArray.push(i)
|
||||
}
|
||||
const r = shallowRef(rawArray)
|
||||
const c = computed(() => {
|
||||
return r.value.reduce((v, a) => a + v, 0)
|
||||
})
|
||||
|
||||
bench(`reduce *raw* array, manually triggered, ${amount} elements`, () => {
|
||||
for (let i = 0, n = rawArray.length; i < n; i++) {
|
||||
rawArray[i]++
|
||||
}
|
||||
triggerRef(r)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
}
|
|
@ -175,6 +175,15 @@ describe('reactivity/reactive/Array', () => {
|
|||
expect(length).toBe('01')
|
||||
})
|
||||
|
||||
// #9742
|
||||
test('mutation on user proxy of reactive Array', () => {
|
||||
const array = reactive<number[]>([])
|
||||
const proxy = new Proxy(array, {})
|
||||
proxy.push(1)
|
||||
expect(array).toHaveLength(1)
|
||||
expect(proxy).toHaveLength(1)
|
||||
})
|
||||
|
||||
describe('Array methods w/ refs', () => {
|
||||
let original: any[]
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -0,0 +1,143 @@
|
|||
import { bench } from 'vitest'
|
||||
import { reactive, computed, ComputedRef } from '../src'
|
||||
|
||||
function createMap(obj: Record<string, any>) {
|
||||
const map = new Map()
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
map.set(key, obj[key])
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
|
||||
bench('create reactive map', () => {
|
||||
reactive(createMap({ a: 1 }))
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
bench('write reactive map property', () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
let i = 0
|
||||
bench("write reactive map, don't read computed (never invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
c.value
|
||||
let i = 0
|
||||
bench("write reactive map, don't read computed (invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
let i = 0
|
||||
bench('write reactive map, read computed', () => {
|
||||
r.set('a', i++)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const _m = new Map()
|
||||
for (let i = 0; i < 10000; i++) {
|
||||
_m.set(i, i)
|
||||
}
|
||||
const r = reactive(_m)
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
r.forEach((value, key) => {
|
||||
total += value
|
||||
})
|
||||
return total
|
||||
})
|
||||
bench("write reactive map (10'000 items), read computed", () => {
|
||||
r.set(5000, r.get(5000) + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive map, don't read 1000 computeds (never invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive map, don't read 1000 computeds (invoked)", () => {
|
||||
r.set('a', i++)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive(createMap({ a: 1 }))
|
||||
const computeds: ComputedRef<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.get('a') * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench('write reactive map, read 1000 computeds', () => {
|
||||
r.set('a', i++)
|
||||
computeds.forEach(c => c.value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const reactives: Map<any, any>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
reactives.push(reactive(createMap({ a: i })))
|
||||
}
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
reactives.forEach(r => (total += r.get('a')))
|
||||
return total
|
||||
})
|
||||
let i = 0
|
||||
const n = reactives.length
|
||||
bench('1000 reactive maps, 1 computed', () => {
|
||||
reactives[i++ % n].set('a', reactives[i++ % n].get('a') + 1)
|
||||
c.value
|
||||
})
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
import { bench } from 'vitest'
|
||||
import { ComputedRef, computed, reactive } from '../src'
|
||||
|
||||
bench('create reactive obj', () => {
|
||||
reactive({ a: 1 })
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const r = reactive({ a: 1 })
|
||||
bench('write reactive obj property', () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read computed (never invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
c.value
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read computed (invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
let i = 0
|
||||
bench('write reactive obj, read computed', () => {
|
||||
r.a = i++
|
||||
c.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read 1000 computeds (never invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const computeds = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
c.value
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench("write reactive obj, don't read 1000 computeds (invoked)", () => {
|
||||
r.a = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const r = reactive({ a: 1 })
|
||||
const computeds: ComputedRef<number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
const c = computed(() => {
|
||||
return r.a * 2
|
||||
})
|
||||
computeds.push(c)
|
||||
}
|
||||
let i = 0
|
||||
bench('write reactive obj, read 1000 computeds', () => {
|
||||
r.a = i++
|
||||
computeds.forEach(c => c.value)
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const reactives: Record<string, number>[] = []
|
||||
for (let i = 0, n = 1000; i < n; i++) {
|
||||
reactives.push(reactive({ a: i }))
|
||||
}
|
||||
const c = computed(() => {
|
||||
let total = 0
|
||||
reactives.forEach(r => (total += r.a))
|
||||
return total
|
||||
})
|
||||
let i = 0
|
||||
const n = reactives.length
|
||||
bench('1000 reactive objs, 1 computed', () => {
|
||||
reactives[i++ % n].a++
|
||||
c.value
|
||||
})
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
import { describe, bench } from 'vitest'
|
||||
import { ref } from '../src/index'
|
||||
|
||||
describe('ref', () => {
|
||||
bench('create ref', () => {
|
||||
ref(100)
|
||||
})
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const v = ref(100)
|
||||
bench('write ref', () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
bench('read ref', () => {
|
||||
v.value
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
let i = 0
|
||||
const v = ref(100)
|
||||
bench('write/read ref', () => {
|
||||
v.value = i++
|
||||
|
||||
v.value
|
||||
})
|
||||
}
|
||||
})
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/reactivity",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/reactivity",
|
||||
"main": "index.js",
|
||||
"module": "dist/reactivity.esm-bundler.js",
|
||||
|
|
|
@ -101,8 +101,8 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
return isReadonly
|
||||
} else if (key === ReactiveFlags.IS_SHALLOW) {
|
||||
return shallow
|
||||
} else if (
|
||||
key === ReactiveFlags.RAW &&
|
||||
} else if (key === ReactiveFlags.RAW) {
|
||||
if (
|
||||
receiver ===
|
||||
(isReadonly
|
||||
? shallow
|
||||
|
@ -111,10 +111,16 @@ class BaseReactiveHandler implements ProxyHandler<Target> {
|
|||
: shallow
|
||||
? shallowReactiveMap
|
||||
: reactiveMap
|
||||
).get(target)
|
||||
).get(target) ||
|
||||
// receiver is not the reactive proxy, but has the same prototype
|
||||
// this means the reciever is a user proxy of the reactive proxy
|
||||
Object.getPrototypeOf(target) === Object.getPrototypeOf(receiver)
|
||||
) {
|
||||
return target
|
||||
}
|
||||
// early return undefined
|
||||
return
|
||||
}
|
||||
|
||||
const targetIsArray = isArray(target)
|
||||
|
||||
|
@ -169,18 +175,20 @@ class MutableReactiveHandler extends BaseReactiveHandler {
|
|||
receiver: object
|
||||
): boolean {
|
||||
let oldValue = (target as any)[key]
|
||||
if (isReadonly(oldValue) && isRef(oldValue) && !isRef(value)) {
|
||||
return false
|
||||
}
|
||||
if (!this._shallow) {
|
||||
const isOldValueReadonly = isReadonly(oldValue)
|
||||
if (!isShallow(value) && !isReadonly(value)) {
|
||||
oldValue = toRaw(oldValue)
|
||||
value = toRaw(value)
|
||||
}
|
||||
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
|
||||
if (isOldValueReadonly) {
|
||||
return false
|
||||
} else {
|
||||
oldValue.value = value
|
||||
return true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// in shallow mode, objects are set as-is regardless of reactive or not
|
||||
}
|
||||
|
|
|
@ -100,7 +100,6 @@ export function isRef(r: any): r is Ref {
|
|||
* @param value - The object to wrap in the ref.
|
||||
* @see {@link https://vuejs.org/api/reactivity-core.html#ref}
|
||||
*/
|
||||
export function ref<T extends Ref>(value: T): T
|
||||
export function ref<T>(value: T): Ref<UnwrapRef<T>>
|
||||
export function ref<T = any>(): Ref<T | undefined>
|
||||
export function ref(value?: unknown) {
|
||||
|
@ -128,9 +127,13 @@ export type ShallowRef<T = any> = Ref<T> & { [ShallowRefMarker]?: true }
|
|||
* @param value - The "inner value" for the shallow ref.
|
||||
* @see {@link https://vuejs.org/api/reactivity-advanced.html#shallowref}
|
||||
*/
|
||||
export function shallowRef<T>(value: MaybeRef<T>): Ref<T> | ShallowRef<T>
|
||||
export function shallowRef<T extends Ref>(value: T): T
|
||||
export function shallowRef<T>(value: T): ShallowRef<T>
|
||||
export function shallowRef<T>(
|
||||
value: T
|
||||
): Ref extends T
|
||||
? T extends Ref
|
||||
? IfAny<T, ShallowRef<T>, T>
|
||||
: ShallowRef<T>
|
||||
: ShallowRef<T>
|
||||
export function shallowRef<T = any>(): ShallowRef<T | undefined>
|
||||
export function shallowRef(value?: unknown) {
|
||||
return createRef(value, true)
|
||||
|
|
|
@ -14,7 +14,15 @@ import {
|
|||
ComputedRef,
|
||||
shallowReactive,
|
||||
nextTick,
|
||||
ref
|
||||
ref,
|
||||
Ref,
|
||||
watch,
|
||||
openBlock,
|
||||
createVNode,
|
||||
createElementVNode,
|
||||
createBlock,
|
||||
createElementBlock,
|
||||
Fragment
|
||||
} from '@vue/runtime-test'
|
||||
import {
|
||||
defineEmits,
|
||||
|
@ -184,13 +192,17 @@ describe('SFC <script setup> helpers', () => {
|
|||
foo.value = 'bar'
|
||||
}
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
foo = useModel(props, 'modelValue')
|
||||
},
|
||||
render() {}
|
||||
return () => {
|
||||
compRender()
|
||||
return foo.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const msg = ref('')
|
||||
|
@ -206,6 +218,8 @@ describe('SFC <script setup> helpers', () => {
|
|||
expect(foo.value).toBe('')
|
||||
expect(msg.value).toBe('')
|
||||
expect(setValue).not.toBeCalled()
|
||||
expect(compRender).toBeCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe('')
|
||||
|
||||
// update from child
|
||||
update()
|
||||
|
@ -214,42 +228,55 @@ describe('SFC <script setup> helpers', () => {
|
|||
expect(msg.value).toBe('bar')
|
||||
expect(foo.value).toBe('bar')
|
||||
expect(setValue).toBeCalledTimes(1)
|
||||
expect(compRender).toBeCalledTimes(2)
|
||||
expect(serializeInner(root)).toBe('bar')
|
||||
|
||||
// update from parent
|
||||
msg.value = 'qux'
|
||||
expect(msg.value).toBe('qux')
|
||||
|
||||
await nextTick()
|
||||
expect(msg.value).toBe('qux')
|
||||
expect(foo.value).toBe('qux')
|
||||
expect(setValue).toBeCalledTimes(1)
|
||||
expect(compRender).toBeCalledTimes(3)
|
||||
expect(serializeInner(root)).toBe('qux')
|
||||
})
|
||||
|
||||
test('local', async () => {
|
||||
test('without parent value (local mutation)', async () => {
|
||||
let foo: any
|
||||
const update = () => {
|
||||
foo.value = 'bar'
|
||||
}
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['foo'],
|
||||
emits: ['update:foo'],
|
||||
setup(props) {
|
||||
foo = useModel(props, 'foo', { local: true })
|
||||
},
|
||||
render() {}
|
||||
foo = useModel(props, 'foo')
|
||||
return () => {
|
||||
compRender()
|
||||
return foo.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
const updateFoo = vi.fn()
|
||||
render(h(Comp, { 'onUpdate:foo': updateFoo }), root)
|
||||
expect(compRender).toBeCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe('<!---->')
|
||||
|
||||
expect(foo.value).toBeUndefined()
|
||||
update()
|
||||
|
||||
// when parent didn't provide value, local mutation is enabled
|
||||
expect(foo.value).toBe('bar')
|
||||
|
||||
await nextTick()
|
||||
expect(updateFoo).toBeCalledTimes(1)
|
||||
expect(compRender).toBeCalledTimes(2)
|
||||
expect(serializeInner(root)).toBe('bar')
|
||||
})
|
||||
|
||||
test('default value', async () => {
|
||||
|
@ -257,25 +284,234 @@ describe('SFC <script setup> helpers', () => {
|
|||
const inc = () => {
|
||||
count.value++
|
||||
}
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: { count: { default: 0 } },
|
||||
emits: ['update:count'],
|
||||
setup(props) {
|
||||
count = useModel(props, 'count', { local: true })
|
||||
},
|
||||
render() {}
|
||||
count = useModel(props, 'count')
|
||||
return () => {
|
||||
compRender()
|
||||
return count.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
const updateCount = vi.fn()
|
||||
render(h(Comp, { 'onUpdate:count': updateCount }), root)
|
||||
expect(compRender).toBeCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe('0')
|
||||
|
||||
expect(count.value).toBe(0)
|
||||
|
||||
inc()
|
||||
// when parent didn't provide value, local mutation is enabled
|
||||
expect(count.value).toBe(1)
|
||||
|
||||
await nextTick()
|
||||
|
||||
expect(updateCount).toBeCalledTimes(1)
|
||||
expect(compRender).toBeCalledTimes(2)
|
||||
expect(serializeInner(root)).toBe('1')
|
||||
})
|
||||
|
||||
test('parent limiting child value', async () => {
|
||||
let childCount: Ref<number>
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['count'],
|
||||
emits: ['update:count'],
|
||||
setup(props) {
|
||||
childCount = useModel(props, 'count')
|
||||
return () => {
|
||||
compRender()
|
||||
return childCount.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
const count = ref(0)
|
||||
watch(count, () => {
|
||||
if (count.value < 0) {
|
||||
count.value = 0
|
||||
}
|
||||
})
|
||||
return () =>
|
||||
h(Comp, {
|
||||
count: count.value,
|
||||
'onUpdate:count': val => {
|
||||
count.value = val
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
expect(serializeInner(root)).toBe('0')
|
||||
|
||||
// child update
|
||||
childCount!.value = 1
|
||||
// not yet updated
|
||||
expect(childCount!.value).toBe(0)
|
||||
|
||||
await nextTick()
|
||||
expect(childCount!.value).toBe(1)
|
||||
expect(serializeInner(root)).toBe('1')
|
||||
|
||||
// child update to invalid value
|
||||
childCount!.value = -1
|
||||
// not yet updated
|
||||
expect(childCount!.value).toBe(1)
|
||||
|
||||
await nextTick()
|
||||
// limited to 0 by parent
|
||||
expect(childCount!.value).toBe(0)
|
||||
expect(serializeInner(root)).toBe('0')
|
||||
})
|
||||
|
||||
test('has parent value -> no parent value', async () => {
|
||||
let childCount: Ref<number>
|
||||
|
||||
const compRender = vi.fn()
|
||||
const Comp = defineComponent({
|
||||
props: ['count'],
|
||||
emits: ['update:count'],
|
||||
setup(props) {
|
||||
childCount = useModel(props, 'count')
|
||||
return () => {
|
||||
compRender()
|
||||
return childCount.value
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const toggle = ref(true)
|
||||
const Parent = defineComponent({
|
||||
setup() {
|
||||
const count = ref(0)
|
||||
return () =>
|
||||
toggle.value
|
||||
? h(Comp, {
|
||||
count: count.value,
|
||||
'onUpdate:count': val => {
|
||||
count.value = val
|
||||
}
|
||||
})
|
||||
: h(Comp)
|
||||
}
|
||||
})
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Parent), root)
|
||||
expect(serializeInner(root)).toBe('0')
|
||||
|
||||
// child update
|
||||
childCount!.value = 1
|
||||
// not yet updated
|
||||
expect(childCount!.value).toBe(0)
|
||||
|
||||
await nextTick()
|
||||
expect(childCount!.value).toBe(1)
|
||||
expect(serializeInner(root)).toBe('1')
|
||||
|
||||
// parent change
|
||||
toggle.value = false
|
||||
|
||||
await nextTick()
|
||||
// localValue should be reset
|
||||
expect(childCount!.value).toBeUndefined()
|
||||
expect(serializeInner(root)).toBe('<!---->')
|
||||
|
||||
// child local mutation should continue to work
|
||||
childCount!.value = 2
|
||||
expect(childCount!.value).toBe(2)
|
||||
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe('2')
|
||||
})
|
||||
|
||||
// #9838
|
||||
test('pass modelValue to slot (optimized mode) ', async () => {
|
||||
let foo: any
|
||||
const update = () => {
|
||||
foo.value = 'bar'
|
||||
}
|
||||
|
||||
const Comp = {
|
||||
render(this: any) {
|
||||
return this.$slots.default()
|
||||
}
|
||||
}
|
||||
|
||||
const childRender = vi.fn()
|
||||
const slotRender = vi.fn()
|
||||
const Child = defineComponent({
|
||||
props: ['modelValue'],
|
||||
emits: ['update:modelValue'],
|
||||
setup(props) {
|
||||
foo = useModel(props, 'modelValue')
|
||||
return () => {
|
||||
childRender()
|
||||
return (
|
||||
openBlock(),
|
||||
createElementBlock(Fragment, null, [
|
||||
createVNode(Comp, null, {
|
||||
default: () => {
|
||||
slotRender()
|
||||
return createElementVNode('div', null, foo.value)
|
||||
},
|
||||
_: 1 /* STABLE */
|
||||
})
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const msg = ref('')
|
||||
const setValue = vi.fn(v => (msg.value = v))
|
||||
const root = nodeOps.createElement('div')
|
||||
createApp({
|
||||
render() {
|
||||
return (
|
||||
openBlock(),
|
||||
createBlock(
|
||||
Child,
|
||||
{
|
||||
modelValue: msg.value,
|
||||
'onUpdate:modelValue': setValue
|
||||
},
|
||||
null,
|
||||
8 /* PROPS */,
|
||||
['modelValue']
|
||||
)
|
||||
)
|
||||
}
|
||||
}).mount(root)
|
||||
|
||||
expect(foo.value).toBe('')
|
||||
expect(msg.value).toBe('')
|
||||
expect(setValue).not.toBeCalled()
|
||||
expect(childRender).toBeCalledTimes(1)
|
||||
expect(slotRender).toBeCalledTimes(1)
|
||||
expect(serializeInner(root)).toBe('<div></div>')
|
||||
|
||||
// update from child
|
||||
update()
|
||||
|
||||
await nextTick()
|
||||
expect(msg.value).toBe('bar')
|
||||
expect(foo.value).toBe('bar')
|
||||
expect(setValue).toBeCalledTimes(1)
|
||||
expect(childRender).toBeCalledTimes(2)
|
||||
expect(slotRender).toBeCalledTimes(2)
|
||||
expect(serializeInner(root)).toBe('<div>bar</div>')
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
import { nextTick, ref, watch, watchEffect } from '../src'
|
||||
import { bench } from 'vitest'
|
||||
|
||||
bench('create watcher', () => {
|
||||
const v = ref(100)
|
||||
watch(v, v => {})
|
||||
})
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watch(v, v => {})
|
||||
let i = 0
|
||||
bench('update ref to trigger watcher (scheduled but not executed)', () => {
|
||||
v.value = i++
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watch(v, v => {})
|
||||
let i = 0
|
||||
bench('update ref to trigger watcher (executed)', async () => {
|
||||
v.value = i++
|
||||
return nextTick()
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
bench('create watchEffect', () => {
|
||||
watchEffect(() => {})
|
||||
})
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watchEffect(() => {
|
||||
v.value
|
||||
})
|
||||
let i = 0
|
||||
bench(
|
||||
'update ref to trigger watchEffect (scheduled but not executed)',
|
||||
() => {
|
||||
v.value = i++
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
{
|
||||
const v = ref(100)
|
||||
watchEffect(() => {
|
||||
v.value
|
||||
})
|
||||
let i = 0
|
||||
bench('update ref to trigger watchEffect (executed)', async () => {
|
||||
v.value = i++
|
||||
await nextTick()
|
||||
})
|
||||
}
|
|
@ -549,6 +549,98 @@ describe('api: watch', () => {
|
|||
expect(cb).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #7030
|
||||
it('should not fire on child component unmount w/ flush: pre', async () => {
|
||||
const visible = ref(true)
|
||||
const cb = vi.fn()
|
||||
const Parent = defineComponent({
|
||||
props: ['visible'],
|
||||
render() {
|
||||
return visible.value ? h(Comp) : null
|
||||
}
|
||||
})
|
||||
const Comp = {
|
||||
setup() {
|
||||
watch(visible, cb, { flush: 'pre' })
|
||||
},
|
||||
render() {}
|
||||
}
|
||||
const App = {
|
||||
render() {
|
||||
return h(Parent, {
|
||||
visible: visible.value
|
||||
})
|
||||
}
|
||||
}
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
expect(cb).not.toHaveBeenCalled()
|
||||
visible.value = false
|
||||
await nextTick()
|
||||
expect(cb).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// #7030
|
||||
it('flush: pre watcher in child component should not fire before parent update', async () => {
|
||||
const b = ref(0)
|
||||
const calls: string[] = []
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
watch(
|
||||
() => b.value,
|
||||
val => {
|
||||
calls.push('watcher child')
|
||||
},
|
||||
{ flush: 'pre' }
|
||||
)
|
||||
return () => {
|
||||
b.value
|
||||
calls.push('render child')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const Parent = {
|
||||
props: ['a'],
|
||||
setup() {
|
||||
watch(
|
||||
() => b.value,
|
||||
val => {
|
||||
calls.push('watcher parent')
|
||||
},
|
||||
{ flush: 'pre' }
|
||||
)
|
||||
return () => {
|
||||
b.value
|
||||
calls.push('render parent')
|
||||
return h(Comp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const App = {
|
||||
render() {
|
||||
return h(Parent, {
|
||||
a: b.value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render(h(App), nodeOps.createElement('div'))
|
||||
expect(calls).toEqual(['render parent', 'render child'])
|
||||
|
||||
b.value++
|
||||
await nextTick()
|
||||
expect(calls).toEqual([
|
||||
'render parent',
|
||||
'render child',
|
||||
'watcher parent',
|
||||
'render parent',
|
||||
'watcher child',
|
||||
'render child'
|
||||
])
|
||||
})
|
||||
|
||||
// #1763
|
||||
it('flush: pre watcher watching props should fire before child update', async () => {
|
||||
const a = ref(0)
|
||||
|
|
|
@ -19,7 +19,8 @@ import {
|
|||
shallowRef,
|
||||
SuspenseProps,
|
||||
resolveDynamicComponent,
|
||||
Fragment
|
||||
Fragment,
|
||||
KeepAlive
|
||||
} from '@vue/runtime-test'
|
||||
import { createApp, defineComponent } from 'vue'
|
||||
import { type RawSlots } from 'packages/runtime-core/src/componentSlots'
|
||||
|
@ -537,6 +538,51 @@ describe('Suspense', () => {
|
|||
expect(unmounted).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
// vuetifyjs/vuetify#15207
|
||||
test('update prop of async element before suspense resolve', async () => {
|
||||
let resolve: () => void
|
||||
const mounted = new Promise<void>(r => {
|
||||
resolve = r
|
||||
})
|
||||
const Async = {
|
||||
async setup() {
|
||||
onMounted(() => {
|
||||
resolve()
|
||||
})
|
||||
const p = new Promise(r => setTimeout(r, 1))
|
||||
await p
|
||||
return () => h('div', 'async')
|
||||
}
|
||||
}
|
||||
|
||||
const Comp: ComponentOptions<{ data: string }> = {
|
||||
props: ['data'],
|
||||
setup(props) {
|
||||
return () => h(Async, { 'data-test': props.data })
|
||||
}
|
||||
}
|
||||
|
||||
const Root = {
|
||||
setup() {
|
||||
const data = ref('1')
|
||||
onMounted(() => {
|
||||
data.value = '2'
|
||||
})
|
||||
return () =>
|
||||
h(Suspense, null, {
|
||||
default: h(Comp, { data: data.value }),
|
||||
fallback: h('div', 'fallback')
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Root), root)
|
||||
expect(serializeInner(root)).toBe(`<div>fallback</div>`)
|
||||
await mounted
|
||||
expect(serializeInner(root)).toBe(`<div data-test="2">async</div>`)
|
||||
})
|
||||
|
||||
test('nested suspense (parent resolves first)', async () => {
|
||||
const calls: string[] = []
|
||||
|
||||
|
@ -1185,6 +1231,72 @@ describe('Suspense', () => {
|
|||
expect(calls).toEqual([`one mounted`, `one unmounted`, `two mounted`])
|
||||
})
|
||||
|
||||
test('mount the fallback content is in the correct position', async () => {
|
||||
const makeComp = (name: string, delay = 0) =>
|
||||
defineAsyncComponent(
|
||||
{
|
||||
setup() {
|
||||
return () => h('div', [name])
|
||||
}
|
||||
},
|
||||
delay
|
||||
)
|
||||
|
||||
const One = makeComp('one')
|
||||
const Two = makeComp('two', 20)
|
||||
|
||||
const view = shallowRef(One)
|
||||
|
||||
const Comp = {
|
||||
setup() {
|
||||
return () =>
|
||||
h('div', [
|
||||
h(
|
||||
Suspense,
|
||||
{
|
||||
timeout: 10
|
||||
},
|
||||
{
|
||||
default: h(view.value),
|
||||
fallback: h('div', 'fallback')
|
||||
}
|
||||
),
|
||||
h('div', 'three')
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
render(h(Comp), root)
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>fallback</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
await deps[0]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>one</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
view.value = Two
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>one</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
await new Promise(r => setTimeout(r, 10))
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>fallback</div><div>three</div></div>`
|
||||
)
|
||||
|
||||
await deps[1]
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(
|
||||
`<div><div>two</div><div>three</div></div>`
|
||||
)
|
||||
})
|
||||
|
||||
// #2214
|
||||
// Since suspense renders its own root like a component, it should not patch
|
||||
// its content in optimized mode.
|
||||
|
@ -1527,6 +1639,97 @@ describe('Suspense', () => {
|
|||
expect(serializeInner(root)).toBe(expected)
|
||||
})
|
||||
|
||||
// #6416
|
||||
test('KeepAlive with Suspense', async () => {
|
||||
const Async = defineAsyncComponent({
|
||||
render() {
|
||||
return h('div', 'async')
|
||||
}
|
||||
})
|
||||
const Sync = {
|
||||
render() {
|
||||
return h('div', 'sync')
|
||||
}
|
||||
}
|
||||
const components = [Async, Sync]
|
||||
const viewRef = ref(0)
|
||||
const root = nodeOps.createElement('div')
|
||||
const App = {
|
||||
render() {
|
||||
return h(KeepAlive, null, {
|
||||
default: () => {
|
||||
return h(Suspense, null, {
|
||||
default: h(components[viewRef.value]),
|
||||
fallback: h('div', 'Loading-dynamic-components')
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe(`<div>Loading-dynamic-components</div>`)
|
||||
|
||||
viewRef.value = 1
|
||||
await nextTick()
|
||||
expect(serializeInner(root)).toBe(`<div>sync</div>`)
|
||||
|
||||
viewRef.value = 0
|
||||
await nextTick()
|
||||
|
||||
expect(serializeInner(root)).toBe('<!---->')
|
||||
|
||||
await Promise.all(deps)
|
||||
await nextTick()
|
||||
// when async resolve,it should be <div>async</div>
|
||||
expect(serializeInner(root)).toBe('<div>async</div>')
|
||||
|
||||
viewRef.value = 1
|
||||
await nextTick()
|
||||
// TypeError: Cannot read properties of null (reading 'parentNode')
|
||||
// This has been fixed
|
||||
expect(serializeInner(root)).toBe(`<div>sync</div>`)
|
||||
})
|
||||
|
||||
// #6416 follow up
|
||||
test('Suspense patched during HOC async component re-mount', async () => {
|
||||
const key = ref('k')
|
||||
const data = ref('data')
|
||||
|
||||
const Async = defineAsyncComponent({
|
||||
render() {
|
||||
return h('div', 'async')
|
||||
}
|
||||
})
|
||||
|
||||
const Comp = {
|
||||
render() {
|
||||
return h(Async, { key: key.value })
|
||||
}
|
||||
}
|
||||
|
||||
const root = nodeOps.createElement('div')
|
||||
const App = {
|
||||
render() {
|
||||
return h(Suspense, null, {
|
||||
default: h(Comp, { data: data.value })
|
||||
})
|
||||
}
|
||||
}
|
||||
render(h(App), root)
|
||||
expect(serializeInner(root)).toBe(`<!---->`)
|
||||
|
||||
await Promise.all(deps)
|
||||
|
||||
// async mounted, but key change causing a new async comp to be loaded
|
||||
key.value = 'k1'
|
||||
await nextTick()
|
||||
|
||||
// patch the Suspense
|
||||
// should not throw error due to Suspense vnode.el being null
|
||||
data.value = 'data2'
|
||||
await Promise.all(deps)
|
||||
})
|
||||
|
||||
describe('warnings', () => {
|
||||
// base function to check if a combination of slots warns or not
|
||||
function baseCheckWarn(
|
||||
|
|
|
@ -812,17 +812,17 @@ describe('SSR hydration', () => {
|
|||
})
|
||||
)
|
||||
|
||||
const bol = ref(true)
|
||||
const toggle = ref(true)
|
||||
const App = {
|
||||
setup() {
|
||||
onMounted(() => {
|
||||
// change state, this makes updateComponent(AsyncComp) execute before
|
||||
// the async component is resolved
|
||||
bol.value = false
|
||||
toggle.value = false
|
||||
})
|
||||
|
||||
return () => {
|
||||
return [bol.value ? 'hello' : 'world', h(AsyncComp)]
|
||||
return [toggle.value ? 'hello' : 'world', h(AsyncComp)]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -859,6 +859,147 @@ describe('SSR hydration', () => {
|
|||
)
|
||||
})
|
||||
|
||||
test('hydrate safely when property used by async setup changed before render', async () => {
|
||||
const toggle = ref(true)
|
||||
|
||||
const AsyncComp = {
|
||||
async setup() {
|
||||
await new Promise<void>(r => setTimeout(r, 10))
|
||||
return () => h('h1', 'Async component')
|
||||
}
|
||||
}
|
||||
|
||||
const AsyncWrapper = {
|
||||
render() {
|
||||
return h(AsyncComp)
|
||||
}
|
||||
}
|
||||
|
||||
const SiblingComp = {
|
||||
setup() {
|
||||
toggle.value = false
|
||||
return () => h('span')
|
||||
}
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
Suspense,
|
||||
{},
|
||||
{
|
||||
default: () => [
|
||||
h('main', {}, [
|
||||
h(AsyncWrapper, {
|
||||
prop: toggle.value ? 'hello' : 'world'
|
||||
}),
|
||||
h(SiblingComp)
|
||||
])
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// server render
|
||||
const html = await renderToString(h(App))
|
||||
|
||||
expect(html).toMatchInlineSnapshot(
|
||||
`"<main><h1 prop="hello">Async component</h1><span></span></main>"`
|
||||
)
|
||||
|
||||
expect(toggle.value).toBe(false)
|
||||
|
||||
// hydration
|
||||
|
||||
// reset the value
|
||||
toggle.value = true
|
||||
expect(toggle.value).toBe(true)
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = html
|
||||
createSSRApp(App).mount(container)
|
||||
|
||||
await new Promise(r => setTimeout(r, 10))
|
||||
|
||||
expect(toggle.value).toBe(false)
|
||||
|
||||
// should be hydrated now
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<main><h1 prop="world">Async component</h1><span></span></main>"`
|
||||
)
|
||||
})
|
||||
|
||||
test('hydrate safely when property used by deep nested async setup changed before render', async () => {
|
||||
const toggle = ref(true)
|
||||
|
||||
const AsyncComp = {
|
||||
async setup() {
|
||||
await new Promise<void>(r => setTimeout(r, 10))
|
||||
return () => h('h1', 'Async component')
|
||||
}
|
||||
}
|
||||
|
||||
const AsyncWrapper = { render: () => h(AsyncComp) }
|
||||
const AsyncWrapperWrapper = { render: () => h(AsyncWrapper) }
|
||||
|
||||
const SiblingComp = {
|
||||
setup() {
|
||||
toggle.value = false
|
||||
return () => h('span')
|
||||
}
|
||||
}
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
return () =>
|
||||
h(
|
||||
Suspense,
|
||||
{},
|
||||
{
|
||||
default: () => [
|
||||
h('main', {}, [
|
||||
h(AsyncWrapperWrapper, {
|
||||
prop: toggle.value ? 'hello' : 'world'
|
||||
}),
|
||||
h(SiblingComp)
|
||||
])
|
||||
]
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// server render
|
||||
const html = await renderToString(h(App))
|
||||
|
||||
expect(html).toMatchInlineSnapshot(
|
||||
`"<main><h1 prop="hello">Async component</h1><span></span></main>"`
|
||||
)
|
||||
|
||||
expect(toggle.value).toBe(false)
|
||||
|
||||
// hydration
|
||||
|
||||
// reset the value
|
||||
toggle.value = true
|
||||
expect(toggle.value).toBe(true)
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = html
|
||||
createSSRApp(App).mount(container)
|
||||
|
||||
await new Promise(r => setTimeout(r, 10))
|
||||
|
||||
expect(toggle.value).toBe(false)
|
||||
|
||||
// should be hydrated now
|
||||
expect(container.innerHTML).toMatchInlineSnapshot(
|
||||
`"<main><h1 prop="world">Async component</h1><span></span></main>"`
|
||||
)
|
||||
})
|
||||
|
||||
// #3787
|
||||
test('unmount async wrapper before load', async () => {
|
||||
let resolve: any
|
||||
|
@ -981,7 +1122,7 @@ describe('SSR hydration', () => {
|
|||
|
||||
test('force hydrate select option with non-string value bindings', () => {
|
||||
const { container } = mountWithHydration(
|
||||
'<select><option :value="true">ok</option></select>',
|
||||
'<select><option value="true">ok</option></select>',
|
||||
() =>
|
||||
h('select', [
|
||||
// hoisted because bound value is a constant...
|
||||
|
@ -1114,6 +1255,41 @@ describe('SSR hydration', () => {
|
|||
expect(`mismatch`).not.toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('transition appear w/ event listener', async () => {
|
||||
const container = document.createElement('div')
|
||||
container.innerHTML = `<template><button>0</button></template>`
|
||||
createSSRApp({
|
||||
data() {
|
||||
return {
|
||||
count: 0
|
||||
}
|
||||
},
|
||||
template: `
|
||||
<Transition appear>
|
||||
<button @click="count++">{{count}}</button>
|
||||
</Transition>
|
||||
`
|
||||
}).mount(container)
|
||||
|
||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||
<button
|
||||
class="v-enter-from v-enter-active"
|
||||
>
|
||||
0
|
||||
</button>
|
||||
`)
|
||||
|
||||
triggerEvent('click', container.querySelector('button')!)
|
||||
await nextTick()
|
||||
expect(container.firstChild).toMatchInlineSnapshot(`
|
||||
<button
|
||||
class="v-enter-from v-enter-active"
|
||||
>
|
||||
1
|
||||
</button>
|
||||
`)
|
||||
})
|
||||
|
||||
describe('mismatch handling', () => {
|
||||
test('text node', () => {
|
||||
const { container } = mountWithHydration(`foo`, () => 'bar')
|
||||
|
@ -1126,7 +1302,7 @@ describe('SSR hydration', () => {
|
|||
h('div', 'bar')
|
||||
)
|
||||
expect(container.innerHTML).toBe('<div>bar</div>')
|
||||
expect(`Hydration text content mismatch in <div>`).toHaveBeenWarned()
|
||||
expect(`Hydration text content mismatch`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('not enough children', () => {
|
||||
|
@ -1136,7 +1312,7 @@ describe('SSR hydration', () => {
|
|||
expect(container.innerHTML).toBe(
|
||||
'<div><span>foo</span><span>bar</span></div>'
|
||||
)
|
||||
expect(`Hydration children mismatch in <div>`).toHaveBeenWarned()
|
||||
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('too many children', () => {
|
||||
|
@ -1145,7 +1321,7 @@ describe('SSR hydration', () => {
|
|||
() => h('div', [h('span', 'foo')])
|
||||
)
|
||||
expect(container.innerHTML).toBe('<div><span>foo</span></div>')
|
||||
expect(`Hydration children mismatch in <div>`).toHaveBeenWarned()
|
||||
expect(`Hydration children mismatch`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('complete mismatch', () => {
|
||||
|
@ -1219,5 +1395,58 @@ describe('SSR hydration', () => {
|
|||
expect(container.innerHTML).toBe('<div><!--hi--></div>')
|
||||
expect(`Hydration node mismatch`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('class mismatch', () => {
|
||||
mountWithHydration(`<div class="foo bar"></div>`, () =>
|
||||
h('div', { class: ['foo', 'bar'] })
|
||||
)
|
||||
mountWithHydration(`<div class="foo bar"></div>`, () =>
|
||||
h('div', { class: { foo: true, bar: true } })
|
||||
)
|
||||
mountWithHydration(`<div class="foo bar"></div>`, () =>
|
||||
h('div', { class: 'foo bar' })
|
||||
)
|
||||
expect(`Hydration class mismatch`).not.toHaveBeenWarned()
|
||||
mountWithHydration(`<div class="foo bar"></div>`, () =>
|
||||
h('div', { class: 'foo' })
|
||||
)
|
||||
expect(`Hydration class mismatch`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('style mismatch', () => {
|
||||
mountWithHydration(`<div style="color:red;"></div>`, () =>
|
||||
h('div', { style: { color: 'red' } })
|
||||
)
|
||||
mountWithHydration(`<div style="color:red;"></div>`, () =>
|
||||
h('div', { style: `color:red;` })
|
||||
)
|
||||
expect(`Hydration style mismatch`).not.toHaveBeenWarned()
|
||||
mountWithHydration(`<div style="color:red;"></div>`, () =>
|
||||
h('div', { style: { color: 'green' } })
|
||||
)
|
||||
expect(`Hydration style mismatch`).toHaveBeenWarned()
|
||||
})
|
||||
|
||||
test('attr mismatch', () => {
|
||||
mountWithHydration(`<div id="foo"></div>`, () => h('div', { id: 'foo' }))
|
||||
mountWithHydration(`<div spellcheck></div>`, () =>
|
||||
h('div', { spellcheck: '' })
|
||||
)
|
||||
mountWithHydration(`<div></div>`, () => h('div', { id: undefined }))
|
||||
// boolean
|
||||
mountWithHydration(`<select multiple></div>`, () =>
|
||||
h('select', { multiple: true })
|
||||
)
|
||||
mountWithHydration(`<select multiple></div>`, () =>
|
||||
h('select', { multiple: 'multiple' })
|
||||
)
|
||||
expect(`Hydration attribute mismatch`).not.toHaveBeenWarned()
|
||||
|
||||
mountWithHydration(`<div></div>`, () => h('div', { id: 'foo' }))
|
||||
expect(`Hydration attribute mismatch`).toHaveBeenWarned()
|
||||
|
||||
mountWithHydration(`<div id="bar"></div>`, () => h('div', { id: 'foo' }))
|
||||
expect(`Hydration attribute mismatch`).toHaveBeenWarnedTimes(2)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-core",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/runtime-core",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-core.esm-bundler.js",
|
||||
|
|
|
@ -15,7 +15,7 @@ import {
|
|||
ComponentPublicInstance
|
||||
} from './componentPublicInstance'
|
||||
import { Directive, validateDirectiveName } from './directives'
|
||||
import { RootRenderFunction } from './renderer'
|
||||
import { ElementNamespace, RootRenderFunction } from './renderer'
|
||||
import { InjectionKey } from './apiInject'
|
||||
import { warn } from './warning'
|
||||
import { createVNode, cloneVNode, VNode } from './vnode'
|
||||
|
@ -41,12 +41,12 @@ export interface App<HostElement = any> {
|
|||
mixin(mixin: ComponentOptions): this
|
||||
component(name: string): Component | undefined
|
||||
component(name: string, component: Component | DefineComponent): this
|
||||
directive(name: string): Directive | undefined
|
||||
directive(name: string, directive: Directive): this
|
||||
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
|
||||
directive<T = any, V = any>(name: string, directive: Directive<T, V>): this
|
||||
mount(
|
||||
rootContainer: HostElement | string,
|
||||
isHydrate?: boolean,
|
||||
isSVG?: boolean
|
||||
namespace?: boolean | ElementNamespace
|
||||
): ComponentPublicInstance
|
||||
unmount(): void
|
||||
provide<T>(key: InjectionKey<T> | string, value: T): this
|
||||
|
@ -148,17 +148,19 @@ export interface AppContext {
|
|||
filters?: Record<string, Function>
|
||||
}
|
||||
|
||||
type PluginInstallFunction<Options> = Options extends unknown[]
|
||||
type PluginInstallFunction<Options = any[]> = Options extends unknown[]
|
||||
? (app: App, ...options: Options) => any
|
||||
: (app: App, options: Options) => any
|
||||
|
||||
export type Plugin<Options = any[]> =
|
||||
| (PluginInstallFunction<Options> & {
|
||||
install?: PluginInstallFunction<Options>
|
||||
})
|
||||
| {
|
||||
export type ObjectPlugin<Options = any[]> = {
|
||||
install: PluginInstallFunction<Options>
|
||||
}
|
||||
}
|
||||
export type FunctionPlugin<Options = any[]> = PluginInstallFunction<Options> &
|
||||
Partial<ObjectPlugin<Options>>
|
||||
|
||||
export type Plugin<Options = any[]> =
|
||||
| FunctionPlugin<Options>
|
||||
| ObjectPlugin<Options>
|
||||
|
||||
export function createAppContext(): AppContext {
|
||||
return {
|
||||
|
@ -296,7 +298,7 @@ export function createAppAPI<HostElement>(
|
|||
mount(
|
||||
rootContainer: HostElement,
|
||||
isHydrate?: boolean,
|
||||
isSVG?: boolean
|
||||
namespace?: boolean | ElementNamespace
|
||||
): any {
|
||||
if (!isMounted) {
|
||||
// #5571
|
||||
|
@ -312,17 +314,29 @@ export function createAppAPI<HostElement>(
|
|||
// this will be set on the root instance on initial mount.
|
||||
vnode.appContext = context
|
||||
|
||||
if (namespace === true) {
|
||||
namespace = 'svg'
|
||||
} else if (namespace === false) {
|
||||
namespace = undefined
|
||||
}
|
||||
|
||||
// HMR root reload
|
||||
if (__DEV__) {
|
||||
context.reload = () => {
|
||||
render(cloneVNode(vnode), rootContainer, isSVG)
|
||||
// casting to ElementNamespace because TS doesn't guarantee type narrowing
|
||||
// over function boundaries
|
||||
render(
|
||||
cloneVNode(vnode),
|
||||
rootContainer,
|
||||
namespace as ElementNamespace
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (isHydrate && hydrate) {
|
||||
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
|
||||
} else {
|
||||
render(vnode, rootContainer, isSVG)
|
||||
render(vnode, rootContainer, namespace)
|
||||
}
|
||||
isMounted = true
|
||||
app._container = rootContainer
|
||||
|
|
|
@ -5,7 +5,8 @@ import {
|
|||
Prettify,
|
||||
UnionToIntersection,
|
||||
extend,
|
||||
LooseRequired
|
||||
LooseRequired,
|
||||
hasChanged
|
||||
} from '@vue/shared'
|
||||
import {
|
||||
getCurrentInstance,
|
||||
|
@ -30,8 +31,8 @@ import {
|
|||
} from './componentProps'
|
||||
import { warn } from './warning'
|
||||
import { SlotsType, StrictUnwrapSlotsType } from './componentSlots'
|
||||
import { Ref, ref } from '@vue/reactivity'
|
||||
import { watch } from './apiWatch'
|
||||
import { Ref, customRef, ref } from '@vue/reactivity'
|
||||
import { watchSyncEffect } from '.'
|
||||
|
||||
// dev only
|
||||
const warnRuntimeUsage = (method: string) =>
|
||||
|
@ -66,9 +67,9 @@ const warnRuntimeUsage = (method: string) =>
|
|||
* foo?: string
|
||||
* bar: number
|
||||
* }>()
|
||||
* ```
|
||||
*
|
||||
* @see {@link https://vuejs.org/api/sfc-script-setup.html#defineprops-defineemits}
|
||||
* ```
|
||||
*
|
||||
* This is only usable inside `<script setup>`, is compiled away in the
|
||||
* output and should **not** be actually called at runtime.
|
||||
|
@ -94,7 +95,7 @@ export function defineProps() {
|
|||
return null as any
|
||||
}
|
||||
|
||||
type DefineProps<T, BKeys extends keyof T> = Readonly<T> & {
|
||||
export type DefineProps<T, BKeys extends keyof T> = Readonly<T> & {
|
||||
readonly [K in BKeys]-?: boolean
|
||||
}
|
||||
|
||||
|
@ -116,8 +117,9 @@ type BooleanKey<T, K extends keyof T = keyof T> = K extends any
|
|||
* Example type-based declaration:
|
||||
* ```ts
|
||||
* const emit = defineEmits<{
|
||||
* (event: 'change'): void
|
||||
* (event: 'update', id: number): void
|
||||
* // <eventName>: <expected arguments>
|
||||
* change: []
|
||||
* update: [value: string] // named tuple syntax
|
||||
* }>()
|
||||
*
|
||||
* emit('change')
|
||||
|
@ -217,7 +219,7 @@ export function defineSlots<
|
|||
}
|
||||
|
||||
/**
|
||||
* (**Experimental**) Vue `<script setup>` compiler macro for declaring a
|
||||
* Vue `<script setup>` compiler macro for declaring a
|
||||
* two-way binding prop that can be consumed via `v-model` from the parent
|
||||
* component. This will declare a prop with the same name and a corresponding
|
||||
* `update:propName` event.
|
||||
|
@ -226,9 +228,11 @@ export function defineSlots<
|
|||
* Otherwise the prop name will default to "modelValue". In both cases, you
|
||||
* can also pass an additional object which will be used as the prop's options.
|
||||
*
|
||||
* The options object can also specify an additional option, `local`. When set
|
||||
* to `true`, the ref can be locally mutated even if the parent did not pass
|
||||
* the matching `v-model`.
|
||||
* The the returned ref behaves differently depending on whether the parent
|
||||
* provided the corresponding v-model props or not:
|
||||
* - If yes, the returned ref's value will always be in sync with the parent
|
||||
* prop.
|
||||
* - If not, the returned ref will behave like a normal local ref.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
|
@ -245,32 +249,26 @@ export function defineSlots<
|
|||
*
|
||||
* // with specified name and default value
|
||||
* const count = defineModel<number>('count', { default: 0 })
|
||||
*
|
||||
* // local mutable model, can be mutated locally
|
||||
* // even if the parent did not pass the matching `v-model`.
|
||||
* const count = defineModel<number>('count', { local: true, default: 0 })
|
||||
* ```
|
||||
*/
|
||||
export function defineModel<T>(
|
||||
options: { required: true } & PropOptions<T> & DefineModelOptions
|
||||
options: { required: true } & PropOptions<T>
|
||||
): Ref<T>
|
||||
export function defineModel<T>(
|
||||
options: { default: any } & PropOptions<T> & DefineModelOptions
|
||||
options: { default: any } & PropOptions<T>
|
||||
): Ref<T>
|
||||
export function defineModel<T>(
|
||||
options?: PropOptions<T> & DefineModelOptions
|
||||
): Ref<T | undefined>
|
||||
export function defineModel<T>(options?: PropOptions<T>): Ref<T | undefined>
|
||||
export function defineModel<T>(
|
||||
name: string,
|
||||
options: { required: true } & PropOptions<T> & DefineModelOptions
|
||||
options: { required: true } & PropOptions<T>
|
||||
): Ref<T>
|
||||
export function defineModel<T>(
|
||||
name: string,
|
||||
options: { default: any } & PropOptions<T> & DefineModelOptions
|
||||
options: { default: any } & PropOptions<T>
|
||||
): Ref<T>
|
||||
export function defineModel<T>(
|
||||
name: string,
|
||||
options?: PropOptions<T> & DefineModelOptions
|
||||
options?: PropOptions<T>
|
||||
): Ref<T | undefined>
|
||||
export function defineModel(): any {
|
||||
if (__DEV__) {
|
||||
|
@ -278,10 +276,6 @@ export function defineModel(): any {
|
|||
}
|
||||
}
|
||||
|
||||
interface DefineModelOptions {
|
||||
local?: boolean
|
||||
}
|
||||
|
||||
type NotUndefined<T> = T extends undefined ? never : T
|
||||
|
||||
type InferDefaults<T> = {
|
||||
|
@ -356,14 +350,9 @@ export function useAttrs(): SetupContext['attrs'] {
|
|||
|
||||
export function useModel<T extends Record<string, any>, K extends keyof T>(
|
||||
props: T,
|
||||
name: K,
|
||||
options?: { local?: boolean }
|
||||
name: K
|
||||
): Ref<T[K]>
|
||||
export function useModel(
|
||||
props: Record<string, any>,
|
||||
name: string,
|
||||
options?: { local?: boolean }
|
||||
): Ref {
|
||||
export function useModel(props: Record<string, any>, name: string): Ref {
|
||||
const i = getCurrentInstance()!
|
||||
if (__DEV__ && !i) {
|
||||
warn(`useModel() called without active instance.`)
|
||||
|
@ -375,32 +364,30 @@ export function useModel(
|
|||
return ref() as any
|
||||
}
|
||||
|
||||
if (options && options.local) {
|
||||
const proxy = ref<any>(props[name])
|
||||
|
||||
watch(
|
||||
() => props[name],
|
||||
v => (proxy.value = v)
|
||||
)
|
||||
|
||||
watch(proxy, value => {
|
||||
if (value !== props[name]) {
|
||||
i.emit(`update:${name}`, value)
|
||||
return customRef((track, trigger) => {
|
||||
let localValue: any
|
||||
watchSyncEffect(() => {
|
||||
const propValue = props[name]
|
||||
if (hasChanged(localValue, propValue)) {
|
||||
localValue = propValue
|
||||
trigger()
|
||||
}
|
||||
})
|
||||
|
||||
return proxy
|
||||
} else {
|
||||
return {
|
||||
__v_isRef: true,
|
||||
get value() {
|
||||
return props[name]
|
||||
get() {
|
||||
track()
|
||||
return localValue
|
||||
},
|
||||
set value(value) {
|
||||
set(value) {
|
||||
const rawProps = i.vnode!.props
|
||||
if (!(rawProps && name in rawProps) && hasChanged(value, localValue)) {
|
||||
localValue = value
|
||||
trigger()
|
||||
}
|
||||
i.emit(`update:${name}`, value)
|
||||
}
|
||||
} as any
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function getContext(): SetupContext {
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
} from '@vue/shared'
|
||||
import { warn } from '../warning'
|
||||
import { cloneVNode, createVNode } from '../vnode'
|
||||
import { RootRenderFunction } from '../renderer'
|
||||
import { ElementNamespace, RootRenderFunction } from '../renderer'
|
||||
import {
|
||||
App,
|
||||
AppConfig,
|
||||
|
@ -82,8 +82,11 @@ export type CompatVue = Pick<App, 'version' | 'component' | 'directive'> & {
|
|||
|
||||
component(name: string): Component | undefined
|
||||
component(name: string, component: Component): CompatVue
|
||||
directive(name: string): Directive | undefined
|
||||
directive(name: string, directive: Directive): CompatVue
|
||||
directive<T = any, V = any>(name: string): Directive<T, V> | undefined
|
||||
directive<T = any, V = any>(
|
||||
name: string,
|
||||
directive: Directive<T, V>
|
||||
): CompatVue
|
||||
|
||||
compile(template: string): RenderFunction
|
||||
|
||||
|
@ -503,7 +506,13 @@ function installCompatMount(
|
|||
container = selectorOrEl || document.createElement('div')
|
||||
}
|
||||
|
||||
const isSVG = container instanceof SVGElement
|
||||
let namespace: ElementNamespace
|
||||
if (container instanceof SVGElement) namespace = 'svg'
|
||||
else if (
|
||||
typeof MathMLElement === 'function' &&
|
||||
container instanceof MathMLElement
|
||||
)
|
||||
namespace = 'mathml'
|
||||
|
||||
// HMR root reload
|
||||
if (__DEV__) {
|
||||
|
@ -511,7 +520,7 @@ function installCompatMount(
|
|||
const cloned = cloneVNode(vnode)
|
||||
// compat mode will use instance if not reset to null
|
||||
cloned.component = null
|
||||
render(cloned, container, isSVG)
|
||||
render(cloned, container, namespace)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -538,7 +547,7 @@ function installCompatMount(
|
|||
container.innerHTML = ''
|
||||
|
||||
// TODO hydration
|
||||
render(vnode, container, isSVG)
|
||||
render(vnode, container, namespace)
|
||||
|
||||
if (container instanceof Element) {
|
||||
container.removeAttribute('v-cloak')
|
||||
|
|
|
@ -51,7 +51,8 @@ import {
|
|||
EmitFn,
|
||||
emit,
|
||||
normalizeEmitsOptions,
|
||||
EmitsToProps
|
||||
EmitsToProps,
|
||||
ShortEmitsToObject
|
||||
} from './componentEmits'
|
||||
import {
|
||||
EMPTY_OBJ,
|
||||
|
@ -82,6 +83,39 @@ import {
|
|||
import { SchedulerJob } from './scheduler'
|
||||
import { LifecycleHooks } from './enums'
|
||||
|
||||
/**
|
||||
* Public utility type for extracting the instance type of a component.
|
||||
* Works with all valid component definition types. This is intended to replace
|
||||
* the usage of `InstanceType<typeof Comp>` which only works for
|
||||
* constructor-based component definition types.
|
||||
*
|
||||
* Exmaple:
|
||||
* ```ts
|
||||
* const MyComp = { ... }
|
||||
* declare const instance: ComponentInstance<typeof MyComp>
|
||||
* ```
|
||||
*/
|
||||
export type ComponentInstance<T> = T extends { new (): ComponentPublicInstance }
|
||||
? InstanceType<T>
|
||||
: T extends FunctionalComponent<infer Props, infer Emits>
|
||||
? ComponentPublicInstance<Props, {}, {}, {}, {}, ShortEmitsToObject<Emits>>
|
||||
: T extends Component<
|
||||
infer Props,
|
||||
infer RawBindings,
|
||||
infer D,
|
||||
infer C,
|
||||
infer M
|
||||
>
|
||||
? // NOTE we override Props/RawBindings/D to make sure is not `unknown`
|
||||
ComponentPublicInstance<
|
||||
unknown extends Props ? {} : Props,
|
||||
unknown extends RawBindings ? {} : RawBindings,
|
||||
unknown extends D ? {} : D,
|
||||
C,
|
||||
M
|
||||
>
|
||||
: never // not a vue Component
|
||||
|
||||
/**
|
||||
* For extending allowed non-declared props on components in TSX
|
||||
*/
|
||||
|
@ -126,16 +160,17 @@ export interface ComponentInternalOptions {
|
|||
|
||||
export interface FunctionalComponent<
|
||||
P = {},
|
||||
E extends EmitsOptions = {},
|
||||
S extends Record<string, any> = any
|
||||
E extends EmitsOptions | Record<string, any[]> = {},
|
||||
S extends Record<string, any> = any,
|
||||
EE extends EmitsOptions = ShortEmitsToObject<E>
|
||||
> extends ComponentInternalOptions {
|
||||
// use of any here is intentional so it can be a valid JSX Element constructor
|
||||
(
|
||||
props: P & EmitsToProps<E>,
|
||||
ctx: Omit<SetupContext<E, IfAny<S, {}, SlotsType<S>>>, 'expose'>
|
||||
props: P & EmitsToProps<EE>,
|
||||
ctx: Omit<SetupContext<EE, IfAny<S, {}, SlotsType<S>>>, 'expose'>
|
||||
): any
|
||||
props?: ComponentPropsOptions<P>
|
||||
emits?: E | (keyof E)[]
|
||||
emits?: EE | (keyof EE)[]
|
||||
slots?: IfAny<S, Slots, SlotsType<S>>
|
||||
inheritAttrs?: boolean
|
||||
displayName?: string
|
||||
|
@ -158,10 +193,12 @@ export type ConcreteComponent<
|
|||
RawBindings = any,
|
||||
D = any,
|
||||
C extends ComputedOptions = ComputedOptions,
|
||||
M extends MethodOptions = MethodOptions
|
||||
M extends MethodOptions = MethodOptions,
|
||||
E extends EmitsOptions | Record<string, any[]> = {},
|
||||
S extends Record<string, any> = any
|
||||
> =
|
||||
| ComponentOptions<Props, RawBindings, D, C, M>
|
||||
| FunctionalComponent<Props, any>
|
||||
| FunctionalComponent<Props, E, S>
|
||||
|
||||
/**
|
||||
* A type used in public APIs where a component type is expected.
|
||||
|
@ -172,9 +209,11 @@ export type Component<
|
|||
RawBindings = any,
|
||||
D = any,
|
||||
C extends ComputedOptions = ComputedOptions,
|
||||
M extends MethodOptions = MethodOptions
|
||||
M extends MethodOptions = MethodOptions,
|
||||
E extends EmitsOptions | Record<string, any[]> = {},
|
||||
S extends Record<string, any> = any
|
||||
> =
|
||||
| ConcreteComponent<Props, RawBindings, D, C, M>
|
||||
| ConcreteComponent<Props, RawBindings, D, C, M, E, S>
|
||||
| ComponentPublicInstanceConstructor<Props>
|
||||
|
||||
export type { ComponentOptions }
|
||||
|
@ -590,13 +629,10 @@ export let currentInstance: ComponentInternalInstance | null = null
|
|||
export const getCurrentInstance: () => ComponentInternalInstance | null = () =>
|
||||
currentInstance || currentRenderingInstance
|
||||
|
||||
type GlobalInstanceSetter = ((
|
||||
let internalSetCurrentInstance: (
|
||||
instance: ComponentInternalInstance | null
|
||||
) => void) & { version?: string }
|
||||
|
||||
let internalSetCurrentInstance: GlobalInstanceSetter
|
||||
let globalCurrentInstanceSetters: GlobalInstanceSetter[]
|
||||
let settersKey = '__VUE_INSTANCE_SETTERS__'
|
||||
) => void
|
||||
let setInSSRSetupState: (state: boolean) => void
|
||||
|
||||
/**
|
||||
* The following makes getCurrentInstance() usage across multiple copies of Vue
|
||||
|
@ -611,21 +647,36 @@ let settersKey = '__VUE_INSTANCE_SETTERS__'
|
|||
* found during browser execution.
|
||||
*/
|
||||
if (__SSR__) {
|
||||
if (!(globalCurrentInstanceSetters = getGlobalThis()[settersKey])) {
|
||||
globalCurrentInstanceSetters = getGlobalThis()[settersKey] = []
|
||||
}
|
||||
globalCurrentInstanceSetters.push(i => (currentInstance = i))
|
||||
internalSetCurrentInstance = instance => {
|
||||
if (globalCurrentInstanceSetters.length > 1) {
|
||||
globalCurrentInstanceSetters.forEach(s => s(instance))
|
||||
} else {
|
||||
globalCurrentInstanceSetters[0](instance)
|
||||
type Setter = (v: any) => void
|
||||
const g = getGlobalThis()
|
||||
const registerGlobalSetter = (key: string, setter: Setter) => {
|
||||
let setters: Setter[]
|
||||
if (!(setters = g[key])) setters = g[key] = []
|
||||
setters.push(setter)
|
||||
return (v: any) => {
|
||||
if (setters.length > 1) setters.forEach(set => set(v))
|
||||
else setters[0](v)
|
||||
}
|
||||
}
|
||||
internalSetCurrentInstance = registerGlobalSetter(
|
||||
`__VUE_INSTANCE_SETTERS__`,
|
||||
v => (currentInstance = v)
|
||||
)
|
||||
// also make `isInSSRComponentSetup` sharable across copies of Vue.
|
||||
// this is needed in the SFC playground when SSRing async components, since
|
||||
// we have to load both the runtime and the server-renderer from CDNs, they
|
||||
// contain duplicated copies of Vue runtime code.
|
||||
setInSSRSetupState = registerGlobalSetter(
|
||||
`__VUE_SSR_SETTERS__`,
|
||||
v => (isInSSRComponentSetup = v)
|
||||
)
|
||||
} else {
|
||||
internalSetCurrentInstance = i => {
|
||||
currentInstance = i
|
||||
}
|
||||
setInSSRSetupState = v => {
|
||||
isInSSRComponentSetup = v
|
||||
}
|
||||
}
|
||||
|
||||
export const setCurrentInstance = (instance: ComponentInternalInstance) => {
|
||||
|
@ -659,7 +710,7 @@ export function setupComponent(
|
|||
instance: ComponentInternalInstance,
|
||||
isSSR = false
|
||||
) {
|
||||
isInSSRComponentSetup = isSSR
|
||||
isSSR && setInSSRSetupState(isSSR)
|
||||
|
||||
const { props, children } = instance.vnode
|
||||
const isStateful = isStatefulComponent(instance)
|
||||
|
@ -669,7 +720,8 @@ export function setupComponent(
|
|||
const setupResult = isStateful
|
||||
? setupStatefulComponent(instance, isSSR)
|
||||
: undefined
|
||||
isInSSRComponentSetup = false
|
||||
|
||||
isSSR && setInSSRSetupState(false)
|
||||
return setupResult
|
||||
}
|
||||
|
||||
|
|
|
@ -38,23 +38,28 @@ export type EmitsOptions = ObjectEmitsOptions | string[]
|
|||
|
||||
export type EmitsToProps<T extends EmitsOptions> = T extends string[]
|
||||
? {
|
||||
[K in string & `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
|
||||
[K in `on${Capitalize<T[number]>}`]?: (...args: any[]) => any
|
||||
}
|
||||
: T extends ObjectEmitsOptions
|
||||
? {
|
||||
[K in string &
|
||||
`on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
||||
? T[Uncapitalize<C>] extends null
|
||||
? (...args: any[]) => any
|
||||
: (
|
||||
[K in `on${Capitalize<string & keyof T>}`]?: K extends `on${infer C}`
|
||||
? (
|
||||
...args: T[Uncapitalize<C>] extends (...args: infer P) => any
|
||||
? P
|
||||
: T[Uncapitalize<C>] extends null
|
||||
? any[]
|
||||
: never
|
||||
) => any
|
||||
: never
|
||||
}
|
||||
: {}
|
||||
|
||||
export type ShortEmitsToObject<E> = E extends Record<string, any[]>
|
||||
? {
|
||||
[K in keyof E]: (...args: E[K]) => any
|
||||
}
|
||||
: E
|
||||
|
||||
export type EmitFn<
|
||||
Options = ObjectEmitsOptions,
|
||||
Event extends keyof Options = keyof Options
|
||||
|
@ -66,6 +71,8 @@ export type EmitFn<
|
|||
{
|
||||
[key in Event]: Options[key] extends (...args: infer Args) => any
|
||||
? (event: key, ...args: Args) => void
|
||||
: Options[key] extends any[]
|
||||
? (event: key, ...args: Options[key]) => void
|
||||
: (event: key, ...args: any[]) => void
|
||||
}[Event]
|
||||
>
|
||||
|
|
|
@ -433,8 +433,17 @@ export function updateHOCHostEl(
|
|||
{ vnode, parent }: ComponentInternalInstance,
|
||||
el: typeof vnode.el // HostNode
|
||||
) {
|
||||
while (parent && parent.subTree === vnode) {
|
||||
if (!el) return
|
||||
while (parent) {
|
||||
const root = parent.subTree
|
||||
if (root.suspense && root.suspense.activeBranch === vnode) {
|
||||
root.el = vnode.el
|
||||
}
|
||||
if (root === vnode) {
|
||||
;(vnode = parent.vnode).el = el
|
||||
parent = parent.parent
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ export type SlotsType<T extends Record<string, any> = Record<string, any>> = {
|
|||
export type StrictUnwrapSlotsType<
|
||||
S extends SlotsType,
|
||||
T = NonNullable<S[typeof SlotSymbol]>
|
||||
> = [keyof S] extends [never] ? Slots : Readonly<T>
|
||||
> = [keyof S] extends [never] ? Slots : Readonly<T> & T
|
||||
|
||||
export type UnwrapSlotsType<
|
||||
S extends SlotsType,
|
||||
|
|
|
@ -37,7 +37,8 @@ import {
|
|||
queuePostRenderEffect,
|
||||
MoveType,
|
||||
RendererElement,
|
||||
RendererNode
|
||||
RendererNode,
|
||||
ElementNamespace
|
||||
} from '../renderer'
|
||||
import { setTransitionHooks } from './BaseTransition'
|
||||
import { ComponentRenderContext } from '../componentPublicInstance'
|
||||
|
@ -64,7 +65,7 @@ export interface KeepAliveContext extends ComponentRenderContext {
|
|||
vnode: VNode,
|
||||
container: RendererElement,
|
||||
anchor: RendererNode | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
optimized: boolean
|
||||
) => void
|
||||
deactivate: (vnode: VNode) => void
|
||||
|
@ -125,7 +126,13 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
} = sharedContext
|
||||
const storageContainer = createElement('div')
|
||||
|
||||
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
|
||||
sharedContext.activate = (
|
||||
vnode,
|
||||
container,
|
||||
anchor,
|
||||
namespace,
|
||||
optimized
|
||||
) => {
|
||||
const instance = vnode.component!
|
||||
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
|
||||
// in case props have changed
|
||||
|
@ -136,7 +143,7 @@ const KeepAliveImpl: ComponentOptions = {
|
|||
anchor,
|
||||
instance,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
vnode.slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
|
|
@ -18,7 +18,8 @@ import {
|
|||
MoveType,
|
||||
SetupRenderEffectFn,
|
||||
RendererNode,
|
||||
RendererElement
|
||||
RendererElement,
|
||||
ElementNamespace
|
||||
} from '../renderer'
|
||||
import { queuePostFlushCb } from '../scheduler'
|
||||
import { filterSingleRoot, updateHOCHostEl } from '../componentRenderUtils'
|
||||
|
@ -46,6 +47,9 @@ export interface SuspenseProps {
|
|||
|
||||
export const isSuspense = (type: any): boolean => type.__isSuspense
|
||||
|
||||
// incrementing unique id for every pending branch
|
||||
let suspenseId = 0
|
||||
|
||||
// Suspense exposes a component-like API, and is treated like a component
|
||||
// in the compiler, but internally it's a special built-in type that hooks
|
||||
// directly into the renderer.
|
||||
|
@ -63,7 +67,7 @@ export const SuspenseImpl = {
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
// platform-specific impl passed from renderer
|
||||
|
@ -76,7 +80,7 @@ export const SuspenseImpl = {
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
rendererInternals
|
||||
|
@ -88,7 +92,7 @@ export const SuspenseImpl = {
|
|||
container,
|
||||
anchor,
|
||||
parentComponent,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
rendererInternals
|
||||
|
@ -130,7 +134,7 @@ function mountSuspense(
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
rendererInternals: RendererInternals
|
||||
|
@ -147,7 +151,7 @@ function mountSuspense(
|
|||
container,
|
||||
hiddenContainer,
|
||||
anchor,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
rendererInternals
|
||||
|
@ -161,7 +165,7 @@ function mountSuspense(
|
|||
null,
|
||||
parentComponent,
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds
|
||||
)
|
||||
// now check if we have encountered any async deps
|
||||
|
@ -179,7 +183,7 @@ function mountSuspense(
|
|||
anchor,
|
||||
parentComponent,
|
||||
null, // fallback tree will not have suspense context
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds
|
||||
)
|
||||
setActiveBranch(suspense, vnode.ssFallback!)
|
||||
|
@ -195,7 +199,7 @@ function patchSuspense(
|
|||
container: RendererElement,
|
||||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
{ p: patch, um: unmount, o: { createElement } }: RendererInternals
|
||||
|
@ -218,13 +222,20 @@ function patchSuspense(
|
|||
null,
|
||||
parentComponent,
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
if (suspense.deps <= 0) {
|
||||
suspense.resolve()
|
||||
} else if (isInFallback) {
|
||||
// It's possible that the app is in hydrating state when patching the
|
||||
// suspense instance. If someone updates the dependency during component
|
||||
// setup in children of suspense boundary, that would be problemtic
|
||||
// because we aren't actually showing a fallback content when
|
||||
// patchSuspense is called. In such case, patch of fallback content
|
||||
// should be no op
|
||||
if (!isHydrating) {
|
||||
patch(
|
||||
activeBranch,
|
||||
newFallback,
|
||||
|
@ -232,15 +243,17 @@ function patchSuspense(
|
|||
anchor,
|
||||
parentComponent,
|
||||
null, // fallback tree will not have suspense context
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
setActiveBranch(suspense, newFallback)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// toggled before pending tree is resolved
|
||||
suspense.pendingId++
|
||||
// increment pending ID. this is used to invalidate async callbacks
|
||||
suspense.pendingId = suspenseId++
|
||||
if (isHydrating) {
|
||||
// if toggled before hydration is finished, the current DOM tree is
|
||||
// no longer valid. set it as the active branch so it will be unmounted
|
||||
|
@ -250,7 +263,6 @@ function patchSuspense(
|
|||
} else {
|
||||
unmount(pendingBranch, parentComponent, suspense)
|
||||
}
|
||||
// increment pending ID. this is used to invalidate async callbacks
|
||||
// reset suspense state
|
||||
suspense.deps = 0
|
||||
// discard effects from pending branch
|
||||
|
@ -267,7 +279,7 @@ function patchSuspense(
|
|||
null,
|
||||
parentComponent,
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -281,7 +293,7 @@ function patchSuspense(
|
|||
anchor,
|
||||
parentComponent,
|
||||
null, // fallback tree will not have suspense context
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -296,7 +308,7 @@ function patchSuspense(
|
|||
anchor,
|
||||
parentComponent,
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -311,7 +323,7 @@ function patchSuspense(
|
|||
null,
|
||||
parentComponent,
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -330,7 +342,7 @@ function patchSuspense(
|
|||
anchor,
|
||||
parentComponent,
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -341,7 +353,11 @@ function patchSuspense(
|
|||
triggerEvent(n2, 'onPending')
|
||||
// mount pending branch in off-dom container
|
||||
suspense.pendingBranch = newBranch
|
||||
suspense.pendingId++
|
||||
if (newBranch.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
|
||||
suspense.pendingId = newBranch.component!.suspenseId!
|
||||
} else {
|
||||
suspense.pendingId = suspenseId++
|
||||
}
|
||||
patch(
|
||||
null,
|
||||
newBranch,
|
||||
|
@ -349,7 +365,7 @@ function patchSuspense(
|
|||
null,
|
||||
parentComponent,
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -376,7 +392,7 @@ export interface SuspenseBoundary {
|
|||
vnode: VNode<RendererNode, RendererElement, SuspenseProps>
|
||||
parent: SuspenseBoundary | null
|
||||
parentComponent: ComponentInternalInstance | null
|
||||
isSVG: boolean
|
||||
namespace: ElementNamespace
|
||||
container: RendererElement
|
||||
hiddenContainer: RendererElement
|
||||
anchor: RendererNode | null
|
||||
|
@ -413,7 +429,7 @@ function createSuspenseBoundary(
|
|||
container: RendererElement,
|
||||
hiddenContainer: RendererElement,
|
||||
anchor: RendererNode | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
rendererInternals: RendererInternals,
|
||||
|
@ -455,7 +471,7 @@ function createSuspenseBoundary(
|
|||
vnode,
|
||||
parent: parentSuspense,
|
||||
parentComponent,
|
||||
isSVG,
|
||||
namespace,
|
||||
container,
|
||||
hiddenContainer,
|
||||
anchor,
|
||||
|
@ -464,7 +480,7 @@ function createSuspenseBoundary(
|
|||
timeout: typeof timeout === 'number' ? timeout : -1,
|
||||
activeBranch: null,
|
||||
pendingBranch: null,
|
||||
isInFallback: true,
|
||||
isInFallback: !isHydrating,
|
||||
isHydrating,
|
||||
isUnmounted: false,
|
||||
effects: [],
|
||||
|
@ -576,12 +592,13 @@ function createSuspenseBoundary(
|
|||
return
|
||||
}
|
||||
|
||||
const { vnode, activeBranch, parentComponent, container, isSVG } =
|
||||
const { vnode, activeBranch, parentComponent, container, namespace } =
|
||||
suspense
|
||||
|
||||
// invoke @fallback event
|
||||
triggerEvent(vnode, 'onFallback')
|
||||
|
||||
const anchor = next(activeBranch!)
|
||||
const mountFallback = () => {
|
||||
if (!suspense.isInFallback) {
|
||||
return
|
||||
|
@ -591,10 +608,10 @@ function createSuspenseBoundary(
|
|||
null,
|
||||
fallbackVNode,
|
||||
container,
|
||||
next(activeBranch!),
|
||||
anchor,
|
||||
parentComponent,
|
||||
null, // fallback tree will not have suspense context
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -675,7 +692,7 @@ function createSuspenseBoundary(
|
|||
// consider the comment placeholder case.
|
||||
hydratedEl ? null : next(instance.subTree),
|
||||
suspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
optimized
|
||||
)
|
||||
if (placeholder) {
|
||||
|
@ -721,7 +738,7 @@ function hydrateSuspense(
|
|||
vnode: VNode,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
rendererInternals: RendererInternals,
|
||||
|
@ -742,7 +759,7 @@ function hydrateSuspense(
|
|||
node.parentNode!,
|
||||
document.createElement('div'),
|
||||
null,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
rendererInternals,
|
||||
|
|
|
@ -6,7 +6,8 @@ import {
|
|||
RendererElement,
|
||||
RendererNode,
|
||||
RendererOptions,
|
||||
traverseStaticChildren
|
||||
traverseStaticChildren,
|
||||
ElementNamespace
|
||||
} from '../renderer'
|
||||
import { VNode, VNodeArrayChildren, VNodeProps } from '../vnode'
|
||||
import { isString, ShapeFlags } from '@vue/shared'
|
||||
|
@ -28,6 +29,9 @@ const isTeleportDisabled = (props: VNode['props']): boolean =>
|
|||
const isTargetSVG = (target: RendererElement): boolean =>
|
||||
typeof SVGElement !== 'undefined' && target instanceof SVGElement
|
||||
|
||||
const isTargetMathML = (target: RendererElement): boolean =>
|
||||
typeof MathMLElement === 'function' && target instanceof MathMLElement
|
||||
|
||||
const resolveTarget = <T = RendererElement>(
|
||||
props: TeleportProps | null,
|
||||
select: RendererOptions['querySelector']
|
||||
|
@ -72,7 +76,7 @@ export const TeleportImpl = {
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
internals: RendererInternals
|
||||
|
@ -109,7 +113,11 @@ export const TeleportImpl = {
|
|||
if (target) {
|
||||
insert(targetAnchor, target)
|
||||
// #2652 we could be teleporting from a non-SVG tree into an SVG tree
|
||||
isSVG = isSVG || isTargetSVG(target)
|
||||
if (namespace === 'svg' || isTargetSVG(target)) {
|
||||
namespace = 'svg'
|
||||
} else if (namespace === 'mathml' || isTargetMathML(target)) {
|
||||
namespace = 'mathml'
|
||||
}
|
||||
} else if (__DEV__ && !disabled) {
|
||||
warn('Invalid Teleport target on mount:', target, `(${typeof target})`)
|
||||
}
|
||||
|
@ -124,7 +132,7 @@ export const TeleportImpl = {
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -145,7 +153,12 @@ export const TeleportImpl = {
|
|||
const wasDisabled = isTeleportDisabled(n1.props)
|
||||
const currentContainer = wasDisabled ? container : target
|
||||
const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
|
||||
isSVG = isSVG || isTargetSVG(target)
|
||||
|
||||
if (namespace === 'svg' || isTargetSVG(target)) {
|
||||
namespace = 'svg'
|
||||
} else if (namespace === 'mathml' || isTargetMathML(target)) {
|
||||
namespace = 'mathml'
|
||||
}
|
||||
|
||||
if (dynamicChildren) {
|
||||
// fast path when the teleport happens to be a block root
|
||||
|
@ -155,7 +168,7 @@ export const TeleportImpl = {
|
|||
currentContainer,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds
|
||||
)
|
||||
// even in block tree mode we need to make sure all root-level nodes
|
||||
|
@ -170,7 +183,7 @@ export const TeleportImpl = {
|
|||
currentAnchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
false
|
||||
)
|
||||
|
|
|
@ -20,6 +20,11 @@ export function initFeatureFlags() {
|
|||
getGlobalThis().__VUE_PROD_DEVTOOLS__ = false
|
||||
}
|
||||
|
||||
if (typeof __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__ !== 'boolean') {
|
||||
__DEV__ && needWarn.push(`__VUE_PROD_HYDRATION_MISMATCH_DETAILS__`)
|
||||
getGlobalThis().__VUE_PROD_HYDRATION_MISMATCH_DETAILS__ = false
|
||||
}
|
||||
|
||||
if (__DEV__ && needWarn.length) {
|
||||
const multi = needWarn.length > 1
|
||||
console.warn(
|
||||
|
|
|
@ -75,10 +75,27 @@ interface Constructor<P = any> {
|
|||
new (...args: any[]): { $props: P }
|
||||
}
|
||||
|
||||
type HTMLElementEventHandler = {
|
||||
[K in keyof HTMLElementEventMap as `on${Capitalize<K>}`]?: (
|
||||
ev: HTMLElementEventMap[K]
|
||||
) => any
|
||||
}
|
||||
|
||||
// The following is a series of overloads for providing props validation of
|
||||
// manually written render functions.
|
||||
|
||||
// element
|
||||
export function h<K extends keyof HTMLElementTagNameMap>(
|
||||
type: K,
|
||||
children?: RawChildren
|
||||
): VNode
|
||||
export function h<K extends keyof HTMLElementTagNameMap>(
|
||||
type: K,
|
||||
props?: (RawProps & HTMLElementEventHandler) | null,
|
||||
children?: RawChildren | RawSlots
|
||||
): VNode
|
||||
|
||||
// custom element
|
||||
export function h(type: string, children?: RawChildren): VNode
|
||||
export function h(
|
||||
type: string,
|
||||
|
|
|
@ -14,7 +14,20 @@ import { flushPostFlushCbs } from './scheduler'
|
|||
import { ComponentInternalInstance } from './component'
|
||||
import { invokeDirectiveHook } from './directives'
|
||||
import { warn } from './warning'
|
||||
import { PatchFlags, ShapeFlags, isReservedProp, isOn } from '@vue/shared'
|
||||
import {
|
||||
PatchFlags,
|
||||
ShapeFlags,
|
||||
isReservedProp,
|
||||
isOn,
|
||||
normalizeClass,
|
||||
normalizeStyle,
|
||||
stringifyStyle,
|
||||
isBooleanAttr,
|
||||
isString,
|
||||
includeBooleanAttr,
|
||||
isKnownHtmlAttr,
|
||||
isKnownSvgAttr
|
||||
} from '@vue/shared'
|
||||
import { needTransition, RendererInternals } from './renderer'
|
||||
import { setRef } from './rendererTemplateRef'
|
||||
import {
|
||||
|
@ -39,7 +52,17 @@ enum DOMNodeTypes {
|
|||
let hasMismatch = false
|
||||
|
||||
const isSVGContainer = (container: Element) =>
|
||||
/svg/.test(container.namespaceURI!) && container.tagName !== 'foreignObject'
|
||||
container.namespaceURI!.includes('svg') &&
|
||||
container.tagName !== 'foreignObject'
|
||||
|
||||
const isMathMLContainer = (container: Element) =>
|
||||
container.namespaceURI!.includes('MathML')
|
||||
|
||||
const getContainerType = (container: Element): 'svg' | 'mathml' | undefined => {
|
||||
if (isSVGContainer(container)) return 'svg'
|
||||
if (isMathMLContainer(container)) return 'mathml'
|
||||
return undefined
|
||||
}
|
||||
|
||||
const isComment = (node: Node): node is Comment =>
|
||||
node.nodeType === DOMNodeTypes.COMMENT
|
||||
|
@ -68,7 +91,7 @@ export function createHydrationFunctions(
|
|||
|
||||
const hydrate: RootHydrateFunction = (vnode, container) => {
|
||||
if (!container.hasChildNodes()) {
|
||||
__DEV__ &&
|
||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
warn(
|
||||
`Attempting to hydrate existing markup but container is empty. ` +
|
||||
`Performing full mount instead.`
|
||||
|
@ -146,13 +169,14 @@ export function createHydrationFunctions(
|
|||
} else {
|
||||
if ((node as Text).data !== vnode.children) {
|
||||
hasMismatch = true
|
||||
__DEV__ &&
|
||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
warn(
|
||||
`Hydration text mismatch:` +
|
||||
`\n- Server rendered: ${JSON.stringify(
|
||||
`Hydration text mismatch in`,
|
||||
node.parentNode,
|
||||
`\n - rendered on server: ${JSON.stringify(
|
||||
(node as Text).data
|
||||
)}` +
|
||||
`\n- Client rendered: ${JSON.stringify(vnode.children)}`
|
||||
`\n - expected on client: ${JSON.stringify(vnode.children)}`
|
||||
)
|
||||
;(node as Text).data = vnode.children as string
|
||||
}
|
||||
|
@ -263,7 +287,7 @@ export function createHydrationFunctions(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(container),
|
||||
getContainerType(container),
|
||||
optimized
|
||||
)
|
||||
|
||||
|
@ -306,13 +330,13 @@ export function createHydrationFunctions(
|
|||
vnode,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(parentNode(node)!),
|
||||
getContainerType(parentNode(node)!),
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
rendererInternals,
|
||||
hydrateNode
|
||||
)
|
||||
} else if (__DEV__) {
|
||||
} else if (__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) {
|
||||
warn('Invalid HostVNode type:', type, `(${typeof type})`)
|
||||
}
|
||||
}
|
||||
|
@ -344,51 +368,6 @@ export function createHydrationFunctions(
|
|||
if (dirs) {
|
||||
invokeDirectiveHook(vnode, null, parentComponent, 'created')
|
||||
}
|
||||
// props
|
||||
if (props) {
|
||||
if (
|
||||
forcePatch ||
|
||||
!optimized ||
|
||||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
||||
) {
|
||||
for (const key in props) {
|
||||
if (
|
||||
(forcePatch &&
|
||||
(key.endsWith('value') || key === 'indeterminate')) ||
|
||||
(isOn(key) && !isReservedProp(key)) ||
|
||||
// force hydrate v-bind with .prop modifiers
|
||||
key[0] === '.'
|
||||
) {
|
||||
patchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
false,
|
||||
undefined,
|
||||
parentComponent
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (props.onClick) {
|
||||
// Fast path for click listeners (which is most often) to avoid
|
||||
// iterating through props.
|
||||
patchProp(
|
||||
el,
|
||||
'onClick',
|
||||
null,
|
||||
props.onClick,
|
||||
false,
|
||||
undefined,
|
||||
parentComponent
|
||||
)
|
||||
}
|
||||
}
|
||||
// vnode / directive hooks
|
||||
let vnodeHooks: VNodeHook | null | undefined
|
||||
if ((vnodeHooks = props && props.onVnodeBeforeMount)) {
|
||||
invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
||||
}
|
||||
|
||||
// handle appear transition
|
||||
let needCallTransitionHooks = false
|
||||
|
@ -411,21 +390,6 @@ export function createHydrationFunctions(
|
|||
vnode.el = el = content
|
||||
}
|
||||
|
||||
if (dirs) {
|
||||
invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
|
||||
}
|
||||
|
||||
if (
|
||||
(vnodeHooks = props && props.onVnodeMounted) ||
|
||||
dirs ||
|
||||
needCallTransitionHooks
|
||||
) {
|
||||
queueEffectWithSuspense(() => {
|
||||
vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
||||
needCallTransitionHooks && transition!.enter(el)
|
||||
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
|
||||
}, parentSuspense)
|
||||
}
|
||||
// children
|
||||
if (
|
||||
shapeFlag & ShapeFlags.ARRAY_CHILDREN &&
|
||||
|
@ -444,10 +408,14 @@ export function createHydrationFunctions(
|
|||
let hasWarned = false
|
||||
while (next) {
|
||||
hasMismatch = true
|
||||
if (__DEV__ && !hasWarned) {
|
||||
if (
|
||||
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
!hasWarned
|
||||
) {
|
||||
warn(
|
||||
`Hydration children mismatch in <${vnode.type as string}>: ` +
|
||||
`server rendered element contains more child nodes than client vdom.`
|
||||
`Hydration children mismatch on`,
|
||||
el,
|
||||
`\nServer rendered element contains more child nodes than client vdom.`
|
||||
)
|
||||
hasWarned = true
|
||||
}
|
||||
|
@ -459,18 +427,84 @@ export function createHydrationFunctions(
|
|||
} else if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
|
||||
if (el.textContent !== vnode.children) {
|
||||
hasMismatch = true
|
||||
__DEV__ &&
|
||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
warn(
|
||||
`Hydration text content mismatch in <${
|
||||
vnode.type as string
|
||||
}>:\n` +
|
||||
`- Server rendered: ${el.textContent}\n` +
|
||||
`- Client rendered: ${vnode.children as string}`
|
||||
`Hydration text content mismatch on`,
|
||||
el,
|
||||
`\n - rendered on server: ${el.textContent}` +
|
||||
`\n - expected on client: ${vnode.children as string}`
|
||||
)
|
||||
el.textContent = vnode.children as string
|
||||
}
|
||||
}
|
||||
|
||||
// props
|
||||
if (props) {
|
||||
if (
|
||||
__DEV__ ||
|
||||
forcePatch ||
|
||||
!optimized ||
|
||||
patchFlag & (PatchFlags.FULL_PROPS | PatchFlags.NEED_HYDRATION)
|
||||
) {
|
||||
for (const key in props) {
|
||||
// check hydration mismatch
|
||||
if (__DEV__ && propHasMismatch(el, key, props[key])) {
|
||||
hasMismatch = true
|
||||
}
|
||||
if (
|
||||
(forcePatch &&
|
||||
(key.endsWith('value') || key === 'indeterminate')) ||
|
||||
(isOn(key) && !isReservedProp(key)) ||
|
||||
// force hydrate v-bind with .prop modifiers
|
||||
key[0] === '.'
|
||||
) {
|
||||
patchProp(
|
||||
el,
|
||||
key,
|
||||
null,
|
||||
props[key],
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent
|
||||
)
|
||||
}
|
||||
}
|
||||
} else if (props.onClick) {
|
||||
// Fast path for click listeners (which is most often) to avoid
|
||||
// iterating through props.
|
||||
patchProp(
|
||||
el,
|
||||
'onClick',
|
||||
null,
|
||||
props.onClick,
|
||||
undefined,
|
||||
undefined,
|
||||
parentComponent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// vnode / directive hooks
|
||||
let vnodeHooks: VNodeHook | null | undefined
|
||||
if ((vnodeHooks = props && props.onVnodeBeforeMount)) {
|
||||
invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
||||
}
|
||||
if (dirs) {
|
||||
invokeDirectiveHook(vnode, null, parentComponent, 'beforeMount')
|
||||
}
|
||||
if (
|
||||
(vnodeHooks = props && props.onVnodeMounted) ||
|
||||
dirs ||
|
||||
needCallTransitionHooks
|
||||
) {
|
||||
queueEffectWithSuspense(() => {
|
||||
vnodeHooks && invokeVNodeHook(vnodeHooks, parentComponent, vnode)
|
||||
needCallTransitionHooks && transition!.enter(el)
|
||||
dirs && invokeDirectiveHook(vnode, null, parentComponent, 'mounted')
|
||||
}, parentSuspense)
|
||||
}
|
||||
}
|
||||
|
||||
return el.nextSibling
|
||||
}
|
||||
|
||||
|
@ -504,10 +538,14 @@ export function createHydrationFunctions(
|
|||
continue
|
||||
} else {
|
||||
hasMismatch = true
|
||||
if (__DEV__ && !hasWarned) {
|
||||
if (
|
||||
(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
!hasWarned
|
||||
) {
|
||||
warn(
|
||||
`Hydration children mismatch in <${container.tagName.toLowerCase()}>: ` +
|
||||
`server rendered element contains fewer child nodes than client vdom.`
|
||||
`Hydration children mismatch on`,
|
||||
container,
|
||||
`\nServer rendered element contains fewer child nodes than client vdom.`
|
||||
)
|
||||
hasWarned = true
|
||||
}
|
||||
|
@ -519,7 +557,7 @@ export function createHydrationFunctions(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(container),
|
||||
getContainerType(container),
|
||||
slotScopeIds
|
||||
)
|
||||
}
|
||||
|
@ -573,17 +611,17 @@ export function createHydrationFunctions(
|
|||
isFragment: boolean
|
||||
): Node | null => {
|
||||
hasMismatch = true
|
||||
__DEV__ &&
|
||||
;(__DEV__ || __FEATURE_PROD_HYDRATION_MISMATCH_DETAILS__) &&
|
||||
warn(
|
||||
`Hydration node mismatch:\n- Client vnode:`,
|
||||
vnode.type,
|
||||
`\n- Server rendered DOM:`,
|
||||
`Hydration node mismatch:\n- rendered on server:`,
|
||||
node,
|
||||
node.nodeType === DOMNodeTypes.TEXT
|
||||
? `(text)`
|
||||
: isComment(node) && node.data === '['
|
||||
? `(start of fragment)`
|
||||
: ``
|
||||
: ``,
|
||||
`\n- expected on client:`,
|
||||
vnode.type
|
||||
)
|
||||
vnode.el = null
|
||||
|
||||
|
@ -611,7 +649,7 @@ export function createHydrationFunctions(
|
|||
next,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVGContainer(container),
|
||||
getContainerType(container),
|
||||
slotScopeIds
|
||||
)
|
||||
return next
|
||||
|
@ -670,3 +708,60 @@ export function createHydrationFunctions(
|
|||
|
||||
return [hydrate, hydrateNode] as const
|
||||
}
|
||||
|
||||
/**
|
||||
* Dev only
|
||||
*/
|
||||
function propHasMismatch(el: Element, key: string, clientValue: any): boolean {
|
||||
let mismatchType: string | undefined
|
||||
let mismatchKey: string | undefined
|
||||
let actual: any
|
||||
let expected: any
|
||||
if (key === 'class') {
|
||||
actual = el.className
|
||||
expected = normalizeClass(clientValue)
|
||||
if (actual !== expected) {
|
||||
mismatchType = mismatchKey = `class`
|
||||
}
|
||||
} else if (key === 'style') {
|
||||
actual = el.getAttribute('style')
|
||||
expected = isString(clientValue)
|
||||
? clientValue
|
||||
: stringifyStyle(normalizeStyle(clientValue))
|
||||
if (actual !== expected) {
|
||||
mismatchType = mismatchKey = 'style'
|
||||
}
|
||||
} else if (
|
||||
(el instanceof SVGElement && isKnownSvgAttr(key)) ||
|
||||
(el instanceof HTMLElement && (isBooleanAttr(key) || isKnownHtmlAttr(key)))
|
||||
) {
|
||||
actual = el.hasAttribute(key) && el.getAttribute(key)
|
||||
expected = isBooleanAttr(key)
|
||||
? includeBooleanAttr(clientValue)
|
||||
? ''
|
||||
: false
|
||||
: clientValue == null
|
||||
? false
|
||||
: String(clientValue)
|
||||
if (actual !== expected) {
|
||||
mismatchType = `attribute`
|
||||
mismatchKey = key
|
||||
}
|
||||
}
|
||||
|
||||
if (mismatchType) {
|
||||
const format = (v: any) =>
|
||||
v === false ? `(not rendered)` : `${mismatchKey}="${v}"`
|
||||
warn(
|
||||
`Hydration ${mismatchType} mismatch on`,
|
||||
el,
|
||||
`\n - rendered on server: ${format(actual)}` +
|
||||
`\n - expected on client: ${format(expected)}` +
|
||||
`\n Note: this mismatch is check-only. The DOM will not be rectified ` +
|
||||
`in production due to performance overhead.` +
|
||||
`\n You should fix the source of the mismatch.`
|
||||
)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ export { provide, inject, hasInjectionContext } from './apiInject'
|
|||
export { nextTick } from './scheduler'
|
||||
export { defineComponent } from './apiDefineComponent'
|
||||
export { defineAsyncComponent } from './apiAsyncComponent'
|
||||
export { useAttrs, useSlots } from './apiSetupHelpers'
|
||||
export { useAttrs, useSlots, type DefineProps } from './apiSetupHelpers'
|
||||
|
||||
// <script setup> API ----------------------------------------------------------
|
||||
|
||||
|
@ -212,6 +212,8 @@ export type {
|
|||
AppConfig,
|
||||
AppContext,
|
||||
Plugin,
|
||||
ObjectPlugin,
|
||||
FunctionPlugin,
|
||||
CreateAppFunction,
|
||||
OptionMergeFunction
|
||||
} from './apiCreateApp'
|
||||
|
@ -230,9 +232,10 @@ export type {
|
|||
ComponentInternalInstance,
|
||||
SetupContext,
|
||||
ComponentCustomProps,
|
||||
AllowedComponentProps
|
||||
AllowedComponentProps,
|
||||
ComponentInstance
|
||||
} from './component'
|
||||
export type { DefineComponent } from './apiDefineComponent'
|
||||
export type { DefineComponent, PublicProps } from './apiDefineComponent'
|
||||
export type {
|
||||
ComponentOptions,
|
||||
ComponentOptionsMixin,
|
||||
|
@ -260,7 +263,8 @@ export type {
|
|||
RendererElement,
|
||||
HydrationRenderer,
|
||||
RendererOptions,
|
||||
RootRenderFunction
|
||||
RootRenderFunction,
|
||||
ElementNamespace
|
||||
} from './renderer'
|
||||
export type { RootHydrateFunction } from './hydration'
|
||||
export type { Slot, Slots, SlotsType } from './componentSlots'
|
||||
|
|
|
@ -83,10 +83,12 @@ export interface HydrationRenderer extends Renderer<Element | ShadowRoot> {
|
|||
hydrate: RootHydrateFunction
|
||||
}
|
||||
|
||||
export type ElementNamespace = 'svg' | 'mathml' | undefined
|
||||
|
||||
export type RootRenderFunction<HostElement = RendererElement> = (
|
||||
vnode: VNode | null,
|
||||
container: HostElement,
|
||||
isSVG?: boolean
|
||||
namespace?: ElementNamespace
|
||||
) => void
|
||||
|
||||
export interface RendererOptions<
|
||||
|
@ -98,7 +100,7 @@ export interface RendererOptions<
|
|||
key: string,
|
||||
prevValue: any,
|
||||
nextValue: any,
|
||||
isSVG?: boolean,
|
||||
namespace?: ElementNamespace,
|
||||
prevChildren?: VNode<HostNode, HostElement>[],
|
||||
parentComponent?: ComponentInternalInstance | null,
|
||||
parentSuspense?: SuspenseBoundary | null,
|
||||
|
@ -108,7 +110,7 @@ export interface RendererOptions<
|
|||
remove(el: HostNode): void
|
||||
createElement(
|
||||
type: string,
|
||||
isSVG?: boolean,
|
||||
namespace?: ElementNamespace,
|
||||
isCustomizedBuiltIn?: string,
|
||||
vnodeProps?: (VNodeProps & { [key: string]: any }) | null
|
||||
): HostElement
|
||||
|
@ -125,7 +127,7 @@ export interface RendererOptions<
|
|||
content: string,
|
||||
parent: HostElement,
|
||||
anchor: HostNode | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
start?: HostNode | null,
|
||||
end?: HostNode | null
|
||||
): [HostNode, HostNode]
|
||||
|
@ -170,7 +172,7 @@ type PatchFn = (
|
|||
anchor?: RendererNode | null,
|
||||
parentComponent?: ComponentInternalInstance | null,
|
||||
parentSuspense?: SuspenseBoundary | null,
|
||||
isSVG?: boolean,
|
||||
namespace?: ElementNamespace,
|
||||
slotScopeIds?: string[] | null,
|
||||
optimized?: boolean
|
||||
) => void
|
||||
|
@ -181,7 +183,7 @@ type MountChildrenFn = (
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean,
|
||||
start?: number
|
||||
|
@ -194,7 +196,7 @@ type PatchChildrenFn = (
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => void
|
||||
|
@ -205,7 +207,7 @@ type PatchBlockChildrenFn = (
|
|||
fallbackContainer: RendererElement,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null
|
||||
) => void
|
||||
|
||||
|
@ -244,7 +246,7 @@ export type MountComponentFn = (
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
optimized: boolean
|
||||
) => void
|
||||
|
||||
|
@ -261,7 +263,7 @@ export type SetupRenderEffectFn = (
|
|||
container: RendererElement,
|
||||
anchor: RendererNode | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
optimized: boolean
|
||||
) => void
|
||||
|
||||
|
@ -362,7 +364,7 @@ function baseCreateRenderer(
|
|||
anchor = null,
|
||||
parentComponent = null,
|
||||
parentSuspense = null,
|
||||
isSVG = false,
|
||||
namespace = undefined,
|
||||
slotScopeIds = null,
|
||||
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
|
||||
) => {
|
||||
|
@ -392,9 +394,9 @@ function baseCreateRenderer(
|
|||
break
|
||||
case Static:
|
||||
if (n1 == null) {
|
||||
mountStaticNode(n2, container, anchor, isSVG)
|
||||
mountStaticNode(n2, container, anchor, namespace)
|
||||
} else if (__DEV__) {
|
||||
patchStaticNode(n1, n2, container, isSVG)
|
||||
patchStaticNode(n1, n2, container, namespace)
|
||||
}
|
||||
break
|
||||
case Fragment:
|
||||
|
@ -405,7 +407,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -419,7 +421,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -431,7 +433,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -443,7 +445,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
internals
|
||||
|
@ -456,7 +458,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
internals
|
||||
|
@ -509,7 +511,7 @@ function baseCreateRenderer(
|
|||
n2: VNode,
|
||||
container: RendererElement,
|
||||
anchor: RendererNode | null,
|
||||
isSVG: boolean
|
||||
namespace: ElementNamespace
|
||||
) => {
|
||||
// static nodes are only present when used with compiler-dom/runtime-dom
|
||||
// which guarantees presence of hostInsertStaticContent.
|
||||
|
@ -517,7 +519,7 @@ function baseCreateRenderer(
|
|||
n2.children as string,
|
||||
container,
|
||||
anchor,
|
||||
isSVG,
|
||||
namespace,
|
||||
n2.el,
|
||||
n2.anchor
|
||||
)
|
||||
|
@ -530,7 +532,7 @@ function baseCreateRenderer(
|
|||
n1: VNode,
|
||||
n2: VNode,
|
||||
container: RendererElement,
|
||||
isSVG: boolean
|
||||
namespace: ElementNamespace
|
||||
) => {
|
||||
// static nodes are only patched during dev for HMR
|
||||
if (n2.children !== n1.children) {
|
||||
|
@ -542,7 +544,7 @@ function baseCreateRenderer(
|
|||
n2.children as string,
|
||||
container,
|
||||
anchor,
|
||||
isSVG
|
||||
namespace
|
||||
)
|
||||
} else {
|
||||
n2.el = n1.el
|
||||
|
@ -581,11 +583,16 @@ function baseCreateRenderer(
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
isSVG = isSVG || n2.type === 'svg'
|
||||
if (n2.type === 'svg') {
|
||||
namespace = 'svg'
|
||||
} else if (n2.type === 'math') {
|
||||
namespace = 'mathml'
|
||||
}
|
||||
|
||||
if (n1 == null) {
|
||||
mountElement(
|
||||
n2,
|
||||
|
@ -593,7 +600,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -603,7 +610,7 @@ function baseCreateRenderer(
|
|||
n2,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -616,17 +623,17 @@ function baseCreateRenderer(
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
let el: RendererElement
|
||||
let vnodeHook: VNodeHook | undefined | null
|
||||
const { type, props, shapeFlag, transition, dirs } = vnode
|
||||
const { props, shapeFlag, transition, dirs } = vnode
|
||||
|
||||
el = vnode.el = hostCreateElement(
|
||||
vnode.type as string,
|
||||
isSVG,
|
||||
namespace,
|
||||
props && props.is,
|
||||
props
|
||||
)
|
||||
|
@ -642,7 +649,7 @@ function baseCreateRenderer(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG && type !== 'foreignObject',
|
||||
resolveChildrenNamespace(vnode, namespace),
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -662,7 +669,7 @@ function baseCreateRenderer(
|
|||
key,
|
||||
null,
|
||||
props[key],
|
||||
isSVG,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
|
@ -680,7 +687,7 @@ function baseCreateRenderer(
|
|||
* affect non-DOM renderers)
|
||||
*/
|
||||
if ('value' in props) {
|
||||
hostPatchProp(el, 'value', null, props.value)
|
||||
hostPatchProp(el, 'value', null, props.value, namespace)
|
||||
}
|
||||
if ((vnodeHook = props.onVnodeBeforeMount)) {
|
||||
invokeVNodeHook(vnodeHook, parentComponent, vnode)
|
||||
|
@ -764,7 +771,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
start = 0
|
||||
|
@ -780,7 +787,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -792,7 +799,7 @@ function baseCreateRenderer(
|
|||
n2: VNode,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
|
@ -822,7 +829,6 @@ function baseCreateRenderer(
|
|||
dynamicChildren = null
|
||||
}
|
||||
|
||||
const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
|
||||
if (dynamicChildren) {
|
||||
patchBlockChildren(
|
||||
n1.dynamicChildren!,
|
||||
|
@ -830,7 +836,7 @@ function baseCreateRenderer(
|
|||
el,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
areChildrenSVG,
|
||||
resolveChildrenNamespace(n2, namespace),
|
||||
slotScopeIds
|
||||
)
|
||||
if (__DEV__) {
|
||||
|
@ -846,7 +852,7 @@ function baseCreateRenderer(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
areChildrenSVG,
|
||||
resolveChildrenNamespace(n2, namespace),
|
||||
slotScopeIds,
|
||||
false
|
||||
)
|
||||
|
@ -866,21 +872,21 @@ function baseCreateRenderer(
|
|||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
namespace
|
||||
)
|
||||
} else {
|
||||
// class
|
||||
// this flag is matched when the element has dynamic class bindings.
|
||||
if (patchFlag & PatchFlags.CLASS) {
|
||||
if (oldProps.class !== newProps.class) {
|
||||
hostPatchProp(el, 'class', null, newProps.class, isSVG)
|
||||
hostPatchProp(el, 'class', null, newProps.class, namespace)
|
||||
}
|
||||
}
|
||||
|
||||
// style
|
||||
// this flag is matched when the element has dynamic style bindings
|
||||
if (patchFlag & PatchFlags.STYLE) {
|
||||
hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
|
||||
hostPatchProp(el, 'style', oldProps.style, newProps.style, namespace)
|
||||
}
|
||||
|
||||
// props
|
||||
|
@ -903,7 +909,7 @@ function baseCreateRenderer(
|
|||
key,
|
||||
prev,
|
||||
next,
|
||||
isSVG,
|
||||
namespace,
|
||||
n1.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
|
@ -930,7 +936,7 @@ function baseCreateRenderer(
|
|||
newProps,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
namespace
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -949,7 +955,7 @@ function baseCreateRenderer(
|
|||
fallbackContainer,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds
|
||||
) => {
|
||||
for (let i = 0; i < newChildren.length; i++) {
|
||||
|
@ -979,7 +985,7 @@ function baseCreateRenderer(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
true
|
||||
)
|
||||
|
@ -993,7 +999,7 @@ function baseCreateRenderer(
|
|||
newProps: Data,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean
|
||||
namespace: ElementNamespace
|
||||
) => {
|
||||
if (oldProps !== newProps) {
|
||||
if (oldProps !== EMPTY_OBJ) {
|
||||
|
@ -1004,7 +1010,7 @@ function baseCreateRenderer(
|
|||
key,
|
||||
oldProps[key],
|
||||
null,
|
||||
isSVG,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
|
@ -1025,7 +1031,7 @@ function baseCreateRenderer(
|
|||
key,
|
||||
prev,
|
||||
next,
|
||||
isSVG,
|
||||
namespace,
|
||||
vnode.children as VNode[],
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
|
@ -1034,7 +1040,7 @@ function baseCreateRenderer(
|
|||
}
|
||||
}
|
||||
if ('value' in newProps) {
|
||||
hostPatchProp(el, 'value', oldProps.value, newProps.value)
|
||||
hostPatchProp(el, 'value', oldProps.value, newProps.value, namespace)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1046,7 +1052,7 @@ function baseCreateRenderer(
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
|
@ -1085,7 +1091,7 @@ function baseCreateRenderer(
|
|||
fragmentEndAnchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1106,7 +1112,7 @@ function baseCreateRenderer(
|
|||
container,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds
|
||||
)
|
||||
if (__DEV__) {
|
||||
|
@ -1134,7 +1140,7 @@ function baseCreateRenderer(
|
|||
fragmentEndAnchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1149,7 +1155,7 @@ function baseCreateRenderer(
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
|
@ -1160,7 +1166,7 @@ function baseCreateRenderer(
|
|||
n2,
|
||||
container,
|
||||
anchor,
|
||||
isSVG,
|
||||
namespace,
|
||||
optimized
|
||||
)
|
||||
} else {
|
||||
|
@ -1170,7 +1176,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
optimized
|
||||
)
|
||||
}
|
||||
|
@ -1185,7 +1191,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace: ElementNamespace,
|
||||
optimized
|
||||
) => {
|
||||
// 2.x compat may pre-create the component instance before actually
|
||||
|
@ -1236,18 +1242,17 @@ function baseCreateRenderer(
|
|||
const placeholder = (instance.subTree = createVNode(Comment))
|
||||
processCommentNode(null, placeholder, container!, anchor)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
} else {
|
||||
setupRenderEffect(
|
||||
instance,
|
||||
initialVNode,
|
||||
container,
|
||||
anchor,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
optimized
|
||||
)
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
popWarningContext()
|
||||
|
@ -1296,7 +1301,7 @@ function baseCreateRenderer(
|
|||
container,
|
||||
anchor,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace: ElementNamespace,
|
||||
optimized
|
||||
) => {
|
||||
const componentUpdateFn = () => {
|
||||
|
@ -1380,7 +1385,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
instance,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
namespace
|
||||
)
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, `patch`)
|
||||
|
@ -1441,10 +1446,32 @@ function baseCreateRenderer(
|
|||
// #2458: deference mount-only object parameters to prevent memleaks
|
||||
initialVNode = container = anchor = null as any
|
||||
} else {
|
||||
let { next, bu, u, parent, vnode } = instance
|
||||
|
||||
if (__FEATURE_SUSPENSE__) {
|
||||
const nonHydratedAsyncRoot = locateNonHydratedAsyncRoot(instance)
|
||||
// we are trying to update some async comp before hydration
|
||||
// this will cause crash because we don't know the root node yet
|
||||
if (nonHydratedAsyncRoot) {
|
||||
// only sync the properties and abort the rest of operations
|
||||
if (next) {
|
||||
next.el = vnode.el
|
||||
updateComponentPreRender(instance, next, optimized)
|
||||
}
|
||||
// and continue the rest of operations once the deps are resolved
|
||||
nonHydratedAsyncRoot.asyncDep!.then(() => {
|
||||
// the instance may be destroyed during the time period
|
||||
if (!instance.isUnmounted) {
|
||||
componentUpdateFn()
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// updateComponent
|
||||
// This is triggered by mutation of component's own state (next: null)
|
||||
// OR parent calling processComponent (next: VNode)
|
||||
let { next, bu, u, parent, vnode } = instance
|
||||
let originNext = next
|
||||
let vnodeHook: VNodeHook | null | undefined
|
||||
if (__DEV__) {
|
||||
|
@ -1499,7 +1526,7 @@ function baseCreateRenderer(
|
|||
getNextHostNode(prevTree),
|
||||
instance,
|
||||
parentSuspense,
|
||||
isSVG
|
||||
namespace
|
||||
)
|
||||
if (__DEV__) {
|
||||
endMeasure(instance, `patch`)
|
||||
|
@ -1588,7 +1615,7 @@ function baseCreateRenderer(
|
|||
pauseTracking()
|
||||
// props update may have triggered pre-flush watchers.
|
||||
// flush them before the render update.
|
||||
flushPreFlushCbs()
|
||||
flushPreFlushCbs(instance)
|
||||
resetTracking()
|
||||
}
|
||||
|
||||
|
@ -1599,7 +1626,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds,
|
||||
optimized = false
|
||||
) => {
|
||||
|
@ -1620,7 +1647,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1634,7 +1661,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1663,7 +1690,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1685,7 +1712,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1701,7 +1728,7 @@ function baseCreateRenderer(
|
|||
anchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
|
@ -1722,7 +1749,7 @@ function baseCreateRenderer(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1745,7 +1772,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized,
|
||||
commonLength
|
||||
|
@ -1761,7 +1788,7 @@ function baseCreateRenderer(
|
|||
parentAnchor: RendererNode | null,
|
||||
parentComponent: ComponentInternalInstance | null,
|
||||
parentSuspense: SuspenseBoundary | null,
|
||||
isSVG: boolean,
|
||||
namespace: ElementNamespace,
|
||||
slotScopeIds: string[] | null,
|
||||
optimized: boolean
|
||||
) => {
|
||||
|
@ -1786,7 +1813,7 @@ function baseCreateRenderer(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1812,7 +1839,7 @@ function baseCreateRenderer(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1844,7 +1871,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1947,7 +1974,7 @@ function baseCreateRenderer(
|
|||
null,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -1976,7 +2003,7 @@ function baseCreateRenderer(
|
|||
anchor,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
isSVG,
|
||||
namespace,
|
||||
slotScopeIds,
|
||||
optimized
|
||||
)
|
||||
|
@ -2321,13 +2348,21 @@ function baseCreateRenderer(
|
|||
return hostNextSibling((vnode.anchor || vnode.el)!)
|
||||
}
|
||||
|
||||
const render: RootRenderFunction = (vnode, container, isSVG) => {
|
||||
const render: RootRenderFunction = (vnode, container, namespace) => {
|
||||
if (vnode == null) {
|
||||
if (container._vnode) {
|
||||
unmount(container._vnode, null, null, true)
|
||||
}
|
||||
} else {
|
||||
patch(container._vnode || null, vnode, container, null, null, null, isSVG)
|
||||
patch(
|
||||
container._vnode || null,
|
||||
vnode,
|
||||
container,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
namespace
|
||||
)
|
||||
}
|
||||
flushPreFlushCbs()
|
||||
flushPostFlushCbs()
|
||||
|
@ -2362,6 +2397,20 @@ function baseCreateRenderer(
|
|||
}
|
||||
}
|
||||
|
||||
function resolveChildrenNamespace(
|
||||
{ type, props }: VNode,
|
||||
currentNamespace: ElementNamespace
|
||||
): ElementNamespace {
|
||||
return (currentNamespace === 'svg' && type === 'foreignObject') ||
|
||||
(currentNamespace === 'mathml' &&
|
||||
type === 'annotation-xml' &&
|
||||
props &&
|
||||
props.encoding &&
|
||||
props.encoding.includes('html'))
|
||||
? undefined
|
||||
: currentNamespace
|
||||
}
|
||||
|
||||
function toggleRecurse(
|
||||
{ effect, update }: ComponentInternalInstance,
|
||||
allowed: boolean
|
||||
|
@ -2461,3 +2510,16 @@ function getSequence(arr: number[]): number[] {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
function locateNonHydratedAsyncRoot(
|
||||
instance: ComponentInternalInstance
|
||||
): ComponentInternalInstance | undefined {
|
||||
const subComponent = instance.subTree.component
|
||||
if (subComponent) {
|
||||
if (subComponent.asyncDep && !subComponent.asyncResolved) {
|
||||
return subComponent
|
||||
} else {
|
||||
return locateNonHydratedAsyncRoot(subComponent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -139,6 +139,7 @@ export function queuePostFlushCb(cb: SchedulerJobs) {
|
|||
}
|
||||
|
||||
export function flushPreFlushCbs(
|
||||
instance?: ComponentInternalInstance,
|
||||
seen?: CountMap,
|
||||
// if currently flushing, skip the current job itself
|
||||
i = isFlushing ? flushIndex + 1 : 0
|
||||
|
@ -149,6 +150,9 @@ export function flushPreFlushCbs(
|
|||
for (; i < queue.length; i++) {
|
||||
const cb = queue[i]
|
||||
if (cb && cb.pre) {
|
||||
if (instance && cb.id !== instance.uid) {
|
||||
continue
|
||||
}
|
||||
if (__DEV__ && checkRecursiveUpdates(seen!, cb)) {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -293,4 +293,35 @@ describe('useCssVars', () => {
|
|||
await nextTick()
|
||||
expect(target.children.length).toBe(0)
|
||||
})
|
||||
|
||||
test('with string style', async () => {
|
||||
document.body.innerHTML = ''
|
||||
const state = reactive({ color: 'red' })
|
||||
const root = document.createElement('div')
|
||||
const disabled = ref(false)
|
||||
|
||||
const App = {
|
||||
setup() {
|
||||
useCssVars(() => state)
|
||||
return () => [
|
||||
h(
|
||||
'div',
|
||||
{ style: disabled.value ? 'pointer-events: none' : undefined },
|
||||
'foo'
|
||||
)
|
||||
]
|
||||
}
|
||||
}
|
||||
render(h(App), root)
|
||||
await nextTick()
|
||||
for (const c of [].slice.call(root.children as any)) {
|
||||
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe('red')
|
||||
}
|
||||
disabled.value = true
|
||||
await nextTick()
|
||||
|
||||
for (const c of [].slice.call(root.children as any)) {
|
||||
expect((c as HTMLElement).style.getPropertyValue(`--color`)).toBe('red')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
|
|
@ -2,7 +2,7 @@ import { nodeOps, svgNS } from '../src/nodeOps'
|
|||
|
||||
describe('runtime-dom: node-ops', () => {
|
||||
test("the <select>'s multiple attr should be set in createElement", () => {
|
||||
const el = nodeOps.createElement('select', false, undefined, {
|
||||
const el = nodeOps.createElement('select', undefined, undefined, {
|
||||
multiple: ''
|
||||
}) as HTMLSelectElement
|
||||
const option1 = nodeOps.createElement('option') as HTMLOptionElement
|
||||
|
@ -21,7 +21,12 @@ describe('runtime-dom: node-ops', () => {
|
|||
test('fresh insertion', () => {
|
||||
const content = `<div>one</div><div>two</div>three`
|
||||
const parent = document.createElement('div')
|
||||
const nodes = nodeOps.insertStaticContent!(content, parent, null, false)
|
||||
const nodes = nodeOps.insertStaticContent!(
|
||||
content,
|
||||
parent,
|
||||
null,
|
||||
undefined
|
||||
)
|
||||
expect(parent.innerHTML).toBe(content)
|
||||
expect(nodes[0]).toBe(parent.firstChild)
|
||||
expect(nodes[1]).toBe(parent.lastChild)
|
||||
|
@ -33,7 +38,12 @@ describe('runtime-dom: node-ops', () => {
|
|||
const parent = document.createElement('div')
|
||||
parent.innerHTML = existing
|
||||
const anchor = parent.firstChild
|
||||
const nodes = nodeOps.insertStaticContent!(content, parent, anchor, false)
|
||||
const nodes = nodeOps.insertStaticContent!(
|
||||
content,
|
||||
parent,
|
||||
anchor,
|
||||
undefined
|
||||
)
|
||||
expect(parent.innerHTML).toBe(content + existing)
|
||||
expect(nodes[0]).toBe(parent.firstChild)
|
||||
expect(nodes[1]).toBe(parent.childNodes[parent.childNodes.length - 2])
|
||||
|
@ -46,7 +56,7 @@ describe('runtime-dom: node-ops', () => {
|
|||
content,
|
||||
parent,
|
||||
null,
|
||||
true
|
||||
'svg'
|
||||
)
|
||||
expect(parent.innerHTML).toBe(content)
|
||||
expect(first).toBe(parent.firstChild)
|
||||
|
@ -65,7 +75,7 @@ describe('runtime-dom: node-ops', () => {
|
|||
content,
|
||||
parent,
|
||||
anchor,
|
||||
true
|
||||
'svg'
|
||||
)
|
||||
expect(parent.innerHTML).toBe(content + existing)
|
||||
expect(first).toBe(parent.firstChild)
|
||||
|
@ -88,7 +98,7 @@ describe('runtime-dom: node-ops', () => {
|
|||
content,
|
||||
parent,
|
||||
anchor,
|
||||
false,
|
||||
undefined,
|
||||
cached.firstChild,
|
||||
cached.lastChild
|
||||
)
|
||||
|
|
|
@ -4,15 +4,15 @@ import { xlinkNS } from '../src/modules/attrs'
|
|||
describe('runtime-dom: attrs patching', () => {
|
||||
test('xlink attributes', () => {
|
||||
const el = document.createElementNS('http://www.w3.org/2000/svg', 'use')
|
||||
patchProp(el, 'xlink:href', null, 'a', true)
|
||||
patchProp(el, 'xlink:href', null, 'a', 'svg')
|
||||
expect(el.getAttributeNS(xlinkNS, 'href')).toBe('a')
|
||||
patchProp(el, 'xlink:href', 'a', null, true)
|
||||
patchProp(el, 'xlink:href', 'a', null, 'svg')
|
||||
expect(el.getAttributeNS(xlinkNS, 'href')).toBe(null)
|
||||
})
|
||||
|
||||
test('textContent attributes /w svg', () => {
|
||||
const el = document.createElementNS('http://www.w3.org/2000/svg', 'use')
|
||||
patchProp(el, 'textContent', null, 'foo', true)
|
||||
patchProp(el, 'textContent', null, 'foo', 'svg')
|
||||
expect(el.attributes.length).toBe(0)
|
||||
expect(el.innerHTML).toBe('foo')
|
||||
})
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('runtime-dom: class patching', () => {
|
|||
|
||||
test('svg', () => {
|
||||
const el = document.createElementNS(svgNS, 'svg')
|
||||
patchProp(el, 'class', null, 'foo', true)
|
||||
patchProp(el, 'class', null, 'foo', 'svg')
|
||||
expect(el.getAttribute('class')).toBe('foo')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -300,6 +300,13 @@ describe('runtime-dom: props patching', () => {
|
|||
expect(el.getAttribute('width')).toBe('24px')
|
||||
})
|
||||
|
||||
// # 9762 should fallthrough to `key in el` logic for non embedded tags
|
||||
test('width and height on custom elements', () => {
|
||||
const el = document.createElement('foobar')
|
||||
patchProp(el, 'width', null, '24px')
|
||||
expect(el.getAttribute('width')).toBe('24px')
|
||||
})
|
||||
|
||||
test('translate attribute', () => {
|
||||
const el = document.createElement('div')
|
||||
patchProp(el, 'translate', null, 'no')
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/runtime-dom",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/runtime-dom",
|
||||
"main": "index.js",
|
||||
"module": "dist/runtime-dom.esm-bundler.js",
|
||||
|
@ -37,6 +37,6 @@
|
|||
"dependencies": {
|
||||
"@vue/shared": "workspace:*",
|
||||
"@vue/runtime-core": "workspace:*",
|
||||
"csstype": "^3.1.2"
|
||||
"csstype": "^3.1.3"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
} from '@vue/runtime-core'
|
||||
import { ShapeFlags } from '@vue/shared'
|
||||
|
||||
export const CSS_VAR_TEXT = Symbol(__DEV__ ? 'CSS_VAR_TEXT' : '')
|
||||
/**
|
||||
* Runtime helper for SFC's CSS variable injection feature.
|
||||
* @private
|
||||
|
@ -79,8 +80,11 @@ function setVarsOnVNode(vnode: VNode, vars: Record<string, string>) {
|
|||
function setVarsOnNode(el: Node, vars: Record<string, string>) {
|
||||
if (el.nodeType === 1) {
|
||||
const style = (el as HTMLElement).style
|
||||
let cssText = ''
|
||||
for (const key in vars) {
|
||||
style.setProperty(`--${key}`, vars[key])
|
||||
cssText += `--${key}: ${vars[key]};`
|
||||
}
|
||||
;(style as any)[CSS_VAR_TEXT] = cssText
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,7 +10,8 @@ import {
|
|||
RootHydrateFunction,
|
||||
isRuntimeOnly,
|
||||
DeprecationTypes,
|
||||
compatUtils
|
||||
compatUtils,
|
||||
ElementNamespace
|
||||
} from '@vue/runtime-core'
|
||||
import { nodeOps } from './nodeOps'
|
||||
import { patchProp } from './patchProp'
|
||||
|
@ -21,7 +22,8 @@ import {
|
|||
isHTMLTag,
|
||||
isSVGTag,
|
||||
extend,
|
||||
NOOP
|
||||
NOOP,
|
||||
isMathMLTag
|
||||
} from '@vue/shared'
|
||||
|
||||
declare module '@vue/reactivity' {
|
||||
|
@ -99,7 +101,7 @@ export const createApp = ((...args) => {
|
|||
|
||||
// clear content before mounting
|
||||
container.innerHTML = ''
|
||||
const proxy = mount(container, false, container instanceof SVGElement)
|
||||
const proxy = mount(container, false, resolveRootNamespace(container))
|
||||
if (container instanceof Element) {
|
||||
container.removeAttribute('v-cloak')
|
||||
container.setAttribute('data-v-app', '')
|
||||
|
@ -122,18 +124,30 @@ export const createSSRApp = ((...args) => {
|
|||
app.mount = (containerOrSelector: Element | ShadowRoot | string): any => {
|
||||
const container = normalizeContainer(containerOrSelector)
|
||||
if (container) {
|
||||
return mount(container, true, container instanceof SVGElement)
|
||||
return mount(container, true, resolveRootNamespace(container))
|
||||
}
|
||||
}
|
||||
|
||||
return app
|
||||
}) as CreateAppFunction<Element>
|
||||
|
||||
function resolveRootNamespace(container: Element): ElementNamespace {
|
||||
if (container instanceof SVGElement) {
|
||||
return 'svg'
|
||||
}
|
||||
if (
|
||||
typeof MathMLElement === 'function' &&
|
||||
container instanceof MathMLElement
|
||||
) {
|
||||
return 'mathml'
|
||||
}
|
||||
}
|
||||
|
||||
function injectNativeTagCheck(app: App) {
|
||||
// Inject `isNativeTag`
|
||||
// this is used for component name validation (dev only)
|
||||
Object.defineProperty(app.config, 'isNativeTag', {
|
||||
value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag),
|
||||
value: (tag: string) => isHTMLTag(tag) || isSVGTag(tag) || isMathMLTag(tag),
|
||||
writable: false
|
||||
})
|
||||
}
|
||||
|
|
|
@ -46,7 +46,7 @@ type Booleanish = boolean | 'true' | 'false'
|
|||
type Numberish = number | string
|
||||
|
||||
// All the WAI-ARIA 1.1 attributes from https://www.w3.org/TR/wai-aria-1.1/
|
||||
interface AriaAttributes {
|
||||
export interface AriaAttributes {
|
||||
/** Identifies the currently active element when DOM focus is on a composite widget, textbox, group, or application. */
|
||||
'aria-activedescendant'?: string
|
||||
/** Indicates whether assistive technologies will present all, or only parts of, the changed region based on the change notifications defined by the aria-relevant attribute. */
|
||||
|
@ -1077,6 +1077,7 @@ export interface SVGAttributes extends AriaAttributes, EventHandlers<Events> {
|
|||
xlinkTitle?: string
|
||||
xlinkType?: string
|
||||
xmlns?: string
|
||||
xmlnsXlink?: string
|
||||
y1?: Numberish
|
||||
y2?: Numberish
|
||||
y?: Numberish
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { isString, hyphenate, capitalize, isArray } from '@vue/shared'
|
||||
import { camelize, warn } from '@vue/runtime-core'
|
||||
import { vShowOldKey } from '../directives/vShow'
|
||||
import { CSS_VAR_TEXT } from '../helpers/useCssVars'
|
||||
|
||||
type Style = string | Record<string, string | string[]> | null
|
||||
|
||||
|
@ -22,6 +23,11 @@ export function patchStyle(el: Element, prev: Style, next: Style) {
|
|||
const currentDisplay = style.display
|
||||
if (isCssString) {
|
||||
if (prev !== next) {
|
||||
// #9821
|
||||
const cssVarText = (style as any)[CSS_VAR_TEXT]
|
||||
if (cssVarText) {
|
||||
;(next as string) += ';' + cssVarText
|
||||
}
|
||||
style.cssText = next as string
|
||||
}
|
||||
} else if (prev) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { RendererOptions } from '@vue/runtime-core'
|
||||
|
||||
export const svgNS = 'http://www.w3.org/2000/svg'
|
||||
export const mathmlNS = 'http://www.w3.org/1998/Math/MathML'
|
||||
|
||||
const doc = (typeof document !== 'undefined' ? document : null) as Document
|
||||
|
||||
|
@ -18,9 +19,12 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
|
|||
}
|
||||
},
|
||||
|
||||
createElement: (tag, isSVG, is, props): Element => {
|
||||
const el = isSVG
|
||||
createElement: (tag, namespace, is, props): Element => {
|
||||
const el =
|
||||
namespace === 'svg'
|
||||
? doc.createElementNS(svgNS, tag)
|
||||
: namespace === 'mathml'
|
||||
? doc.createElementNS(mathmlNS, tag)
|
||||
: doc.createElement(tag, is ? { is } : undefined)
|
||||
|
||||
if (tag === 'select' && props && props.multiple != null) {
|
||||
|
@ -56,7 +60,7 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
|
|||
// Reason: innerHTML.
|
||||
// Static content here can only come from compiled templates.
|
||||
// As long as the user only uses trusted templates, this is safe.
|
||||
insertStaticContent(content, parent, anchor, isSVG, start, end) {
|
||||
insertStaticContent(content, parent, anchor, namespace, start, end) {
|
||||
// <parent> before | first ... last | anchor </parent>
|
||||
const before = anchor ? anchor.previousSibling : parent.lastChild
|
||||
// #5308 can only take cached path if:
|
||||
|
@ -70,10 +74,16 @@ export const nodeOps: Omit<RendererOptions<Node, Element>, 'patchProp'> = {
|
|||
}
|
||||
} else {
|
||||
// fresh insert
|
||||
templateContainer.innerHTML = isSVG ? `<svg>${content}</svg>` : content
|
||||
templateContainer.innerHTML =
|
||||
namespace === 'svg'
|
||||
? `<svg>${content}</svg>`
|
||||
: namespace === 'mathml'
|
||||
? `<math>${content}</math>`
|
||||
: content
|
||||
|
||||
const template = templateContainer.content
|
||||
if (isSVG) {
|
||||
// remove outer svg wrapper
|
||||
if (namespace === 'svg' || namespace === 'mathml') {
|
||||
// remove outer svg/math wrapper
|
||||
const wrapper = template.firstChild!
|
||||
while (wrapper.firstChild) {
|
||||
template.appendChild(wrapper.firstChild)
|
||||
|
|
|
@ -20,12 +20,13 @@ export const patchProp: DOMRendererOptions['patchProp'] = (
|
|||
key,
|
||||
prevValue,
|
||||
nextValue,
|
||||
isSVG = false,
|
||||
namespace,
|
||||
prevChildren,
|
||||
parentComponent,
|
||||
parentSuspense,
|
||||
unmountChildren
|
||||
) => {
|
||||
const isSVG = namespace === 'svg'
|
||||
if (key === 'class') {
|
||||
patchClass(el, nextValue, isSVG)
|
||||
} else if (key === 'style') {
|
||||
|
@ -110,15 +111,17 @@ function shouldSetAsProp(
|
|||
return false
|
||||
}
|
||||
|
||||
// #8780 the width or heigth of embedded tags must be set as attribute
|
||||
// #8780 the width or height of embedded tags must be set as attribute
|
||||
if (key === 'width' || key === 'height') {
|
||||
const tag = el.tagName
|
||||
return !(
|
||||
if (
|
||||
tag === 'IMG' ||
|
||||
tag === 'VIDEO' ||
|
||||
tag === 'CANVAS' ||
|
||||
tag === 'SOURCE'
|
||||
)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// native onclick with string value, must be set as attribute
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/server-renderer",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "@vue/server-renderer",
|
||||
"main": "index.js",
|
||||
"module": "dist/server-renderer.esm-bundler.js",
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
"vite": "^5.0.0"
|
||||
"vite": "^5.0.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vue/repl": "^3.0.0",
|
||||
|
|
|
@ -18,13 +18,15 @@ if (import.meta.env.DEV) {
|
|||
)
|
||||
}
|
||||
|
||||
const replRef = ref<InstanceType<typeof Repl>>()
|
||||
|
||||
const setVH = () => {
|
||||
document.documentElement.style.setProperty('--vh', window.innerHeight + `px`)
|
||||
}
|
||||
window.addEventListener('resize', setVH)
|
||||
setVH()
|
||||
|
||||
const useDevMode = ref(false)
|
||||
const useDevMode = ref(true)
|
||||
const useSSRMode = ref(false)
|
||||
|
||||
let hash = location.hash.slice(1)
|
||||
|
@ -56,8 +58,7 @@ const sfcOptions: SFCOptions = {
|
|||
script: {
|
||||
inlineTemplate: !useDevMode.value,
|
||||
isProd: !useDevMode.value,
|
||||
propsDestructure: true,
|
||||
defineModel: true
|
||||
propsDestructure: true
|
||||
},
|
||||
style: {
|
||||
isProd: !useDevMode.value
|
||||
|
@ -91,6 +92,10 @@ function toggleSSR() {
|
|||
store.setFiles(store.getFiles())
|
||||
}
|
||||
|
||||
function reloadPage() {
|
||||
replRef.value?.reload()
|
||||
}
|
||||
|
||||
const theme = ref<'dark' | 'light'>('dark')
|
||||
function toggleTheme(isDark: boolean) {
|
||||
theme.value = isDark ? 'dark' : 'light'
|
||||
|
@ -109,9 +114,11 @@ onMounted(() => {
|
|||
@toggle-theme="toggleTheme"
|
||||
@toggle-dev="toggleDevMode"
|
||||
@toggle-ssr="toggleSSR"
|
||||
@reload-page="reloadPage"
|
||||
/>
|
||||
<Repl
|
||||
v-if="EditorComponent"
|
||||
ref="replRef"
|
||||
:theme="theme"
|
||||
:editor="EditorComponent"
|
||||
@keydown.ctrl.s.prevent
|
||||
|
|
|
@ -6,6 +6,7 @@ import Moon from './icons/Moon.vue'
|
|||
import Share from './icons/Share.vue'
|
||||
import Download from './icons/Download.vue'
|
||||
import GitHub from './icons/GitHub.vue'
|
||||
import Reload from './icons/Reload.vue'
|
||||
import type { ReplStore } from '@vue/repl'
|
||||
import VersionSelect from './VersionSelect.vue'
|
||||
|
||||
|
@ -14,7 +15,12 @@ const props = defineProps<{
|
|||
dev: boolean
|
||||
ssr: boolean
|
||||
}>()
|
||||
const emit = defineEmits(['toggle-theme', 'toggle-ssr', 'toggle-dev'])
|
||||
const emit = defineEmits([
|
||||
'toggle-theme',
|
||||
'toggle-ssr',
|
||||
'toggle-dev',
|
||||
'reload-page'
|
||||
])
|
||||
|
||||
const { store } = props
|
||||
|
||||
|
@ -24,7 +30,7 @@ const vueVersion = ref(`@${currentCommit}`)
|
|||
async function setVueVersion(v: string) {
|
||||
vueVersion.value = `loading...`
|
||||
await store.setVueVersion(v)
|
||||
vueVersion.value = `v${v}`
|
||||
vueVersion.value = v
|
||||
}
|
||||
|
||||
function resetVueVersion() {
|
||||
|
@ -105,6 +111,9 @@ function toggleDark() {
|
|||
<button title="Copy sharable URL" class="share" @click="copyLink">
|
||||
<Share />
|
||||
</button>
|
||||
<button title="Reload page" class="reload" @click="$emit('reload-page')">
|
||||
<Reload />
|
||||
</button>
|
||||
<button
|
||||
title="Download project files"
|
||||
class="download"
|
||||
|
|
|
@ -74,8 +74,8 @@ onMounted(() => {
|
|||
|
||||
<ul class="versions" :class="{ expanded }">
|
||||
<li v-if="!versions"><a>loading versions...</a></li>
|
||||
<li v-for="version of versions">
|
||||
<a @click="setVersion(version)">v{{ version }}</a>
|
||||
<li v-for="ver of versions" :class="{ active: ver === version }">
|
||||
<a @click="setVersion(ver)">v{{ ver }}</a>
|
||||
</li>
|
||||
<div @click="expanded = false">
|
||||
<slot />
|
||||
|
@ -111,4 +111,8 @@ onMounted(() => {
|
|||
border-top: 6px solid #aaa;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.versions .active a {
|
||||
color: var(--green);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
<svg fill="currentColor" width="1.7em" height="1.7em" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/>
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
</svg>
|
||||
</template>
|
|
@ -10,7 +10,6 @@ export default defineConfig({
|
|||
plugins: [
|
||||
vue({
|
||||
script: {
|
||||
defineModel: true,
|
||||
fs: {
|
||||
fileExists: fs.existsSync,
|
||||
readFile: file => fs.readFileSync(file, 'utf-8')
|
||||
|
|
|
@ -171,4 +171,49 @@ describe('toDisplayString', () => {
|
|||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
//#9727
|
||||
test('Map with Symbol keys', () => {
|
||||
const m = new Map<any, any>([
|
||||
[Symbol(), 'foo'],
|
||||
[Symbol(), 'bar'],
|
||||
[Symbol('baz'), 'baz']
|
||||
])
|
||||
expect(toDisplayString(m)).toMatchInlineSnapshot(`
|
||||
"{
|
||||
"Map(3)": {
|
||||
"Symbol(0) =>": "foo",
|
||||
"Symbol(1) =>": "bar",
|
||||
"Symbol(baz) =>": "baz"
|
||||
}
|
||||
}"
|
||||
`)
|
||||
// confirming the symbol renders Symbol(foo)
|
||||
expect(toDisplayString(new Map([[Symbol('foo'), 'foo']]))).toContain(
|
||||
String(Symbol('foo'))
|
||||
)
|
||||
})
|
||||
|
||||
test('Set with Symbol values', () => {
|
||||
const s = new Set([Symbol('foo'), Symbol('bar'), Symbol()])
|
||||
expect(toDisplayString(s)).toMatchInlineSnapshot(`
|
||||
"{
|
||||
"Set(3)": [
|
||||
"Symbol(foo)",
|
||||
"Symbol(bar)",
|
||||
"Symbol()"
|
||||
]
|
||||
}"
|
||||
`)
|
||||
})
|
||||
|
||||
test('Object with Symbol values', () => {
|
||||
expect(toDisplayString({ foo: Symbol('x'), bar: Symbol() }))
|
||||
.toMatchInlineSnapshot(`
|
||||
"{
|
||||
"foo": "Symbol(x)",
|
||||
"bar": "Symbol()"
|
||||
}"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/shared",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "internal utils shared across @vue packages",
|
||||
"main": "index.js",
|
||||
"module": "dist/shared.esm-bundler.js",
|
||||
|
|
|
@ -118,6 +118,6 @@ export const isKnownSvgAttr = /*#__PURE__*/ makeMap(
|
|||
`v-mathematical,values,vector-effect,version,vert-adv-y,vert-origin-x,` +
|
||||
`vert-origin-y,viewBox,viewTarget,visibility,width,widths,word-spacing,` +
|
||||
`writing-mode,x,x-height,x1,x2,xChannelSelector,xlink:actuate,xlink:arcrole,` +
|
||||
`xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xml:base,xml:lang,` +
|
||||
`xlink:href,xlink:role,xlink:show,xlink:title,xlink:type,xmlns:xlink,xml:base,xml:lang,` +
|
||||
`xml:space,y,y1,y2,yChannelSelector,z,zoomAndPan`
|
||||
)
|
||||
|
|
|
@ -27,6 +27,14 @@ const SVG_TAGS =
|
|||
'polygon,polyline,radialGradient,rect,set,solidcolor,stop,switch,symbol,' +
|
||||
'text,textPath,title,tspan,unknown,use,view'
|
||||
|
||||
// https://www.w3.org/TR/mathml4/ (content elements excluded)
|
||||
const MATH_TAGS =
|
||||
'annotation,annotation-xml,maction,maligngroup,malignmark,math,menclose,' +
|
||||
'merror,mfenced,mfrac,mfraction,mglyph,mi,mlabeledtr,mlongdiv,' +
|
||||
'mmultiscripts,mn,mo,mover,mpadded,mphantom,mprescripts,mroot,mrow,ms,' +
|
||||
'mscarries,mscarry,msgroup,msline,mspace,msqrt,msrow,mstack,mstyle,msub,' +
|
||||
'msubsup,msup,mtable,mtd,mtext,mtr,munder,munderover,none,semantics'
|
||||
|
||||
const VOID_TAGS =
|
||||
'area,base,br,col,embed,hr,img,input,link,meta,param,source,track,wbr'
|
||||
|
||||
|
@ -40,6 +48,11 @@ export const isHTMLTag = /*#__PURE__*/ makeMap(HTML_TAGS)
|
|||
* Do NOT use in runtime code paths unless behind `__DEV__` flag.
|
||||
*/
|
||||
export const isSVGTag = /*#__PURE__*/ makeMap(SVG_TAGS)
|
||||
/**
|
||||
* Compiler only.
|
||||
* Do NOT use in runtime code paths unless behind `__DEV__` flag.
|
||||
*/
|
||||
export const isMathMLTag = /*#__PURE__*/ makeMap(MATH_TAGS)
|
||||
/**
|
||||
* Compiler only.
|
||||
* Do NOT use in runtime code paths unless behind `__DEV__` flag.
|
||||
|
|
|
@ -6,7 +6,8 @@ import {
|
|||
isPlainObject,
|
||||
isSet,
|
||||
objectToString,
|
||||
isString
|
||||
isString,
|
||||
isSymbol
|
||||
} from './general'
|
||||
|
||||
/**
|
||||
|
@ -31,17 +32,26 @@ const replacer = (_key: string, val: any): any => {
|
|||
return replacer(_key, val.value)
|
||||
} else if (isMap(val)) {
|
||||
return {
|
||||
[`Map(${val.size})`]: [...val.entries()].reduce((entries, [key, val]) => {
|
||||
;(entries as any)[`${key} =>`] = val
|
||||
[`Map(${val.size})`]: [...val.entries()].reduce(
|
||||
(entries, [key, val], i) => {
|
||||
entries[stringifySymbol(key, i) + ' =>'] = val
|
||||
return entries
|
||||
}, {})
|
||||
},
|
||||
{} as Record<string, any>
|
||||
)
|
||||
}
|
||||
} else if (isSet(val)) {
|
||||
return {
|
||||
[`Set(${val.size})`]: [...val.values()]
|
||||
[`Set(${val.size})`]: [...val.values()].map(v => stringifySymbol(v))
|
||||
}
|
||||
} else if (isSymbol(val)) {
|
||||
return stringifySymbol(val)
|
||||
} else if (isObject(val) && !isArray(val) && !isPlainObject(val)) {
|
||||
// native elements
|
||||
return String(val)
|
||||
}
|
||||
return val
|
||||
}
|
||||
|
||||
const stringifySymbol = (v: unknown, i: number | string = ''): any =>
|
||||
isSymbol(v) ? `Symbol(${v.description ?? i})` : v
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@vue/compiler-vapor": "workspace:^",
|
||||
"monaco-editor": "^0.44.0",
|
||||
"monaco-editor": "^0.45.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "@vue/compat",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "Vue 3 compatibility build for Vue 2",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
|
@ -37,6 +37,7 @@ Starting with 3.0.0-rc.3, `esm-bundler` builds now exposes global feature flags
|
|||
|
||||
- `__VUE_OPTIONS_API__` (enable/disable Options API support, default: `true`)
|
||||
- `__VUE_PROD_DEVTOOLS__` (enable/disable devtools support in production, default: `false`)
|
||||
- `__VUE_PROD_HYDRATION_MISMATCH_DETAILS__` (enable/disable detailed warnings for hydration mismatches in production, default: `false`)
|
||||
|
||||
The build will work without configuring these flags, however it is **strongly recommended** to properly configure them in order to get proper tree-shaking in the final bundle. To configure these flags:
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
// MathML logic is technically dom-specific, but the logic is placed in core
|
||||
// because splitting it out of core would lead to unnecessary complexity in both
|
||||
// the renderer and compiler implementations.
|
||||
// Related files:
|
||||
// - runtime-core/src/renderer.ts
|
||||
// - compiler-core/src/transforms/transformElement.ts
|
||||
|
||||
import { vtcKey } from '../../runtime-dom/src/components/Transition'
|
||||
import { render, h, ref, nextTick } from '../src'
|
||||
|
||||
describe('MathML support', () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
test('should mount elements with correct html namespace', () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const App = {
|
||||
template: `
|
||||
<math display="block" id="e0">
|
||||
<semantics id="e1">
|
||||
<mrow id="e2">
|
||||
<msup>
|
||||
<mi>x</mi>
|
||||
<mn>2</mn>
|
||||
</msup>
|
||||
<mo>+</mo>
|
||||
<mi>y</mi>
|
||||
</mrow>
|
||||
|
||||
<annotation-xml encoding="text/html" id="e3">
|
||||
<div id="e4" />
|
||||
<svg id="e5" />
|
||||
</annotation-xml>
|
||||
</semantics>
|
||||
</math>
|
||||
`
|
||||
}
|
||||
render(h(App), root)
|
||||
const e0 = document.getElementById('e0')!
|
||||
expect(e0.namespaceURI).toMatch('Math')
|
||||
expect(e0.querySelector('#e1')!.namespaceURI).toMatch('Math')
|
||||
expect(e0.querySelector('#e2')!.namespaceURI).toMatch('Math')
|
||||
expect(e0.querySelector('#e3')!.namespaceURI).toMatch('Math')
|
||||
expect(e0.querySelector('#e4')!.namespaceURI).toMatch('xhtml')
|
||||
expect(e0.querySelector('#e5')!.namespaceURI).toMatch('svg')
|
||||
})
|
||||
|
||||
test('should patch elements with correct namespaces', async () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const cls = ref('foo')
|
||||
const App = {
|
||||
setup: () => ({ cls }),
|
||||
template: `
|
||||
<div>
|
||||
<math id="f1" :class="cls">
|
||||
<annotation encoding="text/html">
|
||||
<div id="f2" :class="cls"/>
|
||||
</annotation>
|
||||
</math>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
render(h(App), root)
|
||||
const f1 = document.querySelector('#f1')!
|
||||
const f2 = document.querySelector('#f2')!
|
||||
expect(f1.getAttribute('class')).toBe('foo')
|
||||
expect(f2.className).toBe('foo')
|
||||
|
||||
// set a transition class on the <div> - which is only respected on non-svg
|
||||
// patches
|
||||
;(f2 as any)[vtcKey] = ['baz']
|
||||
cls.value = 'bar'
|
||||
await nextTick()
|
||||
expect(f1.getAttribute('class')).toBe('bar')
|
||||
expect(f2.className).toBe('bar baz')
|
||||
})
|
||||
})
|
|
@ -9,7 +9,11 @@ import { vtcKey } from '../../runtime-dom/src/components/Transition'
|
|||
import { render, h, ref, nextTick } from '../src'
|
||||
|
||||
describe('SVG support', () => {
|
||||
test('should mount elements with correct namespaces', () => {
|
||||
afterEach(() => {
|
||||
document.body.innerHTML = ''
|
||||
})
|
||||
|
||||
test('should mount elements with correct html namespace', () => {
|
||||
const root = document.createElement('div')
|
||||
document.body.appendChild(root)
|
||||
const App = {
|
||||
|
@ -18,6 +22,8 @@ describe('SVG support', () => {
|
|||
<svg id="e1">
|
||||
<foreignObject id="e2">
|
||||
<div id="e3"/>
|
||||
<svg id="e4"/>
|
||||
<math id="e5"/>
|
||||
</foreignObject>
|
||||
</svg>
|
||||
</div>
|
||||
|
@ -29,6 +35,8 @@ describe('SVG support', () => {
|
|||
expect(e0.querySelector('#e1')!.namespaceURI).toMatch('svg')
|
||||
expect(e0.querySelector('#e2')!.namespaceURI).toMatch('svg')
|
||||
expect(e0.querySelector('#e3')!.namespaceURI).toMatch('xhtml')
|
||||
expect(e0.querySelector('#e4')!.namespaceURI).toMatch('svg')
|
||||
expect(e0.querySelector('#e5')!.namespaceURI).toMatch('Math')
|
||||
})
|
||||
|
||||
test('should patch elements with correct namespaces', async () => {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "vue",
|
||||
"version": "3.4.0-alpha.4",
|
||||
"version": "3.4.0-beta.3",
|
||||
"description": "The progressive JavaScript framework for building modern web UI.",
|
||||
"main": "index.js",
|
||||
"module": "dist/vue.runtime.esm-bundler.js",
|
||||
|
|
|
@ -1,4 +0,0 @@
|
|||
// this is appended to the end of ../dist/vue.d.ts during build.
|
||||
// imports the global JSX namespace registration for compat.
|
||||
// TODO: remove in 3.4
|
||||
import '../jsx'
|
600
pnpm-lock.yaml
600
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