Merge branch 'main' into feat/reactiveity-transform-shorthands

This commit is contained in:
Anthony Fu 2022-07-05 13:53:29 +08:00 committed by GitHub
commit fbbaa0cc3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
108 changed files with 2231 additions and 669 deletions

View File

@ -6,6 +6,7 @@ module.exports = {
parserOptions: {
sourceType: 'module'
},
plugins: ["jest"],
rules: {
'no-debugger': 'error',
'no-unused-vars': [
@ -32,7 +33,9 @@ module.exports = {
files: ['**/__tests__/**', 'test-dts/**'],
rules: {
'no-restricted-globals': 'off',
'no-restricted-syntax': 'off'
'no-restricted-syntax': 'off',
'jest/no-disabled-tests': 'error',
'jest/no-focused-tests': 'error'
}
},
// shared, may be used in any env

View File

@ -17,6 +17,12 @@ body:
Also try to search for your issue - it may have already been answered or even fixed in the development branch.
However, if you find that an old, closed issue still persists in the latest version,
you should open a new issue using the form below instead of commenting on the old issue.
- type: input
id: version
attributes:
label: Vue version
validations:
required: true
- type: input
id: reproduction-link
attributes:

View File

@ -19,6 +19,8 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before
- Checkout a topic branch from a base branch, e.g. `main`, and merge back against that branch.
- [Make sure to tick the "Allow edits from maintainers" box](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork). This allows us to directly make minor edits / refactors and saves a lot of time.
- If adding a new feature:
- Add accompanying test case.

View File

@ -62,3 +62,9 @@ updates:
- dependency-name: node-notifier
versions:
- 8.0.1
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: monthly
open-pull-requests-limit: 10
versioning-strategy: lockfile-only

View File

@ -13,9 +13,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install pnpm
uses: pnpm/action-setup@v2.0.1
with:
version: 7.0.1
uses: pnpm/action-setup@v2
- name: Set node version to 16
uses: actions/setup-node@v2
@ -34,9 +32,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install pnpm
uses: pnpm/action-setup@v2.0.1
with:
version: 7.0.1
uses: pnpm/action-setup@v2
- name: Set node version to 16
uses: actions/setup-node@v2
@ -55,9 +51,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install pnpm
uses: pnpm/action-setup@v2.0.1
with:
version: 7.0.1
uses: pnpm/action-setup@v2
- name: Set node version to 16
uses: actions/setup-node@v2
@ -81,9 +75,7 @@ jobs:
- uses: actions/checkout@v2
- name: Install pnpm
uses: pnpm/action-setup@v2.0.1
with:
version: 7.0.1
uses: pnpm/action-setup@v2
- name: Set node version to 16
uses: actions/setup-node@v2

View File

@ -1,3 +1,138 @@
## [3.2.37](https://github.com/vuejs/core/compare/v3.2.36...v3.2.37) (2022-06-06)
### Bug Fixes
* **compiler-sfc:** improve css v-bind parsing ([e60244b](https://github.com/vuejs/core/commit/e60244bcdf0b386de1560ff7c205ae0870bab355)), closes [#6022](https://github.com/vuejs/core/issues/6022)
* **runtime-core:** hydrate Static vnode ([#6015](https://github.com/vuejs/core/issues/6015)) ([11e17a1](https://github.com/vuejs/core/commit/11e17a1a29cf3d0b37628241d63ff3e8d8525f95)), closes [#6008](https://github.com/vuejs/core/issues/6008)
* **sfc:** avoid auto name inference leading to unwanted recursion ([9734b31](https://github.com/vuejs/core/commit/9734b31c312244a2b5c5cf83c75d7b34076a0c4b)), closes [#5965](https://github.com/vuejs/core/issues/5965) [#6027](https://github.com/vuejs/core/issues/6027) [#6029](https://github.com/vuejs/core/issues/6029)
* **ssr:** ensure app can be unmounted when created with createSSRApp() ([#5992](https://github.com/vuejs/core/issues/5992)) ([d4d3319](https://github.com/vuejs/core/commit/d4d3319c1be16dc9a046b2c5521096debc205f25)), closes [#5990](https://github.com/vuejs/core/issues/5990)
* **ssr:** hydration for transition wrapper components with empty slot content ([#5995](https://github.com/vuejs/core/issues/5995)) ([eb22a62](https://github.com/vuejs/core/commit/eb22a62798d845a8756b0a73b68afdd874feda59)), closes [#5991](https://github.com/vuejs/core/issues/5991)
## [3.2.36](https://github.com/vuejs/core/compare/v3.2.35...v3.2.36) (2022-05-23)
### Bug Fixes
* **compat:** fix app-level asset registration affecting other local apps ([#5979](https://github.com/vuejs/core/issues/5979)) ([7fb5732](https://github.com/vuejs/core/commit/7fb57327b9d0e4d9eb675149f167d915fb0d59fa))
* **compat:** fix globalProperties pollution in v3 mode ([2f07e34](https://github.com/vuejs/core/commit/2f07e3460bf51bc1b083f3d03b3d192e042d2d75)), closes [#5699](https://github.com/vuejs/core/issues/5699)
* **compiler-core:** fix svg with directives being incorrectly hoisted ([#5919](https://github.com/vuejs/core/issues/5919)) ([7fbc933](https://github.com/vuejs/core/commit/7fbc933f4d80c0259ee24872ba790681cf3cbe76)), closes [#5289](https://github.com/vuejs/core/issues/5289)
* **sfc/types:** allow use default factory for primitive types in `withDefaults` ([#5939](https://github.com/vuejs/core/issues/5939)) ([b546282](https://github.com/vuejs/core/commit/b5462822d6c0a43866deef2b3437bbe3bbfb3625)), closes [#5938](https://github.com/vuejs/core/issues/5938)
* **transition:** fix cancel leave regression ([#5974](https://github.com/vuejs/core/issues/5974)) ([dddbd96](https://github.com/vuejs/core/commit/dddbd96dfe69292cee401f72d2703e8fb3708a14)), closes [#5973](https://github.com/vuejs/core/issues/5973)
### Performance Improvements
* improve the performance of getNow ([#5944](https://github.com/vuejs/core/issues/5944)) ([3bdc41d](https://github.com/vuejs/core/commit/3bdc41dff305422cb5334a64353c314bce1202a4))
## [3.2.35](https://github.com/vuejs/core/compare/v3.2.34...v3.2.35) (2022-05-20)
### Bug Fixes
* **compiler-sfc:** fix usage detection for types in v-for/v-slot expressions ([583b625](https://github.com/vuejs/core/commit/583b6259870211c32efee0bb4a60b342799d80f7)), closes [#5959](https://github.com/vuejs/core/issues/5959)
* **types:** fix typescript error when spreading `$props`([#5968](https://github.com/vuejs/core/issues/5968)) ([0c7fd13](https://github.com/vuejs/core/commit/0c7fd13ea628a2f1b72c6f4150c0dba32da4468e))
* **types:** restore DefineComponent argument order ([8071ef4](https://github.com/vuejs/core/commit/8071ef47b5adcd5fcd9d0d2ea2cefff5c34ce095)), closes [#5416](https://github.com/vuejs/core/issues/5416) [#3796](https://github.com/vuejs/core/issues/3796) [#5967](https://github.com/vuejs/core/issues/5967)
## [3.2.34](https://github.com/vuejs/core/compare/v3.2.34-beta.1...v3.2.34) (2022-05-19)
### Bug Fixes
* **compiler-core:** should generate HYDRATE_EVENTS flag on dynamic component that resolves to element ([415091b](https://github.com/vuejs/core/commit/415091b0ee2de66e622145028f00523f2032ce77)), closes [#5870](https://github.com/vuejs/core/issues/5870)
* **compiler-sfc:** support `export { default } from '...'` ([#5937](https://github.com/vuejs/core/issues/5937)) ([73e6523](https://github.com/vuejs/core/commit/73e6523134a013f9e369f53f213a214497ac7c40)), closes [#5935](https://github.com/vuejs/core/issues/5935)
* **compiler-sfc:** type-only defineProps does not recognize Promise (fix [#5941](https://github.com/vuejs/core/issues/5941)) ([#5943](https://github.com/vuejs/core/issues/5943)) ([991d623](https://github.com/vuejs/core/commit/991d62322fa67d50b7ae8b0460f294d6b39f9711))
* **compiler-ssr:** fix component event handlers inheritance in ssr ([f811dc2](https://github.com/vuejs/core/commit/f811dc2b60ba7efdbb9b1ab330dcbc18c1cc9a75)), closes [#5664](https://github.com/vuejs/core/issues/5664)
* **compiler-ssr:** fix wrong attrs fallthrough on non-single-root v-if branches ([516bc54](https://github.com/vuejs/core/commit/516bc548fce295f6d564c7fb371c8067ead7cd71)), closes [#5140](https://github.com/vuejs/core/issues/5140)
* **compiler-ssr:** only inject fallthrough attrs for root transition/keep-alive ([c65b805](https://github.com/vuejs/core/commit/c65b805ef1f9b164fb8aaa7bc679a91248b5891a))
* **keep-alive:** fix keep-alive rendering when placed in vnode branch ([0841b9b](https://github.com/vuejs/core/commit/0841b9b5243acdaf191099b25e9a145b30189dea)), closes [#4817](https://github.com/vuejs/core/issues/4817)
* **runtime-core:** adjust force diff of dev root fragments ([cdda49b](https://github.com/vuejs/core/commit/cdda49bbfb1939c9cf812d624992ea7bdae74c78)), closes [#5946](https://github.com/vuejs/core/issues/5946)
* **ssr/teleport:** support nested teleports in ssr ([595263c](https://github.com/vuejs/core/commit/595263c0e9f5728c3650c6526dbed27cda9ba114)), closes [#5242](https://github.com/vuejs/core/issues/5242)
* **ssr:** fix hydration error on falsy v-if inside transition/keep-alive ([ee4186e](https://github.com/vuejs/core/commit/ee4186ef9ebbc45827b208f6f5b648dbf4337d1d)), closes [#5352](https://github.com/vuejs/core/issues/5352)
* **ssr:** fix hydration error when teleport is used as component root ([b60cff0](https://github.com/vuejs/core/commit/b60cff052c880b2965d06007f0ec4d0349ab47c0)), closes [#4293](https://github.com/vuejs/core/issues/4293)
* **ssr:** fix hydration error when transition contains comment children ([3705b3b](https://github.com/vuejs/core/commit/3705b3b46aa8f3e929014f564f8afa4a663e6375)), closes [#5351](https://github.com/vuejs/core/issues/5351)
* **ssr:** fix hydration for slot with empty text node ([939209c](https://github.com/vuejs/core/commit/939209c6b554aed6634d9cf2ca10a2aa46ba7673)), closes [#5728](https://github.com/vuejs/core/issues/5728)
* **ssr:** fix hydration mismatch caused by multi-line comments inside slot ([e1bc268](https://github.com/vuejs/core/commit/e1bc2681ef64aed7975ad38950a478ae53c1abad)), closes [#5355](https://github.com/vuejs/core/issues/5355)
* **ssr:** inherit scope id on functional component during ssr ([847d7f7](https://github.com/vuejs/core/commit/847d7f782bb6074c6b31378e07d94cb41ad30bd2)), closes [#5817](https://github.com/vuejs/core/issues/5817)
* **ssr:** render fallthrough attributes for transition-group with tag ([aed10c5](https://github.com/vuejs/core/commit/aed10c507279900f8afc4861dc01ca4f2b95acb8)), closes [#5141](https://github.com/vuejs/core/issues/5141)
* **ssr:** support client-compiled v-model with dynamic type during ssr ([#5787](https://github.com/vuejs/core/issues/5787)) ([c03459b](https://github.com/vuejs/core/commit/c03459b9b6d3c18450235bc4074a603677996320)), closes [#5786](https://github.com/vuejs/core/issues/5786)
* **types:** export ComponentProvideOptions ([#5947](https://github.com/vuejs/core/issues/5947)) ([3e2850f](https://github.com/vuejs/core/commit/3e2850fa6c628284b4a1ab5deba3b11f1d2f66b6))
* **types:** fix `defineComponent` inference to `Component` ([#5949](https://github.com/vuejs/core/issues/5949)) ([7c8f457](https://github.com/vuejs/core/commit/7c8f4578e9e7178e326cf8e343f7a8b4143ba63b))
## [3.2.34-beta.1](https://github.com/vuejs/core/compare/v3.2.33...v3.2.34-beta.1) (2022-05-17)
### Bug Fixes
* **compiler-core:** normalize v-bind:style with array literal value ([0f00cf4](https://github.com/vuejs/core/commit/0f00cf43cf5eaeeee7f12d523a5f4936f7dc0a84)), closes [#5106](https://github.com/vuejs/core/issues/5106)
* **compiler-core:** template v-if should never be treated as dev root fragment ([51f3d38](https://github.com/vuejs/core/commit/51f3d386de7f5fcec6eb4c1c223ba824be036648)), closes [#5189](https://github.com/vuejs/core/issues/5189)
* **compiler-dom:** properly stringify v-html/v-text with constant value ([6283b2e](https://github.com/vuejs/core/commit/6283b2ec413b78fe88775d249d3598cdce977b7a)), closes [#5439](https://github.com/vuejs/core/issues/5439) [#5445](https://github.com/vuejs/core/issues/5445)
* **compiler-sfc:** `<script>` after `<script setup>` the script content not end with `\\n` ([3b7b107](https://github.com/vuejs/core/commit/3b7b107120c6dba70b068312afd594c3575ea9eb))
* **compiler-sfc:** add test for [#5808](https://github.com/vuejs/core/issues/5808) ([a0290fe](https://github.com/vuejs/core/commit/a0290fe781a0ab2e90239d615d18fdb7ee37cdfe))
* **compiler-sfc:** async transformer doesn't correctly detect need for semicolon in block [#5808](https://github.com/vuejs/core/issues/5808) ([6c3b681](https://github.com/vuejs/core/commit/6c3b681d235bc70293827c535572f90be1ab6c68))
* **compiler-sfc:** automatically infer component name from filename when using script setup ([#4997](https://github.com/vuejs/core/issues/4997)) ([1693924](https://github.com/vuejs/core/commit/16939241b0f64cefea254b024a9b5a25caea93d9)), closes [#4993](https://github.com/vuejs/core/issues/4993)
* **compiler-sfc:** defineProps return binding or rest binding should be considered reactive ([4101441](https://github.com/vuejs/core/commit/410144149fbaaecad2b2d36a9cfe965ab7b2b6e6))
* **compiler-sfc:** ensure consistent behavior of export default render with script setup ([b7025d2](https://github.com/vuejs/core/commit/b7025d24f1c33023d020d60292319740852d1810)), closes [#4980](https://github.com/vuejs/core/issues/4980)
* **compiler-sfc:** fix defineEmits() scope reference check error message ([#5404](https://github.com/vuejs/core/issues/5404)) ([f2c48f5](https://github.com/vuejs/core/commit/f2c48f535250d01133a5e49c0bf2c26a46b6d935))
* **compiler-sfc:** fix object default values for reactive props destructure ([7dfe146](https://github.com/vuejs/core/commit/7dfe146096487a98ba1733598c44407bc89a1b9f))
* **compiler-sfc:** fix skipped srcset transform when using base option ([41d255b](https://github.com/vuejs/core/commit/41d255ba5ddd40f1a1dd2dd6add01f38e20d6325)), closes [#4835](https://github.com/vuejs/core/issues/4835) [#4819](https://github.com/vuejs/core/issues/4819) [#4834](https://github.com/vuejs/core/issues/4834) [#4835](https://github.com/vuejs/core/issues/4835)
* **compiler-sfc:** fix template usage check false positives on types ([ccf9256](https://github.com/vuejs/core/commit/ccf92564d339cdee3947aecfba2e861c88864883)), closes [#5414](https://github.com/vuejs/core/issues/5414)
* **compiler-sfc:** fix treeshaking of namespace import when used in template ([8a123ac](https://github.com/vuejs/core/commit/8a123ac34fd30fc36ac9e0c252dd345d6d7c7f50)), closes [#5209](https://github.com/vuejs/core/issues/5209)
* **compiler-sfc:** remove the jsx from the babelParserPlugins when not match the case of adding jsx ([#5846](https://github.com/vuejs/core/issues/5846)) ([7d7a241](https://github.com/vuejs/core/commit/7d7a2410e58d3ae59ca3fcf619f332699209fc96)), closes [#5845](https://github.com/vuejs/core/issues/5845)
* **keep-alive:** fix unmounting late-included components ([da49c86](https://github.com/vuejs/core/commit/da49c863a21fb9d9de4a1b816dcc4faff8046fdb)), closes [#3648](https://github.com/vuejs/core/issues/3648) [#3650](https://github.com/vuejs/core/issues/3650)
* **keep-alive:** invoke initial activated hook for async components ([20ed16f](https://github.com/vuejs/core/commit/20ed16f68c632a271d4e8cb09087c7ed4e936637)), closes [#5459](https://github.com/vuejs/core/issues/5459) [#5095](https://github.com/vuejs/core/issues/5095) [#5651](https://github.com/vuejs/core/issues/5651)
* **reactivity-transform:** fix props access codegen for non-identifier prop names ([#5436](https://github.com/vuejs/core/issues/5436)) ([242914d](https://github.com/vuejs/core/commit/242914d938fccad1c09a52a7ed09f15ac509cf6b)), closes [#5425](https://github.com/vuejs/core/issues/5425)
* **reactivity:** ensure computed is invalidated before other effects ([82bdf86](https://github.com/vuejs/core/commit/82bdf8625475e81c44f0d4db4061b882d2fe7612)), closes [#5720](https://github.com/vuejs/core/issues/5720)
* **reactivity:** ios10.x compatibility ([#4900](https://github.com/vuejs/core/issues/4900)) ([b87dc06](https://github.com/vuejs/core/commit/b87dc061930e62c78b4cc9a79f385e5880cdf8ae))
* **runtime-core:** clone root vnode before inheriting directives ([d36ca4d](https://github.com/vuejs/core/commit/d36ca4d80e297056f6c78cd7e3bc2004e0b58660))
* **runtime-core:** ensure consistent behavior between dev/prod on invalid v-for range ([67099fe](https://github.com/vuejs/core/commit/67099fe20299a51f9974f0e2f9ef19ca05efe92b)), closes [#5867](https://github.com/vuejs/core/issues/5867)
* **runtime-core:** ensure consistent identity of $forceUpdate and $nextTick instance methods ([d52907f](https://github.com/vuejs/core/commit/d52907f4ebc9bcfd7b3141fbebecd4c5b19aa80f)), closes [#5556](https://github.com/vuejs/core/issues/5556)
* **runtime-core:** ensure raw slot function is only normalized once ([#5358](https://github.com/vuejs/core/issues/5358)) ([e4dffe9](https://github.com/vuejs/core/commit/e4dffe900a7475c9b4c22c06283b5635e5c2de37)), closes [#5343](https://github.com/vuejs/core/issues/5343)
* **runtime-core:** fix activated hook when using async component with KeepAlive ([#5459](https://github.com/vuejs/core/issues/5459)) ([f1d1cdb](https://github.com/vuejs/core/commit/f1d1cdbb699e27ac6a0d241209f7e8f8d9ebf2c7)), closes [#5095](https://github.com/vuejs/core/issues/5095) [#5651](https://github.com/vuejs/core/issues/5651)
* **runtime-core:** fix directive inheritance on dev root fragment ([2bab639](https://github.com/vuejs/core/commit/2bab63968355995a4026cf5cd1ccad7c79e8d89c)), closes [#5523](https://github.com/vuejs/core/issues/5523)
* **runtime-core:** fix missed updates when passing vnode to <component :is> ([ba17792](https://github.com/vuejs/core/commit/ba17792b7248552ea01a1ca6eca7d49b47d827f0)), closes [#4903](https://github.com/vuejs/core/issues/4903)
* **runtime-core:** handle NaN identity check in v-memo ([#5852](https://github.com/vuejs/core/issues/5852)) ([a388129](https://github.com/vuejs/core/commit/a3881299e98de6217f05bbd42ac509e8cc8be3d9)), closes [#5853](https://github.com/vuejs/core/issues/5853)
* **runtime-core:** transition hooks can be arrays of functions ([#5177](https://github.com/vuejs/core/issues/5177)) ([fec12d7](https://github.com/vuejs/core/commit/fec12d7dccc573c016ee2d8e1fa2b67f134c2786))
* **runtime-dom:** "el._assign is not a function" in compat mode ([#4121](https://github.com/vuejs/core/issues/4121)) ([e58277f](https://github.com/vuejs/core/commit/e58277f6eaeaec84cf05b34126bec01b619a1b90))
* **sfc-playground:** default selected app ([#5370](https://github.com/vuejs/core/issues/5370)) ([04fff05](https://github.com/vuejs/core/commit/04fff05f0043adb753928e806f9d8fb604701a38))
* **shared:** improve isDate check ([#5803](https://github.com/vuejs/core/issues/5803)) ([eef1447](https://github.com/vuejs/core/commit/eef14471b228e8f3d3c921a0451d268f81a0a74d))
* **shared:** missed Symbol judge in looseEqual ([#3553](https://github.com/vuejs/core/issues/3553)) ([0aeb4bc](https://github.com/vuejs/core/commit/0aeb4bc9bf68c0006b496142bb5aeb3f06689b7c))
* **ssr/sfc-css-vars:** fix v-bind css vars codegen for SSR ([efea4a8](https://github.com/vuejs/core/commit/efea4a8b5784f4660836b124d16dc29f7fea41e4)), closes [#5443](https://github.com/vuejs/core/issues/5443) [#5444](https://github.com/vuejs/core/issues/5444)
* **ssr:** don't warn for missing teleport target if disabled ([#5135](https://github.com/vuejs/core/issues/5135)) ([da10dd7](https://github.com/vuejs/core/commit/da10dd7de91123666152d058f3c18da3e9e7f22a))
* **ssr:** fix hydration error for slot outlet inside transition ([9309b04](https://github.com/vuejs/core/commit/9309b044bd4f9d0a34e0d702ed4690a529443a41)), closes [#3989](https://github.com/vuejs/core/issues/3989)
* **ssr:** fix ssr render output for fragment in slots ([70c2d5b](https://github.com/vuejs/core/commit/70c2d5bbc066585febd12536a6ab39c4384b277b)), closes [#5859](https://github.com/vuejs/core/issues/5859)
* **ssr:** implement empty read() on node stream ([c355c4b](https://github.com/vuejs/core/commit/c355c4b78451708b04e17a7c50680dee507f0c40)), closes [#3846](https://github.com/vuejs/core/issues/3846) [#3867](https://github.com/vuejs/core/issues/3867)
* **ssr:** render teleport inside async component ([#5187](https://github.com/vuejs/core/issues/5187)) ([4d7803e](https://github.com/vuejs/core/commit/4d7803ed28fb67d45a83d3500f5407754e65bf64))
* **ssr:** resolve teleports for stream render APIs ([77fef97](https://github.com/vuejs/core/commit/77fef9734496abe1e842d8e4086c497656d1fab2))
* **ssr:** should de-optimize on vnode with PatchFlags.BAIL ([#4818](https://github.com/vuejs/core/issues/4818)) ([cd659fc](https://github.com/vuejs/core/commit/cd659fc86f74741987bacc351f0d94ef3b80a1ca)), closes [#4679](https://github.com/vuejs/core/issues/4679) [#5771](https://github.com/vuejs/core/issues/5771)
* **ssr:** should not hoist transformed asset urls in ssr compile ([57bb37b](https://github.com/vuejs/core/commit/57bb37bd64cc6b2aa3099d91585441db6d43e2a2)), closes [#3874](https://github.com/vuejs/core/issues/3874)
* **transition/v-show:** ensure transition is in persisted mode when used with v-show ([425310e](https://github.com/vuejs/core/commit/425310e8b614ad91660dd93d4e7905fbe572ef31)), closes [#4845](https://github.com/vuejs/core/issues/4845) [#4852](https://github.com/vuejs/core/issues/4852)
* **transition:** handle edge case of cancel leave before next frame ([59cf295](https://github.com/vuejs/core/commit/59cf2958e7bae5f1e13953373fcdbb4ad988be6c)), closes [#4462](https://github.com/vuejs/core/issues/4462)
* **types:** add `Set<any>` to checkbox binding type for v-model ([#5713](https://github.com/vuejs/core/issues/5713)) ([e5a9089](https://github.com/vuejs/core/commit/e5a90893a6a25a40464cc9d5484093ec531e8b78))
* **types:** allow css variables in style binding ([#5542](https://github.com/vuejs/core/issues/5542)) ([9def7aa](https://github.com/vuejs/core/commit/9def7aa50827b12fdfbef28e5db6f373d5cba279))
* **types:** allow indeterminate for checkbox ([#3473](https://github.com/vuejs/core/issues/3473)) ([d4fcfdd](https://github.com/vuejs/core/commit/d4fcfddec6170cc1528a285fc8e0b3f3c5590311))
* **types:** keep the original type when unwrapping `markRaw` ([#3791](https://github.com/vuejs/core/issues/3791)) ([32e53bf](https://github.com/vuejs/core/commit/32e53bfd478af895dd090ea6c8766fa043e179e4))
* **types:** preserve and expose original options on defineComponent return type ([#5416](https://github.com/vuejs/core/issues/5416)) ([98b821d](https://github.com/vuejs/core/commit/98b821d94a0a0fb4d7701809da6bec331a47e6e5)), closes [#3796](https://github.com/vuejs/core/issues/3796)
* **v-model:** exclude range from lazy guard logic ([8c51c65](https://github.com/vuejs/core/commit/8c51c6514f99b4c70183f4c1a0eaabd482f88d5b)), closes [#5875](https://github.com/vuejs/core/issues/5875)
* **v-model:** fix case where .trim and .number modifiers are used together ([#5842](https://github.com/vuejs/core/issues/5842)) ([71066b5](https://github.com/vuejs/core/commit/71066b5afed7d3707b8ec9a6313dbdbd1adad45e)), closes [#5839](https://github.com/vuejs/core/issues/5839)
* **watch:** fix flush: pre watchers triggered synchronously in setup ([74d2a76](https://github.com/vuejs/core/commit/74d2a76af6e830af5abb8aac8484dc1b3e90a510)), closes [#5721](https://github.com/vuejs/core/issues/5721)
* **watch:** fix watching multiple sources containing shallowRef ([#5381](https://github.com/vuejs/core/issues/5381)) ([220f255](https://github.com/vuejs/core/commit/220f255fe94d5f1d2ccf3af3a469e9328c916167)), closes [#5371](https://github.com/vuejs/core/issues/5371)
### Features
* **types:** avoid props JSDocs loss by `default` option ([#5871](https://github.com/vuejs/core/issues/5871)) ([c901dca](https://github.com/vuejs/core/commit/c901dca5add2c32554403e5896247fdb8aa7cf7d))
## [3.2.33](https://github.com/vuejs/core/compare/v3.2.32...v3.2.33) (2022-04-14)

View File

@ -1,13 +1,14 @@
{
"private": true,
"version": "3.2.33",
"version": "3.2.37",
"packageManager": "pnpm@7.1.0",
"scripts": {
"dev": "node scripts/dev.js",
"build": "node scripts/build.js",
"size": "run-s size-global size-baseline",
"size-global": "node scripts/build.js vue runtime-dom -f global -p",
"size-baseline": "node scripts/build.js runtime-dom runtime-core reactivity shared -f esm-bundler && cd packages/size-check && vite build && node brotli",
"lint": "eslint --ext .ts packages/*/src/**.ts",
"lint": "eslint --ext .ts packages/*/{src,__tests__}/**.ts",
"format": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"",
"test": "run-s \"test-unit {@}\" \"test-e2e {@}\"",
"test-unit": "jest --filter ./scripts/filter-unit.js",
@ -17,13 +18,14 @@
"test-coverage": "node scripts/build.js vue -f global -d && jest --runInBand --coverage --bail",
"release": "node scripts/release.js",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s",
"dev-esm": "node scripts/dev.js -if esm-bundler-runtime",
"dev-compiler": "run-p \"dev template-explorer\" serve",
"dev-sfc": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime \" serve-sfc-playground",
"dev-sfc": "run-p \"dev compiler-sfc -f esm-browser\" \"dev vue -if esm-bundler-runtime\" \"dev server-renderer -if esm-bundler\" serve-sfc-playground",
"serve-sfc-playground": "vite packages/sfc-playground --host",
"serve": "serve",
"open": "open http://localhost:5000/packages/template-explorer/local.html",
"preinstall": "node ./scripts/preinstall.js",
"prebuild-sfc-playground": "node scripts/build.js compiler reactivity-transform shared -af cjs && node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime && node scripts/build.js compiler-sfc -f esm-browser",
"prebuild-sfc-playground": "node scripts/build.js compiler reactivity-transform shared -af cjs && node scripts/build.js runtime reactivity shared -af esm-bundler && node scripts/build.js vue -f esm-bundler-runtime && node scripts/build.js vue -f esm-browser-runtime && node scripts/build.js compiler-sfc server-renderer -f esm-browser",
"build-sfc-playground": "cd packages/sfc-playground && npm run build"
},
"types": "test-dts/index.d.ts",
@ -44,7 +46,7 @@
]
},
"engines": {
"node": ">=16.5.0"
"node": ">=16.11.0"
},
"devDependencies": {
"@babel/types": "^7.12.0",
@ -69,6 +71,7 @@
"enquirer": "^2.3.2",
"esbuild": "^0.14.35",
"eslint": "^7.7.0",
"eslint-plugin-jest": "26.1.5",
"execa": "^4.0.2",
"fs-extra": "^9.0.1",
"jest": "^27.1.0",

View File

@ -211,6 +211,30 @@ return function render(_ctx, _cache) {
}"
`;
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist SVG with directives 1`] = `
"const _Vue = Vue
const { createElementVNode: _createElementVNode } = _Vue
const _hoisted_1 = /*#__PURE__*/_createElementVNode(\\"path\\", { d: \\"M2,3H5.5L12\\" }, null, -1 /* HOISTED */)
const _hoisted_2 = [
_hoisted_1
]
return function render(_ctx, _cache) {
with (_ctx) {
const { createElementVNode: _createElementVNode, resolveDirective: _resolveDirective, openBlock: _openBlock, createElementBlock: _createElementBlock, withDirectives: _withDirectives } = _Vue
const _directive_foo = _resolveDirective(\\"foo\\")
return (_openBlock(), _createElementBlock(\\"div\\", null, [
_withDirectives((_openBlock(), _createElementBlock(\\"svg\\", null, _hoisted_2)), [
[_directive_foo]
])
]))
}
}"
`;
exports[`compiler: hoistStatic transform prefixIdentifiers should NOT hoist elements with cached handlers + other bindings 1`] = `
"import { normalizeClass as _normalizeClass, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"

View File

@ -616,5 +616,11 @@ describe('compiler: hoistStatic transform', () => {
expect(root.hoists.length).toBe(0)
expect(generate(root).code).toMatchSnapshot()
})
test('should NOT hoist SVG with directives', () => {
const root = transformWithHoist(`<div><svg v-foo><path d="M2,3H5.5L12"/></svg></div>`)
expect(root.hoists.length).toBe(2)
expect(generate(root).code).toMatchSnapshot()
})
})
})

View File

@ -1053,6 +1053,21 @@ describe('compiler: element transform', () => {
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
)
})
// #5870
test('HYDRATE_EVENTS on dynamic component', () => {
const { node } = parseWithElementTransform(
`<component :is="foo" @input="foo" />`,
{
directiveTransforms: {
on: transformOn
}
}
)
expect(node.patchFlag).toBe(
genFlagText([PatchFlags.PROPS, PatchFlags.HYDRATE_EVENTS])
)
})
})
describe('dynamic component', () => {

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-core",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/compiler-core",
"main": "index.js",
"module": "dist/compiler-core.esm-bundler.js",
@ -32,7 +32,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-core#readme",
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/shared": "3.2.37",
"@babel/parser": "^7.16.4",
"estree-walker": "^2.0.2",
"source-map": "^0.6.1"

View File

@ -68,7 +68,7 @@ export interface DirectiveTransformResult {
ssrTagParts?: TemplateLiteral['elements']
}
// A structural directive transform is a technically a NodeTransform;
// A structural directive transform is technically also a NodeTransform;
// Only v-if and v-for fall into this category.
export type StructuralDirectiveTransform = (
node: ElementNode,

View File

@ -230,6 +230,15 @@ export function getConstantType(
// static then they don't need to be blocks since there will be no
// nested updates.
if (codegenNode.isBlock) {
// except set custom directives.
for (let i = 0; i < node.props.length; i++) {
const p = node.props[i]
if (p.type === NodeTypes.DIRECTIVE) {
constantCache.set(node, ConstantTypes.NOT_CONSTANT)
return ConstantTypes.NOT_CONSTANT
}
}
context.removeHelper(OPEN_BLOCK)
context.removeHelper(
getVNodeBlockHelper(context.inSSR, codegenNode.isComponent)

View File

@ -121,7 +121,13 @@ export const transformElement: NodeTransform = (node, context) => {
// props
if (props.length > 0) {
const propsBuildResult = buildProps(node, context)
const propsBuildResult = buildProps(
node,
context,
undefined,
isComponent,
isDynamicComponent
)
vnodeProps = propsBuildResult.props
patchFlag = propsBuildResult.patchFlag
dynamicPropNames = propsBuildResult.dynamicPropNames
@ -380,6 +386,8 @@ export function buildProps(
node: ElementNode,
context: TransformContext,
props: ElementNode['props'] = node.props,
isComponent: boolean,
isDynamicComponent: boolean,
ssr = false
): {
props: PropsExpression | undefined
@ -389,7 +397,6 @@ export function buildProps(
shouldUseBlock: boolean
} {
const { tag, loc: elementLoc, children } = node
const isComponent = node.tagType === ElementTypes.COMPONENT
let properties: ObjectExpression['properties'] = []
const mergeArgs: PropsExpression[] = []
const runtimeDirectives: DirectiveNode[] = []
@ -411,8 +418,8 @@ export function buildProps(
const name = key.content
const isEventHandler = isOn(name)
if (
!isComponent &&
isEventHandler &&
(!isComponent || isDynamicComponent) &&
// omit the flag for click handlers because hydration gives click
// dedicated fast path.
name.toLowerCase() !== 'onclick' &&

View File

@ -87,7 +87,13 @@ export function processSlotOutlet(
}
if (nonNameProps.length > 0) {
const { props, directives } = buildProps(node, context, nonNameProps)
const { props, directives } = buildProps(
node,
context,
nonNameProps,
false,
false
)
slotProps = props
if (directives.length) {

View File

@ -5,7 +5,8 @@ import {
createCallExpression,
CallExpression,
ElementTypes,
ConstantTypes
ConstantTypes,
createCompoundExpression
} from '../ast'
import { isText } from '../utils'
import { CREATE_TEXT } from '../runtimeHelpers'
@ -36,11 +37,10 @@ export const transformText: NodeTransform = (node, context) => {
const next = children[j]
if (isText(next)) {
if (!currentContainer) {
currentContainer = children[i] = {
type: NodeTypes.COMPOUND_EXPRESSION,
loc: child.loc,
children: [child]
}
currentContainer = children[i] = createCompoundExpression(
[child],
child.loc
)
}
// merge adjacent text node into current
currentContainer.children.push(` + `, next)

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-dom",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/compiler-dom",
"main": "index.js",
"module": "dist/compiler-dom.esm-bundler.js",
@ -37,7 +37,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-dom#readme",
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/compiler-core": "3.2.33"
"@vue/shared": "3.2.37",
"@vue/compiler-core": "3.2.37"
}
}

View File

@ -2,7 +2,7 @@
exports[`SFC analyze <script> bindings auto name inference basic 1`] = `
"export default {
name: 'FooBar',
__name: 'FooBar',
setup(__props, { expose }) {
expose();
const a = 1
@ -205,7 +205,7 @@ export default {
expose();
let __temp, __restore
if (ok) {
if (ok) {
for (let a of [1,2,3]) {
(
([__temp,__restore] = _withAsyncContext(() => a)),
@ -240,7 +240,7 @@ export default {
expose();
let __temp, __restore
if (ok) {
if (ok) {
while (d) {
(
([__temp,__restore] = _withAsyncContext(() => 5)),
@ -295,7 +295,7 @@ export default {
expose();
let __temp, __restore
if (ok) {
if (ok) {
let a = 'foo'
;(
([__temp,__restore] = _withAsyncContext(() => 0)),
@ -351,7 +351,7 @@ if (ok) {
__restore()
)
}
} else {
} else {
(
([__temp,__restore] = _withAsyncContext(() => 5)),
await __temp,
@ -722,7 +722,7 @@ return { props, a, emit }
exports[`SFC compile <script setup> dev mode import usage check TS annotations 1`] = `
"import { defineComponent as _defineComponent } from 'vue'
import { Foo, Bar, Baz } from './x'
import { Foo, Bar, Baz, Qux, Fred } from './x'
export default /*#__PURE__*/_defineComponent({
setup(__props, { expose }) {
@ -1039,8 +1039,7 @@ return (_ctx, _cache) => {
`;
exports[`SFC compile <script setup> inlineTemplate mode ssr codegen 1`] = `
"import { useCssVars as _useCssVars, unref as _unref } from 'vue'
import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from \\"vue/server-renderer\\"
"import { ssrRenderAttrs as _ssrRenderAttrs, ssrInterpolate as _ssrInterpolate } from \\"vue/server-renderer\\"
import { ref } from 'vue'
@ -1048,15 +1047,11 @@ export default {
__ssrInlineRender: true,
setup(__props) {
_useCssVars(_ctx => ({
\\"xxxxxxxx-count\\": (count.value)
}))
const count = ref(0)
return (_ctx, _push, _parent, _attrs) => {
const _cssVars = { style: {
\\"xxxxxxxx-count\\": (count.value)
\\"--xxxxxxxx-count\\": (count.value)
}}
_push(\`<!--[--><div\${
_ssrRenderAttrs(_cssVars)

View File

@ -1,5 +1,5 @@
import { BindingTypes } from '@vue/compiler-core'
import { compileSFCScript as compile, assertCode } from './utils'
import { compileSFCScript as compile, assertCode, mockId } from './utils'
describe('SFC compile <script setup>', () => {
test('should expose top level declarations', () => {
@ -168,7 +168,7 @@ defineExpose({ foo: 123 })
expect(content).toMatch(/\bexpose\(\{ foo: 123 \}\)/)
})
test('<script> after <script setup> the script content not end with `\\n`',() => {
test('<script> after <script setup> the script content not end with `\\n`', () => {
const { content } = compile(`
<script setup>
import { x } from './x'
@ -446,7 +446,7 @@ defineExpose({ foo: 123 })
test('TS annotations', () => {
const { content } = compile(`
<script setup lang="ts">
import { Foo, Bar, Baz } from './x'
import { Foo, Bar, Baz, Qux, Fred } from './x'
const a = 1
function b() {}
</script>
@ -454,11 +454,26 @@ defineExpose({ foo: 123 })
{{ a as Foo }}
{{ b<Bar>() }}
{{ Baz }}
<Comp v-slot="{ data }: Qux">{{ data }}</Comp>
<div v-for="{ z = x as Qux } in list as Fred"/>
</template>
`)
expect(content).toMatch(`return { a, b, Baz }`)
assertCode(content)
})
// vuejs/vue#12591
test('v-on inline statement', () => {
// should not error
compile(`
<script setup lang="ts">
import { foo } from './foo'
</script>
<template>
<div @click="$emit('update:a');"></div>
</tempalte>
`)
})
})
describe('inlineTemplate mode', () => {
@ -726,6 +741,8 @@ defineExpose({ foo: 123 })
expect(content).toMatch(`\n __ssrInlineRender: true,\n`)
expect(content).toMatch(`return (_ctx, _push`)
expect(content).toMatch(`ssrInterpolate`)
expect(content).not.toMatch(`useCssVars`)
expect(content).toMatch(`"--${mockId}-count": (count.value)`)
assertCode(content)
})
})
@ -1196,7 +1213,7 @@ const emit = defineEmits(['a', 'b'])
})
test('multiple `if` nested statements', () => {
assertAwaitDetection(`if (ok) {
assertAwaitDetection(`if (ok) {
let a = 'foo'
await 0 + await 1
await 2
@ -1212,13 +1229,13 @@ const emit = defineEmits(['a', 'b'])
await 3
await 4
}
} else {
} else {
await 5
}`)
})
test('multiple `if while` nested statements', () => {
assertAwaitDetection(`if (ok) {
assertAwaitDetection(`if (ok) {
while (d) {
await 5
}
@ -1237,7 +1254,7 @@ const emit = defineEmits(['a', 'b'])
})
test('multiple `if for` nested statements', () => {
assertAwaitDetection(`if (ok) {
assertAwaitDetection(`if (ok) {
for (let a of [1,2,3]) {
await a
}
@ -1646,7 +1663,7 @@ describe('SFC analyze <script> bindings', () => {
}
)
expect(content).toMatch(`export default {
name: 'FooBar'`)
__name: 'FooBar'`)
assertCode(content)
})

View File

@ -1,4 +1,4 @@
import { compileStyle } from '../src'
import { compileStyle, parse } from '../src'
import { mockId, compileSFCScript, assertCode } from './utils'
describe('CSS vars injection', () => {
@ -231,5 +231,21 @@ describe('CSS vars injection', () => {
})`)
assertCode(content)
})
// #6022
test('should be able to parse incomplete expressions', () => {
const {
descriptor: { cssVars }
} = parse(
`<script setup>let xxx = 1</script>
<style scoped>
label {
font-weight: v-bind("count.toString(");
font-weight: v-bind(xxx);
}
</style>`
)
expect(cssVars).toMatchObject([`count.toString(`, `xxx`])
})
})
})

View File

@ -25,6 +25,17 @@ describe('compiler sfc: rewriteDefault', () => {
export { a as b, a as c}
const script = a"
`)
expect(
rewriteDefault(
`const a = 1 \n export { a as b, a as default , a as c}`,
'script'
)
).toMatchInlineSnapshot(`
"const a = 1
export { a as b, a as c}
const script = a"
`)
})
test('w/ comments', async () => {
@ -63,6 +74,81 @@ describe('compiler sfc: rewriteDefault', () => {
// export { myFunction as default }
const script = a"
`)
expect(
rewriteDefault(
`const a = 1 \n export {\n a as b,\n a as default ,\n a as c}\n` +
`// export { myFunction as default }`,
'script'
)
).toMatchInlineSnapshot(`
"const a = 1
export {
a as b,
a as c}
// export { myFunction as default }
const script = a"
`)
})
test(`export { default } from '...'`, async () => {
expect(
rewriteDefault(`export { default, foo } from './index.js'`, 'script')
).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js'
export { foo } from './index.js'
const script = __VUE_DEFAULT__"
`)
expect(
rewriteDefault(`export { default , foo } from './index.js'`, 'script')
).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js'
export { foo } from './index.js'
const script = __VUE_DEFAULT__"
`)
expect(
rewriteDefault(`export { foo, default } from './index.js'`, 'script')
).toMatchInlineSnapshot(`
"import { default as __VUE_DEFAULT__ } from './index.js'
export { foo, } from './index.js'
const script = __VUE_DEFAULT__"
`)
expect(
rewriteDefault(
`export { foo as default, bar } from './index.js'`,
'script'
)
).toMatchInlineSnapshot(`
"import { foo } from './index.js'
export { bar } from './index.js'
const script = foo"
`)
expect(
rewriteDefault(
`export { foo as default , bar } from './index.js'`,
'script'
)
).toMatchInlineSnapshot(`
"import { foo } from './index.js'
export { bar } from './index.js'
const script = foo"
`)
expect(
rewriteDefault(
`export { bar, foo as default } from './index.js'`,
'script'
)
).toMatchInlineSnapshot(`
"import { foo } from './index.js'
export { bar, } from './index.js'
const script = foo"
`)
})
test('export default class', async () => {

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-sfc",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/compiler-sfc",
"main": "dist/compiler-sfc.cjs.js",
"module": "dist/compiler-sfc.esm-browser.js",
@ -33,11 +33,11 @@
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-sfc#readme",
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.33",
"@vue/compiler-dom": "3.2.33",
"@vue/compiler-ssr": "3.2.33",
"@vue/reactivity-transform": "3.2.33",
"@vue/shared": "3.2.33",
"@vue/compiler-core": "3.2.37",
"@vue/compiler-dom": "3.2.37",
"@vue/compiler-ssr": "3.2.37",
"@vue/reactivity-transform": "3.2.37",
"@vue/shared": "3.2.37",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7",
"source-map": "^0.6.1",

View File

@ -74,7 +74,7 @@ const WITH_DEFAULTS = 'withDefaults'
const DEFAULT_VAR = `__default__`
const isBuiltInDir = makeMap(
`once,memo,if,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
`once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is`
)
export interface SFCScriptCompileOptions {
@ -201,7 +201,12 @@ export function compileScript(
)
}
if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
if (isTS) plugins.push('typescript', 'decorators-legacy')
if (isTS) {
plugins.push('typescript')
if (!plugins.includes('decorators')) {
plugins.push('decorators-legacy')
}
}
if (!scriptSetup) {
if (!script) {
@ -1332,7 +1337,11 @@ export function compileScript(
}
// 8. inject `useCssVars` calls
if (cssVars.length) {
if (
cssVars.length &&
// no need to do this when targeting SSR
!(options.inlineTemplate && options.templateOptions?.ssr)
) {
helperImports.add(CSS_VARS_HELPER)
helperImports.add('unref')
s.prependRight(
@ -1486,7 +1495,7 @@ export function compileScript(
if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
const match = filename.match(/([^/\\]+)\.\w+$/)
if (match) {
runtimeOptions += `\n name: '${match[1]}',`
runtimeOptions += `\n __name: '${match[1]}',`
}
}
if (hasInlinedSsrRenderFn) {
@ -1833,6 +1842,7 @@ function inferRuntimeType(
case 'WeakSet':
case 'WeakMap':
case 'Date':
case 'Promise':
return [node.typeName.name]
case 'Record':
case 'Partial':
@ -2125,7 +2135,8 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
}
if (prop.exp) {
code += `,${processExp(
(prop.exp as SimpleExpressionNode).content
(prop.exp as SimpleExpressionNode).content,
prop.name
)}`
}
}
@ -2144,8 +2155,21 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) {
return code
}
function processExp(exp: string) {
if (/ as \w|<.*>/.test(exp)) {
const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/
function processExp(exp: string, dir?: string): string {
if (/ as\s+\w|<.*>|:/.test(exp)) {
if (dir === 'slot') {
exp = `(${exp})=>{}`
} else if (dir === 'on') {
exp = `()=>{${exp}}`
} else if (dir === 'for') {
const inMatch = exp.match(forAliasRE)
if (inMatch) {
const [, LHS, RHS] = inMatch
return processExp(`(${LHS})=>{}`) + processExp(RHS)
}
}
let ret = ''
// has potential type cast or generic arguments that uses types
const ast = parseExpression(exp, { plugins: ['typescript'] })

View File

@ -202,7 +202,7 @@ function doCompileTemplate({
cacheHandlers: true,
ssrCssVars:
ssr && ssrCssVars && ssrCssVars.length
? genCssVarsFromList(ssrCssVars, shortId, isProd)
? genCssVarsFromList(ssrCssVars, shortId, isProd, true)
: '',
scopeId: scoped ? longId : undefined,
slotted,

View File

@ -12,16 +12,17 @@ import { PluginCreator } from 'postcss'
import hash from 'hash-sum'
export const CSS_VARS_HELPER = `useCssVars`
// match v-bind() with max 2-levels of nested parens.
const cssVarRE = /v-bind\s*\(((?:[^)(]+|\((?:[^)(]+|\([^)(]*\))*\))*)\)/g
export function genCssVarsFromList(
vars: string[],
id: string,
isProd: boolean
isProd: boolean,
isSSR = false
): string {
return `{\n ${vars
.map(key => `"${genVarName(id, key, isProd)}": (${key})`)
.map(
key => `"${isSSR ? `--` : ``}${genVarName(id, key, isProd)}": (${key})`
)
.join(',\n ')}\n}`
}
@ -44,22 +45,71 @@ function normalizeExpression(exp: string) {
return exp
}
const vBindRE = /v-bind\s*\(/g
export function parseCssVars(sfc: SFCDescriptor): string[] {
const vars: string[] = []
sfc.styles.forEach(style => {
let match
// ignore v-bind() in comments /* ... */
const content = style.content.replace(/\/\*([\s\S]*?)\*\//g, '')
while ((match = cssVarRE.exec(content))) {
const variable = normalizeExpression(match[1])
if (!vars.includes(variable)) {
vars.push(variable)
while ((match = vBindRE.exec(content))) {
const start = match.index + match[0].length
const end = lexBinding(content, start)
if (end !== null) {
const variable = normalizeExpression(content.slice(start, end))
if (!vars.includes(variable)) {
vars.push(variable)
}
}
}
})
return vars
}
const enum LexerState {
inParens,
inSingleQuoteString,
inDoubleQuoteString
}
function lexBinding(content: string, start: number): number | null {
let state: LexerState = LexerState.inParens
let parenDepth = 0
for (let i = start; i < content.length; i++) {
const char = content.charAt(i)
switch (state) {
case LexerState.inParens:
if (char === `'`) {
state = LexerState.inSingleQuoteString
} else if (char === `"`) {
state = LexerState.inDoubleQuoteString
} else if (char === `(`) {
parenDepth++
} else if (char === `)`) {
if (parenDepth > 0) {
parenDepth--
} else {
return i
}
}
break
case LexerState.inSingleQuoteString:
if (char === `'`) {
state = LexerState.inParens
}
break
case LexerState.inDoubleQuoteString:
if (char === `"`) {
state = LexerState.inParens
}
break
}
}
return null
}
// for compileStyle
export interface CssVarsPluginOptions {
id: string
@ -72,10 +122,24 @@ export const cssVarsPlugin: PluginCreator<CssVarsPluginOptions> = opts => {
postcssPlugin: 'vue-sfc-vars',
Declaration(decl) {
// rewrite CSS variables
if (cssVarRE.test(decl.value)) {
decl.value = decl.value.replace(cssVarRE, (_, $1) => {
return `var(--${genVarName(id, normalizeExpression($1), isProd)})`
})
const value = decl.value
if (vBindRE.test(value)) {
vBindRE.lastIndex = 0
let transformed = ''
let lastIndex = 0
let match
while ((match = vBindRE.exec(value))) {
const start = match.index + match[0].length
const end = lexBinding(value, start)
if (end !== null) {
const variable = normalizeExpression(value.slice(start, end))
transformed +=
value.slice(lastIndex, match.index) +
`var(--${genVarName(id, variable, isProd)})`
lastIndex = end + 1
}
}
decl.value = transformed + value.slice(lastIndex)
}
}
}

View File

@ -2,7 +2,7 @@ import { parse, ParserPlugin } from '@babel/parser'
import MagicString from 'magic-string'
const defaultExportRE = /((?:^|\n|;)\s*)export(\s*)default/
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)as(\s*)default/s
const namedDefaultExportRE = /((?:^|\n|;)\s*)export(.+)(?:as)?(\s*)default/s
const exportDefaultClassRE =
/((?:^|\n|;)\s*)export\s+default\s+class\s+([\w$]+)/
@ -45,21 +45,39 @@ export function rewriteDefault(
s.overwrite(node.start!, node.declaration.start!, `const ${as} = `)
}
if (node.type === 'ExportNamedDeclaration') {
node.specifiers.forEach(specifier => {
for (const specifier of node.specifiers) {
if (
specifier.type === 'ExportSpecifier' &&
specifier.exported.type === 'Identifier' &&
specifier.exported.name === 'default'
) {
const end = specifier.end!
s.overwrite(
specifier.start!,
input.charAt(end) === ',' ? end + 1 : end,
``
)
if (node.source) {
if (specifier.local.name === 'default') {
const end = specifierEnd(input, specifier.local.end!, node.end)
s.prepend(
`import { default as __VUE_DEFAULT__ } from '${node.source.value}'\n`
)
s.overwrite(specifier.start!, end, ``)
s.append(`\nconst ${as} = __VUE_DEFAULT__`)
continue
} else {
const end = specifierEnd(input, specifier.exported.end!, node.end)
s.prepend(
`import { ${input.slice(
specifier.local.start!,
specifier.local.end!
)} } from '${node.source.value}'\n`
)
s.overwrite(specifier.start!, end, ``)
s.append(`\nconst ${as} = ${specifier.local.name}`)
continue
}
}
const end = specifierEnd(input, specifier.end!, node.end)
s.overwrite(specifier.start!, end, ``)
s.append(`\nconst ${as} = ${specifier.local.name}`)
}
})
}
}
})
return s.toString()
@ -68,3 +86,21 @@ export function rewriteDefault(
export function hasDefaultExport(input: string): boolean {
return defaultExportRE.test(input) || namedDefaultExportRE.test(input)
}
function specifierEnd(input: string, end: number, nodeEnd: number | null) {
// export { default , foo } ...
let hasCommas = false
let oldEnd = end
while (end < nodeEnd!) {
if (/\s/.test(input.charAt(end))) {
end++
} else if (input.charAt(end) === ',') {
end++
hasCommas = true
break
} else if (input.charAt(end) === '}') {
break
}
}
return hasCommas ? end : oldEnd
}

View File

@ -17,6 +17,20 @@ describe('ssr: components', () => {
`)
})
// event listeners should still be passed
test('event listeners', () => {
expect(compile(`<foo @click="bar" />`).code).toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent, mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _mergeProps({ onClick: _ctx.bar }, _attrs), null, _parent))
}"
`)
})
test('dynamic component', () => {
expect(compile(`<component is="foo" prop="b" />`).code)
.toMatchInlineSnapshot(`
@ -266,112 +280,68 @@ describe('ssr: components', () => {
`)
})
test('built-in fallthroughs', () => {
expect(compile(`<transition><div/></transition>`).code)
.toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
describe('built-in fallthroughs', () => {
test('transition', () => {
expect(compile(`<transition><div/></transition>`).code)
.toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
}"
`)
expect(compile(`<keep-alive><foo/></keep-alive>`).code)
.toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, null, _parent))
}"
`)
})
// transition-group should flatten and concat its children fragments into
// a single one
describe('transition-group', () => {
test('basic', () => {
expect(
compile(
`<transition-group><div v-for="i in list"/></transition-group>`
).code
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`<!--]-->\`)
}"
`)
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
}"
`)
})
test('with static tag', () => {
expect(
compile(
`<transition-group tag="ul"><div v-for="i in list"/></transition-group>`
).code
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
test('keep-alive', () => {
expect(compile(`<keep-alive><foo/></keep-alive>`).code)
.toMatchInlineSnapshot(`
"const { resolveComponent: _resolveComponent } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<ul>\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`</ul>\`)
}"
`)
return function ssrRender(_ctx, _push, _parent, _attrs) {
const _component_foo = _resolveComponent(\\"foo\\")
_push(_ssrRenderComponent(_component_foo, _attrs, null, _parent))
}"
`)
})
test('with dynamic tag', () => {
// #5352
test('should push marker string if is slot root', () => {
expect(
compile(
`<transition-group :tag="someTag"><div v-for="i in list"/></transition-group>`
).code
compile(`<foo><transition><div v-if="false"/></transition></foo>`)
.code
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
"const { resolveComponent: _resolveComponent, withCtx: _withCtx, openBlock: _openBlock, createBlock: _createBlock, createCommentVNode: _createCommentVNode, Transition: _Transition, createVNode: _createVNode } = require(\\"vue\\")
const { ssrRenderComponent: _ssrRenderComponent } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<\${_ctx.someTag}>\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`</\${_ctx.someTag}>\`)
}"
`)
})
const _component_foo = _resolveComponent(\\"foo\\")
test('with multi fragments children', () => {
expect(
compile(
`<transition-group>
<div v-for="i in 10"/>
<div v-for="i in 10"/>
<template v-if="ok"><div>ok</div></template>
</transition-group>`
).code
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`)
_ssrRenderList(10, (i) => {
_push(\`<div></div>\`)
})
_ssrRenderList(10, (i) => {
_push(\`<div></div>\`)
})
if (_ctx.ok) {
_push(\`<div>ok</div>\`)
} else {
_push(\`<!---->\`)
}
_push(\`<!--]-->\`)
_push(_ssrRenderComponent(_component_foo, _attrs, {
default: _withCtx((_, _push, _parent, _scopeId) => {
if (_push) {
_push(\`\`)
if (false) {
_push(\`<div\${_scopeId}></div>\`)
} else {
_push(\`<!---->\`)
}
} else {
return [
_createVNode(_Transition, null, {
default: _withCtx(() => [
false
? (_openBlock(), _createBlock(\\"div\\", { key: 0 }))
: _createCommentVNode(\\"v-if\\", true)
]),
_: 1 /* STABLE */
})
]
}
}),
_: 1 /* STABLE */
}, _parent))
}"
`)
})

View File

@ -0,0 +1,60 @@
import { compile } from '../src'
describe('ssr: attrs fallthrough', () => {
test('basic', () => {
expect(compile(`<div/>`).code).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<div\${_ssrRenderAttrs(_attrs)}></div>\`)
}"
`)
})
test('with comments', () => {
expect(compile(`<!--!--><div/>`).code).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[--><!--!--><div\${_ssrRenderAttrs(_attrs)}></div><!--]-->\`)
}"
`)
})
// #5140
test('should not inject to non-single-root if branches', () => {
expect(compile(`<div v-if="true"/><div/>`).code).toMatchInlineSnapshot(`
"
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`)
if (true) {
_push(\`<div></div>\`)
} else {
_push(\`<!---->\`)
}
_push(\`<div></div><!--]-->\`)
}"
`)
})
test('fallthrough component content (root with coomments)', () => {
expect(compile(`<!--root--><transition><div/></transition>`).code)
.toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[--><!--root--><div\${_ssrRenderAttrs(_attrs)}></div><!--]-->\`)
}"
`)
})
test('should not inject to fallthrough component content if not root', () => {
expect(compile(`<div/><transition><div/></transition>`).code)
.toMatchInlineSnapshot(`
"
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[--><div></div><div></div><!--]-->\`)
}"
`)
})
})

View File

@ -123,7 +123,7 @@ describe('ssr: <slot>', () => {
"const { ssrRenderSlotInner: _ssrRenderSlotInner } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_ssrRenderSlotInner(_ctx.$slots, \\"default\\", {}, null, _push, _parent)
_ssrRenderSlotInner(_ctx.$slots, \\"default\\", {}, null, _push, _parent, null, true)
}"
`)
})

View File

@ -0,0 +1,111 @@
import { compile } from '../src'
// transition-group should flatten and concat its children fragments into
// a single one
describe('transition-group', () => {
test('basic', () => {
expect(
compile(`<transition-group><div v-for="i in list"/></transition-group>`)
.code
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`<!--]-->\`)
}"
`)
})
test('with static tag', () => {
expect(
compile(
`<transition-group tag="ul"><div v-for="i in list"/></transition-group>`
).code
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<ul\${_ssrRenderAttrs(_attrs)}>\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`</ul>\`)
}"
`)
})
test('with dynamic tag', () => {
expect(
compile(
`<transition-group :tag="someTag"><div v-for="i in list"/></transition-group>`
).code
).toMatchInlineSnapshot(`
"const { ssrRenderAttrs: _ssrRenderAttrs, ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<\${
_ctx.someTag
}\${
_ssrRenderAttrs(_attrs)
}>\`)
_ssrRenderList(_ctx.list, (i) => {
_push(\`<div></div>\`)
})
_push(\`</\${_ctx.someTag}>\`)
}"
`)
})
test('with multi fragments children', () => {
expect(
compile(
`<transition-group>
<div v-for="i in 10"/>
<div v-for="i in 10"/>
<template v-if="ok"><div>ok</div></template>
</transition-group>`
).code
).toMatchInlineSnapshot(`
"const { ssrRenderList: _ssrRenderList } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<!--[-->\`)
_ssrRenderList(10, (i) => {
_push(\`<div></div>\`)
})
_ssrRenderList(10, (i) => {
_push(\`<div></div>\`)
})
if (_ctx.ok) {
_push(\`<div>ok</div>\`)
} else {
_push(\`<!---->\`)
}
_push(\`<!--]-->\`)
}"
`)
})
test('attribute fallthrough', () => {
expect(
compile(
`<transition-group tag="ul" class="red" id="ok">
</transition-group>`
).code
).toMatchInlineSnapshot(`
"const { mergeProps: _mergeProps } = require(\\"vue\\")
const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\")
return function ssrRender(_ctx, _push, _parent, _attrs) {
_push(\`<ul\${_ssrRenderAttrs(_mergeProps({
class: \\"red\\",
id: \\"ok\\"
}, _attrs))}></ul>\`)
}"
`)
})
})

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compiler-ssr",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/compiler-ssr",
"main": "dist/compiler-ssr.cjs.js",
"types": "dist/compiler-ssr.d.ts",
@ -28,7 +28,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/compiler-ssr#readme",
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/compiler-dom": "3.2.33"
"@vue/shared": "3.2.37",
"@vue/compiler-dom": "3.2.37"
}
}

View File

@ -10,7 +10,8 @@ import {
trackSlotScopes,
noopDirectiveTransform,
transformBind,
transformStyle
transformStyle,
transformOn
} from '@vue/compiler-dom'
import { ssrCodegenTransform } from './ssrCodegenTransform'
import { ssrTransformElement } from './transforms/ssrTransformElement'
@ -70,11 +71,12 @@ export function compile(
directiveTransforms: {
// reusing core v-bind
bind: transformBind,
on: transformOn,
// model and show has dedicated SSR handling
model: ssrTransformModel,
show: ssrTransformShow,
// the following are ignored during SSR
on: noopDirectiveTransform,
// on: noopDirectiveTransform,
cloak: noopDirectiveTransform,
once: noopDirectiveTransform,
memo: noopDirectiveTransform,

View File

@ -51,7 +51,7 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
const isFragment =
ast.children.length > 1 && ast.children.some(c => !isText(c))
processChildren(ast.children, context, isFragment)
processChildren(ast, context, isFragment)
ast.codegenNode = createBlockStatement(context.body)
// Finalize helpers.
@ -125,8 +125,12 @@ function createChildContext(
)
}
interface Container {
children: TemplateChildNode[]
}
export function processChildren(
children: TemplateChildNode[],
parent: Container,
context: SSRTransformContext,
asFragment = false,
disableNestedFragments = false
@ -134,6 +138,7 @@ export function processChildren(
if (asFragment) {
context.pushStringPart(`<!--[-->`)
}
const { children } = parent
for (let i = 0; i < children.length; i++) {
const child = children[i]
switch (child.type) {
@ -143,7 +148,7 @@ export function processChildren(
ssrProcessElement(child, context)
break
case ElementTypes.COMPONENT:
ssrProcessComponent(child, context)
ssrProcessComponent(child, context, parent)
break
case ElementTypes.SLOT:
ssrProcessSlotOutlet(child, context)
@ -208,12 +213,12 @@ export function processChildren(
}
export function processChildrenAsStatement(
children: TemplateChildNode[],
parent: Container,
parentContext: SSRTransformContext,
asFragment = false,
withSlotScopeId = parentContext.withSlotScopeId
): BlockStatement {
const childContext = createChildContext(parentContext, withSlotScopeId)
processChildren(children, childContext, asFragment)
processChildren(parent, childContext, asFragment)
return createBlockStatement(childContext.body)
}

View File

@ -11,8 +11,11 @@ import {
isBuiltInType
} from '@vue/compiler-dom'
const filterChild = (node: ParentNode) =>
node.children.filter(n => n.type !== NodeTypes.COMMENT)
const hasSingleChild = (node: ParentNode): boolean =>
node.children.filter(n => n.type !== NodeTypes.COMMENT).length === 1
filterChild(node).length === 1
export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
// _attrs is provided as a function argument.
@ -28,10 +31,13 @@ export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
(isBuiltInType(node.tag, 'Transition') ||
isBuiltInType(node.tag, 'KeepAlive'))
) {
if (hasSingleChild(node)) {
injectFallthroughAttrs(node.children[0])
const rootChildren = filterChild(context.root)
if (rootChildren.length === 1 && rootChildren[0] === node) {
if (hasSingleChild(node)) {
injectFallthroughAttrs(node.children[0])
}
return
}
return
}
const parent = context.parent
@ -40,6 +46,25 @@ export const ssrInjectFallthroughAttrs: NodeTransform = (node, context) => {
}
if (node.type === NodeTypes.IF_BRANCH && hasSingleChild(node)) {
// detect cases where the parent v-if is not the only root level node
let hasEncounteredIf = false
for (const c of filterChild(parent)) {
if (
c.type === NodeTypes.IF ||
(c.type === NodeTypes.ELEMENT && findDir(c, 'if'))
) {
// multiple root v-if
if (hasEncounteredIf) return
hasEncounteredIf = true
} else if (
// node before v-if
!hasEncounteredIf ||
// non else nodes
!(c.type === NodeTypes.ELEMENT && findDir(c, /else/, true))
) {
return
}
}
injectFallthroughAttrs(node.children[0])
} else if (hasSingleChild(parent)) {
injectFallthroughAttrs(node)

View File

@ -34,7 +34,9 @@ import {
TRANSITION_GROUP,
CREATE_VNODE,
CallExpression,
JSChildNode
JSChildNode,
RESOLVE_DYNAMIC_COMPONENT,
TRANSITION
} from '@vue/compiler-dom'
import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
import {
@ -47,7 +49,10 @@ import {
ssrProcessSuspense,
ssrTransformSuspense
} from './ssrTransformSuspense'
import { ssrProcessTransitionGroup } from './ssrTransformTransitionGroup'
import {
ssrProcessTransitionGroup,
ssrTransformTransitionGroup
} from './ssrTransformTransitionGroup'
import { isSymbol, isObject, isArray } from '@vue/shared'
import { buildSSRProps } from './ssrTransformElement'
@ -57,7 +62,10 @@ import { buildSSRProps } from './ssrTransformElement'
// pass and complete them in the 2nd pass.
const wipMap = new WeakMap<ComponentNode, WIPSlotEntry[]>()
const WIP_SLOT = Symbol()
interface WIPSlotEntry {
type: typeof WIP_SLOT
fn: FunctionExpression
children: TemplateChildNode[]
vnodeBranch: ReturnStatement
@ -83,13 +91,18 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
}
const component = resolveComponentType(node, context, true /* ssr */)
const isDynamicComponent =
isObject(component) && component.callee === RESOLVE_DYNAMIC_COMPONENT
componentTypeMap.set(node, component)
if (isSymbol(component)) {
if (component === SUSPENSE) {
return ssrTransformSuspense(node, context)
}
return // built-in component: fallthrough
if (component === TRANSITION_GROUP) {
return ssrTransformTransitionGroup(node, context)
}
return // other built-in components: fallthrough
}
// Build the fallback vnode-based branch for the component's slots.
@ -116,7 +129,13 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
if (node.props.length) {
// note we are not passing ssr: true here because for components, v-on
// handlers should still be passed
const { props, directives } = buildProps(node, context)
const { props, directives } = buildProps(
node,
context,
undefined,
true,
isDynamicComponent
)
if (props || directives.length) {
propsExp = buildSSRProps(props, directives, context)
}
@ -134,6 +153,7 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
loc
)
wipEntries.push({
type: WIP_SLOT,
fn,
children,
// also collect the corresponding vnode branch built earlier
@ -173,7 +193,8 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
export function ssrProcessComponent(
node: ComponentNode,
context: SSRTransformContext
context: SSRTransformContext,
parent: { children: TemplateChildNode[] }
) {
const component = componentTypeMap.get(node)!
if (!node.ssrCodegenNode) {
@ -187,13 +208,23 @@ export function ssrProcessComponent(
} else {
// real fall-through: Transition / KeepAlive
// just render its children.
processChildren(node.children, context)
// #5352: if is at root level of a slot, push an empty string.
// this does not affect the final output, but avoids all-comment slot
// content of being treated as empty by ssrRenderSlot().
if ((parent as WIPSlotEntry).type === WIP_SLOT) {
context.pushStringPart(``)
}
// #5351: filter out comment children inside transition
if (component === TRANSITION) {
node.children = node.children.filter(c => c.type !== NodeTypes.COMMENT)
}
processChildren(node, context)
}
} else {
// finish up slot function expressions from the 1st pass.
const wipEntries = wipMap.get(node) || []
for (let i = 0; i < wipEntries.length; i++) {
const { fn, children, vnodeBranch } = wipEntries[i]
const { fn, vnodeBranch } = wipEntries[i]
// For each slot, we generate two branches: one SSR-optimized branch and
// one normal vnode-based branch. The branches are taken based on the
// presence of the 2nd `_push` argument (which is only present if the slot
@ -201,7 +232,7 @@ export function ssrProcessComponent(
fn.body = createIfStatement(
createSimpleExpression(`_push`, false),
processChildrenAsStatement(
children,
wipEntries[i],
context,
false,
true /* withSlotScopeId */

View File

@ -89,6 +89,8 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
node,
context,
node.props,
false /* isComponent */,
false /* isDynamicComponent */,
true /* ssr */
)
if (props || directives.length) {
@ -194,7 +196,7 @@ export const ssrTransformElement: NodeTransform = (node, context) => {
if (!needMergeProps) {
node.children = [createInterpolation(prop.exp, prop.loc)]
}
} else if (!needMergeProps) {
} else if (!needMergeProps && prop.name !== 'on') {
// Directive transforms.
const directiveTransform = context.directiveTransforms[prop.name]
if (directiveTransform) {
@ -426,7 +428,7 @@ export function ssrProcessElement(
if (rawChildren) {
context.pushStringPart(rawChildren)
} else if (node.children.length) {
processChildren(node.children, context)
processChildren(node, context)
}
if (!isVoidTag(node.tag)) {

View File

@ -50,6 +50,10 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => {
parent.children.filter(c => c.type === NodeTypes.ELEMENT).length === 1
) {
method = SSR_RENDER_SLOT_INNER
if (!(context.scopeId && context.slotted !== false)) {
args.push('null')
}
args.push('true')
}
node.ssrCodegenNode = createCallExpression(context.helper(method), args)
@ -65,7 +69,7 @@ export function ssrProcessSlotOutlet(
// has fallback content
if (node.children.length) {
const fallbackRenderFn = createFunctionExpression([])
fallbackRenderFn.body = processChildrenAsStatement(node.children, context)
fallbackRenderFn.body = processChildrenAsStatement(node, context)
// _renderSlot(slots, name, props, fallback, ...)
renderCall.arguments[3] = fallbackRenderFn
}

View File

@ -66,8 +66,8 @@ export function ssrProcessSuspense(
}
const { slotsExp, wipSlots } = wipEntry
for (let i = 0; i < wipSlots.length; i++) {
const { fn, children } = wipSlots[i]
fn.body = processChildrenAsStatement(children, context)
const slot = wipSlots[i]
slot.fn.body = processChildrenAsStatement(slot, context)
}
// _push(ssrRenderSuspense(slots))
context.pushStatement(

View File

@ -58,7 +58,7 @@ export function ssrProcessTeleport(
false, // isSlot
node.loc
)
contentRenderFn.body = processChildrenAsStatement(node.children, context)
contentRenderFn.body = processChildrenAsStatement(node, context)
context.pushStatement(
createCallExpression(context.helper(SSR_RENDER_TELEPORT), [
`_push`,

View File

@ -1,20 +1,75 @@
import { ComponentNode, findProp, NodeTypes } from '@vue/compiler-dom'
import {
AttributeNode,
buildProps,
ComponentNode,
createCallExpression,
DirectiveNode,
findProp,
JSChildNode,
NodeTypes,
TransformContext
} from '@vue/compiler-dom'
import { SSR_RENDER_ATTRS } from '../runtimeHelpers'
import { processChildren, SSRTransformContext } from '../ssrCodegenTransform'
import { buildSSRProps } from './ssrTransformElement'
const wipMap = new WeakMap<ComponentNode, WIPEntry>()
interface WIPEntry {
tag: AttributeNode | DirectiveNode
propsExp: string | JSChildNode | null
}
// phase 1: build props
export function ssrTransformTransitionGroup(
node: ComponentNode,
context: TransformContext
) {
return () => {
const tag = findProp(node, 'tag')
if (tag) {
const otherProps = node.props.filter(p => p !== tag)
const { props, directives } = buildProps(
node,
context,
otherProps,
true, /* isComponent */
false, /* isDynamicComponent */
true /* ssr (skip event listeners) */
)
let propsExp = null
if (props || directives.length) {
propsExp = createCallExpression(context.helper(SSR_RENDER_ATTRS), [
buildSSRProps(props, directives, context)
])
}
wipMap.set(node, {
tag,
propsExp
})
}
}
}
// phase 2: process children
export function ssrProcessTransitionGroup(
node: ComponentNode,
context: SSRTransformContext
) {
const tag = findProp(node, 'tag')
if (tag) {
const entry = wipMap.get(node)
if (entry) {
const { tag, propsExp } = entry
if (tag.type === NodeTypes.DIRECTIVE) {
// dynamic :tag
context.pushStringPart(`<`)
context.pushStringPart(tag.exp!)
if (propsExp) {
context.pushStringPart(propsExp)
}
context.pushStringPart(`>`)
processChildren(
node.children,
node,
context,
false,
/**
@ -30,12 +85,16 @@ export function ssrProcessTransitionGroup(
context.pushStringPart(`>`)
} else {
// static tag
context.pushStringPart(`<${tag.value!.content}>`)
processChildren(node.children, context, false, true)
context.pushStringPart(`<${tag.value!.content}`)
if (propsExp) {
context.pushStringPart(propsExp)
}
context.pushStringPart(`>`)
processChildren(node, context, false, true)
context.pushStringPart(`</${tag.value!.content}>`)
}
} else {
// fragment
processChildren(node.children, context, true, true)
processChildren(node, context, true, true)
}
}

View File

@ -33,7 +33,7 @@ export function ssrProcessFor(
createForLoopParams(node.parseResult)
)
renderLoop.body = processChildrenAsStatement(
node.children,
node,
context,
needFragmentWrapper
)

View File

@ -72,5 +72,5 @@ function processIfBranch(
(children.length !== 1 || children[0].type !== NodeTypes.ELEMENT) &&
// optimize away nested fragments when the only child is a ForNode
!(children.length === 1 && children[0].type === NodeTypes.FOR)
return processChildrenAsStatement(children, context, needFragmentWrapper)
return processChildrenAsStatement(branch, context, needFragmentWrapper)
}

View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2018-present, Yuxi (Evan) You
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -1,6 +1,6 @@
{
"name": "@vue/reactivity-transform",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/reactivity-transform",
"main": "dist/reactivity-transform.cjs.js",
"files": [
@ -29,8 +29,8 @@
"homepage": "https://github.com/vuejs/core/tree/dev/packages/reactivity-transform#readme",
"dependencies": {
"@babel/parser": "^7.16.4",
"@vue/compiler-core": "3.2.33",
"@vue/shared": "3.2.33",
"@vue/compiler-core": "3.2.37",
"@vue/shared": "3.2.37",
"estree-walker": "^2.0.2",
"magic-string": "^0.25.7"
},

View File

@ -1,6 +1,6 @@
{
"name": "@vue/reactivity",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/reactivity",
"main": "index.js",
"module": "dist/reactivity.esm-bundler.js",
@ -36,6 +36,6 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/reactivity#readme",
"dependencies": {
"@vue/shared": "3.2.33"
"@vue/shared": "3.2.37"
}
}

View File

@ -231,6 +231,7 @@ describe('api: lifecycle hooks', () => {
}
const Mid = {
props: ['count'],
setup(props: any) {
onBeforeMount(() => calls.push('mid onBeforeMount'))
onMounted(() => calls.push('mid onMounted'))
@ -243,6 +244,7 @@ describe('api: lifecycle hooks', () => {
}
const Child = {
props: ['count'],
setup(props: any) {
onBeforeMount(() => calls.push('child onBeforeMount'))
onMounted(() => calls.push('child onMounted'))

View File

@ -75,39 +75,6 @@ describe('api: setup context', () => {
expect(dummy).toBe(1)
})
it('setup props should resolve the correct types from props object', async () => {
const count = ref(0)
let dummy
const Parent = {
render: () => h(Child, { count: count.value })
}
const Child = defineComponent({
props: {
count: Number
},
setup(props) {
watchEffect(() => {
dummy = props.count
})
return () => h('div', props.count)
}
})
const root = nodeOps.createElement('div')
render(h(Parent), root)
expect(serializeInner(root)).toMatch(`<div>0</div>`)
expect(dummy).toBe(0)
// props should be reactive
count.value++
await nextTick()
expect(serializeInner(root)).toMatch(`<div>1</div>`)
expect(dummy).toBe(1)
})
it('context.attrs', async () => {
const toggle = ref(true)

View File

@ -13,7 +13,8 @@ import {
createTextVNode,
createVNode,
withDirectives,
vModelCheckbox
vModelCheckbox,
renderSlot
} from '@vue/runtime-dom'
import { renderToString, SSRContext } from '@vue/server-renderer'
import { PatchFlags } from '../../shared/src'
@ -96,6 +97,28 @@ describe('SSR hydration', () => {
expect(s.children).toBe(staticContent)
})
// #6008
test('static (with text node as starting node)', () => {
const html = ` A <span>foo</span> B`
const { vnode, container } = mountWithHydration(html, () =>
createStaticVNode(` A <span>foo</span> B`, 3)
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.anchor).toBe(container.lastChild)
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
})
test('static with content adoption', () => {
const html = ` A <span>foo</span> B`
const { vnode, container } = mountWithHydration(html, () =>
createStaticVNode(``, 3)
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.anchor).toBe(container.lastChild)
expect(vnode.children).toBe(html)
expect(`Hydration node mismatch`).not.toHaveBeenWarned()
})
test('element with text children', async () => {
const msg = ref('foo')
const { vnode, container } = mountWithHydration(
@ -202,7 +225,7 @@ describe('SSR hydration', () => {
const fn = jest.fn()
const teleportContainer = document.createElement('div')
teleportContainer.id = 'teleport'
teleportContainer.innerHTML = `<span>foo</span><span class="foo"></span><!---->`
teleportContainer.innerHTML = `<span>foo</span><span class="foo"></span><!--teleport anchor-->`
document.body.appendChild(teleportContainer)
const { vnode, container } = mountWithHydration(
@ -233,7 +256,7 @@ describe('SSR hydration', () => {
msg.value = 'bar'
await nextTick()
expect(teleportContainer.innerHTML).toBe(
`<span>bar</span><span class="bar"></span><!---->`
`<span>bar</span><span class="bar"></span><!--teleport anchor-->`
)
})
@ -263,7 +286,7 @@ describe('SSR hydration', () => {
const teleportHtml = ctx.teleports!['#teleport2']
expect(teleportHtml).toMatchInlineSnapshot(
`"<span>foo</span><span class=\\"foo\\"></span><!----><span>foo2</span><span class=\\"foo2\\"></span><!---->"`
`"<span>foo</span><span class=\\"foo\\"></span><!--teleport anchor--><span>foo2</span><span class=\\"foo2\\"></span><!--teleport anchor-->"`
)
teleportContainer.innerHTML = teleportHtml
@ -300,7 +323,7 @@ describe('SSR hydration', () => {
msg.value = 'bar'
await nextTick()
expect(teleportContainer.innerHTML).toMatchInlineSnapshot(
`"<span>bar</span><span class=\\"bar\\"></span><!----><span>bar2</span><span class=\\"bar2\\"></span><!---->"`
`"<span>bar</span><span class=\\"bar\\"></span><!--teleport anchor--><span>bar2</span><span class=\\"bar2\\"></span><!--teleport anchor-->"`
)
})
@ -327,7 +350,7 @@ describe('SSR hydration', () => {
)
const teleportHtml = ctx.teleports!['#teleport3']
expect(teleportHtml).toMatchInlineSnapshot(`"<!---->"`)
expect(teleportHtml).toMatchInlineSnapshot(`"<!--teleport anchor-->"`)
teleportContainer.innerHTML = teleportHtml
document.body.appendChild(teleportContainer)
@ -366,6 +389,67 @@ describe('SSR hydration', () => {
)
})
test('Teleport (as component root)', () => {
const teleportContainer = document.createElement('div')
teleportContainer.id = 'teleport4'
teleportContainer.innerHTML = `hello<!--teleport anchor-->`
document.body.appendChild(teleportContainer)
const wrapper = {
render() {
return h(Teleport, { to: '#teleport4' }, ['hello'])
}
}
const { vnode, container } = mountWithHydration(
'<div><!--teleport start--><!--teleport end--><div></div></div>',
() => h('div', [h(wrapper), h('div')])
)
expect(vnode.el).toBe(container.firstChild)
// component el
const wrapperVNode = (vnode as any).children[0]
const tpStart = container.firstChild?.firstChild
const tpEnd = tpStart?.nextSibling
expect(wrapperVNode.el).toBe(tpStart)
expect(wrapperVNode.component.subTree.el).toBe(tpStart)
expect(wrapperVNode.component.subTree.anchor).toBe(tpEnd)
// next node hydrate properly
const nextVNode = (vnode as any).children[1]
expect(nextVNode.el).toBe(container.firstChild?.lastChild)
})
test('Teleport (nested)', () => {
const teleportContainer = document.createElement('div')
teleportContainer.id = 'teleport5'
teleportContainer.innerHTML = `<div><!--teleport start--><!--teleport end--></div><!--teleport anchor--><div>child</div><!--teleport anchor-->`
document.body.appendChild(teleportContainer)
const { vnode, container } = mountWithHydration(
'<!--teleport start--><!--teleport end-->',
() =>
h(Teleport, { to: '#teleport5' }, [
h('div', [h(Teleport, { to: '#teleport5' }, [h('div', 'child')])])
])
)
expect(vnode.el).toBe(container.firstChild)
expect(vnode.anchor).toBe(container.lastChild)
const childDivVNode = (vnode as any).children[0]
const div = teleportContainer.firstChild
expect(childDivVNode.el).toBe(div)
expect(vnode.targetAnchor).toBe(div?.nextSibling)
const childTeleportVNode = childDivVNode.children[0]
expect(childTeleportVNode.el).toBe(div?.firstChild)
expect(childTeleportVNode.anchor).toBe(div?.lastChild)
expect(childTeleportVNode.targetAnchor).toBe(teleportContainer.lastChild)
expect(childTeleportVNode.children[0].el).toBe(
teleportContainer.lastChild?.previousSibling
)
})
// compile SSR + client render fn from the same template & hydrate
test('full compiler integration', async () => {
const mounted: string[] = []
@ -851,6 +935,53 @@ describe('SSR hydration', () => {
expect((container.firstChild!.firstChild as any)._value).toBe(true)
})
// #5728
test('empty text node in slot', () => {
const Comp = {
render(this: any) {
return renderSlot(this.$slots, 'default', {}, () => [
createTextVNode('')
])
}
}
const { container, vnode } = mountWithHydration('<!--[--><!--]-->', () =>
h(Comp)
)
expect(container.childNodes.length).toBe(3)
const text = container.childNodes[1]
expect(text.nodeType).toBe(3)
expect(vnode.el).toBe(container.childNodes[0])
// component => slot fragment => text node
expect((vnode as any).component?.subTree.children[0].el).toBe(text)
})
test('app.unmount()', async () => {
const container = document.createElement('DIV')
container.innerHTML = '<button></button>'
const App = defineComponent({
setup(_, { expose }) {
const count = ref(0)
expose({ count })
return () =>
h('button', {
onClick: () => count.value++
})
}
})
const app = createSSRApp(App)
const vm = app.mount(container)
await nextTick()
expect((container as any)._vnode).toBeDefined()
// @ts-expect-error - expose()'d properties are not available on vm type
expect(vm.count).toBe(0)
app.unmount()
expect((container as any)._vnode).toBe(null)
})
describe('mismatch handling', () => {
test('text node', () => {
const { container } = mountWithHydration(`foo`, () => 'bar')

View File

@ -1,6 +1,6 @@
{
"name": "@vue/runtime-core",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/runtime-core",
"main": "index.js",
"module": "dist/runtime-core.esm-bundler.js",
@ -32,7 +32,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-core#readme",
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/reactivity": "3.2.33"
"@vue/shared": "3.2.37",
"@vue/reactivity": "3.2.37"
}
}

View File

@ -111,7 +111,7 @@ export function defineAsyncComponent<
)
}
return defineComponent<{}>({
return defineComponent({
name: 'AsyncComponentWrapper',
__asyncLoader: load,

View File

@ -6,8 +6,7 @@ import {
ComponentOptionsWithObjectProps,
ComponentOptionsMixin,
RenderFunction,
ComponentOptionsBase,
ComponentProvideOptions
ComponentOptionsBase
} from './componentOptions'
import {
SetupContext,
@ -41,8 +40,6 @@ export type DefineComponent<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = {},
EE extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
RawOptions extends {} = {},
PP = PublicProps,
Props = Readonly<
PropsOrPropOptions extends ComponentPropsOptions
@ -51,23 +48,22 @@ export type DefineComponent<
> &
({} extends E ? {} : EmitsToProps<E>),
Defaults = ExtractDefaultPropTypes<PropsOrPropOptions>
> = RawOptions &
ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
PP & Props,
Defaults,
true
> &
Props
> = ComponentPublicInstanceConstructor<
CreateComponentPublicInstance<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
PP & Props,
Defaults,
true
> &
Props
> &
ComponentOptionsBase<
Props,
RawBindings,
@ -78,8 +74,7 @@ export type DefineComponent<
Extends,
E,
EE,
Defaults,
Provide
Defaults
> &
PP
@ -109,36 +104,20 @@ export function defineComponent<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Options extends {} = {}
EE extends string = string
>(
options: Options &
ComponentOptionsWithoutProps<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Provide
>
): DefineComponent<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Provide,
Options
>
options: ComponentOptionsWithoutProps<
Props,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
>
): DefineComponent<Props, RawBindings, D, C, M, Mixin, Extends, E, EE>
// overload 3: object format with array props declaration
// props inferred as { [key in PropNames]?: any }
@ -152,23 +131,19 @@ export function defineComponent<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Options extends {} = {}
EE extends string = string
>(
options: Options &
ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Provide
>
options: ComponentOptionsWithArrayProps<
PropNames,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
>
): DefineComponent<
Readonly<{ [key in PropNames]?: any }>,
RawBindings,
@ -178,9 +153,7 @@ export function defineComponent<
Mixin,
Extends,
E,
EE,
Provide,
Options
EE
>
// overload 4: object format with object props declaration
@ -196,36 +169,20 @@ export function defineComponent<
Mixin extends ComponentOptionsMixin = ComponentOptionsMixin,
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = Record<string, any>,
EE extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Options extends {} = {}
EE extends string = string
>(
options: Options &
ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Provide
>
): DefineComponent<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE,
Provide,
Options
>
options: ComponentOptionsWithObjectProps<
PropsOptions,
RawBindings,
D,
C,
M,
Mixin,
Extends,
E,
EE
>
): DefineComponent<PropsOptions, RawBindings, D, C, M, Mixin, Extends, E, EE>
// implementation, close to no-op
export function defineComponent(options: unknown) {

View File

@ -139,7 +139,7 @@ type InferDefault<P, T> = T extends
| boolean
| symbol
| Function
? T
? T | ((props: P) => T)
: (props: P) => T
type PropsWithDefaults<Base, Defaults> = Base & {

View File

@ -381,9 +381,10 @@ function installLegacyAPIs(app: App) {
function applySingletonAppMutations(app: App) {
// copy over asset registries and deopt flag
;['mixins', 'components', 'directives', 'filters', 'deopt'].forEach(key => {
app._context.mixins = [...singletonApp._context.mixins]
;['components', 'directives', 'filters'].forEach(key => {
// @ts-ignore
app._context[key] = singletonApp._context[key]
app._context[key] = Object.create(singletonApp._context[key])
})
// copy over global config mutations
@ -398,7 +399,7 @@ function applySingletonAppMutations(app: App) {
}
const val = singletonApp.config[key as keyof AppConfig]
// @ts-ignore
app.config[key] = val
app.config[key] = isObject(val) ? Object.create(val) : val
// compat for runtime ignoredElements -> isCustomElement
if (

View File

@ -106,6 +106,10 @@ export interface ComponentInternalOptions {
* This one should be exposed so that devtools can make use of it
*/
__file?: string
/**
* name inferred from filename
*/
__name?: string
}
export interface FunctionalComponent<P = {}, E extends EmitsOptions = {}>
@ -949,11 +953,12 @@ const classify = (str: string): string =>
str.replace(classifyRE, c => c.toUpperCase()).replace(/[-_]/g, '')
export function getComponentName(
Component: ConcreteComponent
): string | undefined {
Component: ConcreteComponent,
includeInferred = true
): string | false | undefined {
return isFunction(Component)
? Component.displayName || Component.name
: Component.name
: Component.name || (includeInferred && Component.__name)
}
/* istanbul ignore next */

View File

@ -58,7 +58,8 @@ import { EmitsOptions, EmitsToProps } from './componentEmits'
import { Directive } from './directives'
import {
CreateComponentPublicInstance,
ComponentPublicInstance
ComponentPublicInstance,
isReservedPrefix
} from './componentPublicInstance'
import { warn } from './warning'
import { VNodeChild } from './vnode'
@ -117,9 +118,8 @@ export interface ComponentOptionsBase<
Extends extends ComponentOptionsMixin,
E extends EmitsOptions,
EE extends string = string,
Defaults = {},
Provide extends ComponentProvideOptions = ComponentProvideOptions
> extends LegacyOptions<Props, D, C, M, Mixin, Extends, Provide>,
Defaults = {}
> extends LegacyOptions<Props, D, C, M, Mixin, Extends>,
ComponentInternalOptions,
ComponentCustomOptions {
setup?: (
@ -225,7 +225,6 @@ export type ComponentOptionsWithoutProps<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
PE = Props & EmitsToProps<E>
> = ComponentOptionsBase<
PE,
@ -237,8 +236,7 @@ export type ComponentOptionsWithoutProps<
Extends,
E,
EE,
{},
Provide
{}
> & {
props?: undefined
} & ThisType<
@ -255,7 +253,6 @@ export type ComponentOptionsWithArrayProps<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Readonly<{ [key in PropNames]?: any }> & EmitsToProps<E>
> = ComponentOptionsBase<
Props,
@ -267,8 +264,7 @@ export type ComponentOptionsWithArrayProps<
Extends,
E,
EE,
{},
Provide
{}
> & {
props: PropNames[]
} & ThisType<
@ -294,7 +290,6 @@ export type ComponentOptionsWithObjectProps<
Extends extends ComponentOptionsMixin = ComponentOptionsMixin,
E extends EmitsOptions = EmitsOptions,
EE extends string = string,
Provide extends ComponentProvideOptions = ComponentProvideOptions,
Props = Readonly<ExtractPropTypes<PropsOptions>> & EmitsToProps<E>,
Defaults = ExtractDefaultPropTypes<PropsOptions>
> = ComponentOptionsBase<
@ -307,8 +302,7 @@ export type ComponentOptionsWithObjectProps<
Extends,
E,
EE,
Defaults,
Provide
Defaults
> & {
props: PropsOptions & ThisType<void>
} & ThisType<
@ -408,8 +402,7 @@ interface LegacyOptions<
C extends ComputedOptions,
M extends MethodOptions,
Mixin extends ComponentOptionsMixin,
Extends extends ComponentOptionsMixin,
Provide extends ComponentProvideOptions = ComponentProvideOptions
Extends extends ComponentOptionsMixin
> {
compatConfig?: CompatConfig
@ -443,7 +436,7 @@ interface LegacyOptions<
computed?: C
methods?: M
watch?: ComponentWatchOptions
provide?: Provide
provide?: ComponentProvideOptions
inject?: ComponentInjectOptions
// assets
@ -681,7 +674,7 @@ export function applyOptions(instance: ComponentInternalInstance) {
for (const key in data) {
checkDuplicateProperties!(OptionTypes.DATA, key)
// expose data on ctx during dev
if (key[0] !== '$' && key[0] !== '_') {
if (!isReservedPrefix(key[0])) {
Object.defineProperty(ctx, key, {
configurable: true,
enumerable: true,

View File

@ -34,8 +34,7 @@ import {
OptionTypesKeys,
resolveMergedOptions,
shouldCacheAccess,
MergedComponentOptionsOverride,
ComponentProvideOptions
MergedComponentOptionsOverride
} from './componentOptions'
import { EmitsOptions, EmitFn } from './componentEmits'
import { Slots } from './componentSlots'
@ -151,8 +150,7 @@ export type CreateComponentPublicInstance<
PublicM extends MethodOptions = UnwrapMixinsType<PublicMixin, 'M'> &
EnsureNonVoid<M>,
PublicDefaults = UnwrapMixinsType<PublicMixin, 'Defaults'> &
EnsureNonVoid<Defaults>,
Provide extends ComponentProvideOptions = ComponentProvideOptions
EnsureNonVoid<Defaults>
> = ComponentPublicInstance<
PublicP,
PublicB,
@ -163,19 +161,7 @@ export type CreateComponentPublicInstance<
PublicProps,
PublicDefaults,
MakeDefaultsOptional,
ComponentOptionsBase<
P,
B,
D,
C,
M,
Mixin,
Extends,
E,
string,
Defaults,
Provide
>
ComponentOptionsBase<P, B, D, C, M, Mixin, Extends, E, string, Defaults>
>
// public properties exposed on the proxy, which is used as the render context
@ -274,6 +260,8 @@ export interface ComponentRenderContext {
_: ComponentInternalInstance
}
export const isReservedPrefix = (key: string) => key === '_' || key === '$'
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }: ComponentRenderContext, key: string) {
const { ctx, setupState, data, props, accessCache, type, appContext } =
@ -385,11 +373,7 @@ export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
// to infinite warning loop
key.indexOf('__v') !== 0)
) {
if (
data !== EMPTY_OBJ &&
(key[0] === '$' || key[0] === '_') &&
hasOwn(data, key)
) {
if (data !== EMPTY_OBJ && isReservedPrefix(key[0]) && hasOwn(data, key)) {
warn(
`Property ${JSON.stringify(
key
@ -571,7 +555,7 @@ export function exposeSetupStateOnRenderContext(
const { ctx, setupState } = instance
Object.keys(toRaw(setupState)).forEach(key => {
if (!setupState.__isScriptSetup) {
if (key[0] === '$' || key[0] === '_') {
if (isReservedPrefix(key[0])) {
warn(
`setup() return property ${JSON.stringify(
key

View File

@ -38,6 +38,8 @@ export function markAttrsAccessed() {
accessedAttrs = true
}
type SetRootFn = ((root: VNode) => void) | undefined
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
@ -121,7 +123,7 @@ export function renderComponentRoot(
// in dev mode, comments are preserved, and it's possible for a template
// to have comments along side the root element which makes it a fragment
let root = result
let setRoot: ((root: VNode) => void) | undefined = undefined
let setRoot: SetRootFn = undefined
if (
__DEV__ &&
result.patchFlag > 0 &&
@ -246,9 +248,7 @@ export function renderComponentRoot(
* template into a fragment root, but we need to locate the single element
* root for attrs and scope id processing.
*/
const getChildRoot = (
vnode: VNode
): [VNode, ((root: VNode) => void) | undefined] => {
const getChildRoot = (vnode: VNode): [VNode, SetRootFn] => {
const rawChildren = vnode.children as VNodeArrayChildren
const dynamicChildren = vnode.dynamicChildren
const childRoot = filterSingleRoot(rawChildren)
@ -257,7 +257,7 @@ const getChildRoot = (
}
const index = rawChildren.indexOf(childRoot)
const dynamicIndex = dynamicChildren ? dynamicChildren.indexOf(childRoot) : -1
const setRoot = (updatedRoot: VNode) => {
const setRoot: SetRootFn = (updatedRoot: VNode) => {
rawChildren[index] = updatedRoot
if (dynamicChildren) {
if (dynamicIndex > -1) {

View File

@ -96,8 +96,11 @@ const KeepAliveImpl: ComponentOptions = {
// if the internal renderer is not registered, it indicates that this is server-side rendering,
// for KeepAlive, we just need to render its children
if (!sharedContext.renderer) {
return slots.default
if (__SSR__ && !sharedContext.renderer) {
return () => {
const children = slots.default && slots.default()
return children && children.length === 1 ? children[0] : children
}
}
const cache: Cache = new Map()

View File

@ -353,7 +353,26 @@ function hydrateTeleport(
vnode.targetAnchor = targetNode
} else {
vnode.anchor = nextSibling(node)
vnode.targetAnchor = hydrateChildren(
// lookahead until we find the target anchor
// we cannot rely on return value of hydrateChildren() because there
// could be nested teleports
let targetAnchor = targetNode
while (targetAnchor) {
targetAnchor = nextSibling(targetAnchor)
if (
targetAnchor &&
targetAnchor.nodeType === 8 &&
(targetAnchor as Comment).data === 'teleport anchor'
) {
vnode.targetAnchor = targetAnchor
;(target as TeleportTargetElement)._lpa =
vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node)
break
}
}
hydrateChildren(
targetNode,
vnode,
target,
@ -363,8 +382,6 @@ function hydrateTeleport(
optimized
)
}
;(target as TeleportTargetElement)._lpa =
vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node)
}
}
return vnode.anchor && nextSibling(vnode.anchor as Node)

View File

@ -86,7 +86,10 @@ function resolveAsset(
// explicit self name has highest priority
if (type === COMPONENTS) {
const selfName = getComponentName(Component)
const selfName = getComponentName(
Component,
false /* do not include inferred name to avoid breaking existing code */
)
if (
selfName &&
(selfName === name ||

View File

@ -27,7 +27,7 @@ import { isAsyncWrapper } from './apiAsyncComponent'
export type RootHydrateFunction = (
vnode: VNode<Node, Element>,
container: Element | ShadowRoot
container: (Element | ShadowRoot) & { _vnode?: VNode }
) => void
const enum DOMNodeTypes {
@ -55,7 +55,15 @@ export function createHydrationFunctions(
const {
mt: mountComponent,
p: patch,
o: { patchProp, nextSibling, parentNode, remove, insert, createComment }
o: {
patchProp,
createText,
nextSibling,
parentNode,
remove,
insert,
createComment
}
} = rendererInternals
const hydrate: RootHydrateFunction = (vnode, container) => {
@ -67,11 +75,13 @@ export function createHydrationFunctions(
)
patch(null, vnode, container)
flushPostFlushCbs()
container._vnode = vnode
return
}
hasMismatch = false
hydrateNode(container.firstChild!, vnode, null, null, null)
flushPostFlushCbs()
container._vnode = vnode
if (hasMismatch && !__TEST__) {
// this error should show up in production
console.error(`Hydration completed but contains mismatches.`)
@ -110,7 +120,14 @@ export function createHydrationFunctions(
switch (type) {
case Text:
if (domType !== DOMNodeTypes.TEXT) {
nextNode = onMismatch()
// #5728 empty text node inside a slot can cause hydration failure
// because the server rendered HTML won't contain a text node
if (vnode.children === '') {
insert((vnode.el = createText('')), parentNode(node)!, node)
nextNode = node
} else {
nextNode = onMismatch()
}
} else {
if ((node as Text).data !== vnode.children) {
hasMismatch = true
@ -133,7 +150,7 @@ export function createHydrationFunctions(
}
break
case Static:
if (domType !== DOMNodeTypes.ELEMENT) {
if (domType !== DOMNodeTypes.ELEMENT && domType !== DOMNodeTypes.TEXT) {
nextNode = onMismatch()
} else {
// determine anchor, adopt content
@ -143,7 +160,10 @@ export function createHydrationFunctions(
const needToAdoptContent = !(vnode.children as string).length
for (let i = 0; i < vnode.staticCount!; i++) {
if (needToAdoptContent)
vnode.children += (nextNode as Element).outerHTML
vnode.children +=
nextNode.nodeType === DOMNodeTypes.ELEMENT
? (nextNode as Element).outerHTML
: (nextNode as Text).data
if (i === vnode.staticCount! - 1) {
vnode.anchor = nextNode
}
@ -207,6 +227,15 @@ export function createHydrationFunctions(
? locateClosingAsyncAnchor(node)
: nextSibling(node)
// #4293 teleport as component root
if (
nextNode &&
isComment(nextNode) &&
nextNode.data === 'teleport end'
) {
nextNode = nextSibling(nextNode)
}
// #3787
// if component is async, it may get moved / unmounted before its
// inner component is loaded, so we need to give it a placeholder

View File

@ -86,7 +86,7 @@ export { h } from './h'
// Advanced render function utilities
export { createVNode, cloneVNode, mergeProps, isVNode } from './vnode'
// VNode types
export { Fragment, Text, Comment, Static } from './vnode'
export { Fragment, Text, Comment, Static, VNodeRef } from './vnode'
// Built-in components
export { Teleport, TeleportProps } from './components/Teleport'
export { Suspense, SuspenseProps } from './components/Suspense'
@ -217,6 +217,7 @@ export {
ComponentOptionsWithArrayProps,
ComponentCustomOptions,
ComponentOptionsBase,
ComponentProvideOptions,
RenderFunction,
MethodOptions,
ComputedOptions,
@ -324,7 +325,7 @@ const _ssrUtils = {
}
/**
* SSR utils for \@vue/server-renderer. Only exposed in cjs builds.
* SSR utils for \@vue/server-renderer. Only exposed in ssr-possible builds.
* @internal
*/
export const ssrUtils = (__SSR__ ? _ssrUtils : null) as typeof _ssrUtils

View File

@ -1064,8 +1064,12 @@ function baseCreateRenderer(
let { patchFlag, dynamicChildren, slotScopeIds: fragmentSlotScopeIds } = n2
if (__DEV__ && isHmrUpdating) {
// HMR updated, force full diff
if (
__DEV__ &&
// #5523 dev root fragment may inherit directives
(isHmrUpdating || patchFlag & PatchFlags.DEV_ROOT_FRAGMENT)
) {
// HMR updated / Dev root fragment (w/ comments), force full diff
patchFlag = 0
optimized = false
dynamicChildren = null
@ -1098,8 +1102,6 @@ function baseCreateRenderer(
if (
patchFlag > 0 &&
patchFlag & PatchFlags.STABLE_FRAGMENT &&
// #5523 dev root fragment may inherit directives so always force update
!(__DEV__ && patchFlag & PatchFlags.DEV_ROOT_FRAGMENT) &&
dynamicChildren &&
// #2715 the previous fragment could've been a BAILed one as a result
// of renderSlot() with no valid children

View File

@ -107,7 +107,7 @@ export function setRef(
if (hasOwn(setupState, ref)) {
setupState[ref] = value
}
} else if (isRef(ref)) {
} else if (_isRef) {
ref.value = value
if (rawRef.k) refs[rawRef.k] = value
} else if (__DEV__) {

View File

@ -43,6 +43,7 @@ import { convertLegacyComponent } from './compat/component'
import { convertLegacyVModelProps } from './compat/componentVModel'
import { defineLegacyVNodeProperties } from './compat/renderFn'
import { callWithAsyncErrorHandling, ErrorCodes } from './errorHandling'
import { ComponentPublicInstance } from './componentPublicInstance'
export const Fragment = Symbol(__DEV__ ? 'Fragment' : undefined) as any as {
__isFragment: true
@ -68,7 +69,10 @@ export type VNodeTypes =
export type VNodeRef =
| string
| Ref
| ((ref: object | null, refs: Record<string, any>) => void)
| ((
ref: Element | ComponentPublicInstance | null,
refs: Record<string, any>
) => void)
export type VNodeNormalizedRefAtom = {
i: ComponentInternalInstance

View File

@ -291,6 +291,94 @@ describe('vModel', () => {
expect(data.lazy).toEqual('foo')
})
it('should work with range', async () => {
const component = defineComponent({
data() {
return { value: 25 }
},
render() {
return [
withVModel(
h('input', {
type: 'range',
min: 1,
max: 100,
class: 'foo',
'onUpdate:modelValue': setValue.bind(this)
}),
this.value,
{
number: true
}
),
withVModel(
h('input', {
type: 'range',
min: 1,
max: 100,
class: 'bar',
'onUpdate:modelValue': setValue.bind(this)
}),
this.value,
{
lazy: true
}
)
]
}
})
render(h(component), root)
const foo = root.querySelector('.foo')
const bar = root.querySelector('.bar')
const data = root._vnode.component.data
foo.value = 20
triggerEvent('input', foo)
await nextTick()
expect(data.value).toEqual(20)
foo.value = 200
triggerEvent('input', foo)
await nextTick()
expect(data.value).toEqual(100)
foo.value = -1
triggerEvent('input', foo)
await nextTick()
expect(data.value).toEqual(1)
bar.value = 30
triggerEvent('change', bar)
await nextTick()
expect(data.value).toEqual('30')
bar.value = 200
triggerEvent('change', bar)
await nextTick()
expect(data.value).toEqual('100')
bar.value = -1
triggerEvent('change', bar)
await nextTick()
expect(data.value).toEqual('1')
data.value = 60
await nextTick()
expect(foo.value).toEqual('60')
expect(bar.value).toEqual('60')
data.value = -1
await nextTick()
expect(foo.value).toEqual('1')
expect(bar.value).toEqual('1')
data.value = 200
await nextTick()
expect(foo.value).toEqual('100')
expect(bar.value).toEqual('100')
})
it('should work with checkbox', async () => {
const component = defineComponent({
data() {

View File

@ -1,6 +1,6 @@
{
"name": "@vue/runtime-dom",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/runtime-dom",
"main": "index.js",
"module": "dist/runtime-dom.esm-bundler.js",
@ -35,8 +35,8 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-dom#readme",
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/runtime-core": "3.2.33",
"@vue/shared": "3.2.37",
"@vue/runtime-core": "3.2.37",
"csstype": "^2.6.8"
}
}

View File

@ -174,9 +174,11 @@ export function resolveTransitionProps(
done && done()
}
let isLeaving = false
const finishLeave = (el: Element, done?: () => void) => {
isLeaving = false
const finishLeave = (
el: Element & { _isLeaving?: boolean },
done?: () => void
) => {
el._isLeaving = false
removeTransitionClass(el, leaveFromClass)
removeTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveActiveClass)
@ -223,8 +225,8 @@ export function resolveTransitionProps(
},
onEnter: makeEnterHook(false),
onAppear: makeEnterHook(true),
onLeave(el, done) {
isLeaving = true
onLeave(el: Element & { _isLeaving?: boolean }, done) {
el._isLeaving = true
const resolve = () => finishLeave(el, done)
addTransitionClass(el, leaveFromClass)
if (__COMPAT__ && legacyClassEnabled) {
@ -234,7 +236,7 @@ export function resolveTransitionProps(
forceReflow()
addTransitionClass(el, leaveActiveClass)
nextFrame(() => {
if (!isLeaving) {
if (!el._isLeaving) {
// cancelled
return
}

View File

@ -269,6 +269,24 @@ export const vModelDynamic: ObjectDirective<
}
}
function resolveDynamicModel(tagName: string, type: string | undefined) {
switch (tagName) {
case 'SELECT':
return vModelSelect
case 'TEXTAREA':
return vModelText
default:
switch (type) {
case 'checkbox':
return vModelCheckbox
case 'radio':
return vModelRadio
default:
return vModelText
}
}
}
function callModelHook(
el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
binding: DirectiveBinding,
@ -276,26 +294,10 @@ function callModelHook(
prevVNode: VNode | null,
hook: keyof ObjectDirective
) {
let modelToUse: ObjectDirective
switch (el.tagName) {
case 'SELECT':
modelToUse = vModelSelect
break
case 'TEXTAREA':
modelToUse = vModelText
break
default:
switch (vnode.props && vnode.props.type) {
case 'checkbox':
modelToUse = vModelCheckbox
break
case 'radio':
modelToUse = vModelRadio
break
default:
modelToUse = vModelText
}
}
const modelToUse = resolveDynamicModel(
el.tagName,
vnode.props && vnode.props.type
)
const fn = modelToUse[hook] as DirectiveHook
fn && fn(el, binding, vnode, prevVNode)
}
@ -324,4 +326,18 @@ export function initVModelForSSR() {
return { checked: true }
}
}
vModelDynamic.getSSRProps = (binding, vnode) => {
if (typeof vnode.type !== 'string') {
return
}
const modelToUse = resolveDynamicModel(
// resolveDynamicModel expects an uppercase tag name, but vnode.type is lowercase
vnode.type.toUpperCase(),
vnode.props && vnode.props.type
)
if (modelToUse.getSSRProps) {
return modelToUse.getSSRProps(binding, vnode)
}
}
}

View File

@ -25,7 +25,7 @@ const [_getNow, skipTimestampCheck] = /*#__PURE__*/ (() => {
// if the low-res timestamp which is bigger than the event timestamp
// (which is evaluated AFTER) it means the event is using a hi-res timestamp,
// and we need to use the hi-res version for event listeners as well.
_getNow = () => performance.now()
_getNow = performance.now.bind(performance)
}
// #3485: Firefox <= 53 has incorrect Event.timeStamp implementation
// and does not fire microtasks in between event propagation, so safe to exclude.

View File

@ -40,6 +40,7 @@ export interface CSSProperties
* For examples and more information, visit:
* https://github.com/frenic/csstype#what-should-i-do-when-i-get-type-errors
*/
[v: `--${string}`]: string | number | undefined
}
type Booleanish = boolean | 'true' | 'false'
@ -457,7 +458,7 @@ export interface InputHTMLAttributes extends HTMLAttributes {
autocomplete?: string
autofocus?: Booleanish
capture?: boolean | 'user' | 'environment' // https://www.w3.org/tr/html-media-capture/#the-capture-attribute
checked?: Booleanish | any[] // for IDE v-model multi-checkbox support
checked?: Booleanish | any[] | Set<any> // for IDE v-model multi-checkbox support
crossorigin?: string
disabled?: Booleanish
form?: string
@ -1309,10 +1310,7 @@ import * as RuntimeCore from '@vue/runtime-core'
type ReservedProps = {
key?: string | number | symbol
ref?:
| string
| RuntimeCore.Ref
| ((ref: Element | RuntimeCore.ComponentPublicInstance | null) => void)
ref?: RuntimeCore.VNodeRef
ref_for?: boolean
ref_key?: string
}

View File

@ -1,6 +1,6 @@
{
"name": "@vue/runtime-test",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/runtime-test",
"private": true,
"main": "index.js",
@ -25,7 +25,7 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/runtime-test#readme",
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/runtime-core": "3.2.33"
"@vue/shared": "3.2.37",
"@vue/runtime-core": "3.2.37"
}
}

View File

@ -676,7 +676,7 @@ function testRender(type: string, render: typeof renderToString) {
render: () => h('p', 'hello')
}
expect(await render(h(KeepAlive, () => h(MyComp)))).toBe(
`<!--[--><p>hello</p><!--]-->`
`<p>hello</p>`
)
})

View File

@ -11,6 +11,7 @@ import {
vModelText,
vModelRadio,
vModelCheckbox,
vModelDynamic,
resolveDirective
} from 'vue'
import { ssrGetDirectiveProps, ssrRenderAttrs } from '../src'
@ -376,6 +377,100 @@ describe('ssr: directives', () => {
})
})
describe('vnode v-model dynamic', () => {
test('text', async () => {
expect(
await renderToString(
createApp({
render() {
return withDirectives(h('input'), [[vModelDynamic, 'hello']])
}
})
)
).toBe(`<input value="hello">`)
})
test('radio', async () => {
expect(
await renderToString(
createApp({
render() {
return withDirectives(
h('input', { type: 'radio', value: 'hello' }),
[[vModelDynamic, 'hello']]
)
}
})
)
).toBe(`<input type="radio" value="hello" checked>`)
expect(
await renderToString(
createApp({
render() {
return withDirectives(
h('input', { type: 'radio', value: 'hello' }),
[[vModelDynamic, 'foo']]
)
}
})
)
).toBe(`<input type="radio" value="hello">`)
})
test('checkbox', async () => {
expect(
await renderToString(
createApp({
render() {
return withDirectives(h('input', { type: 'checkbox' }), [
[vModelDynamic, true]
])
}
})
)
).toBe(`<input type="checkbox" checked>`)
expect(
await renderToString(
createApp({
render() {
return withDirectives(h('input', { type: 'checkbox' }), [
[vModelDynamic, false]
])
}
})
)
).toBe(`<input type="checkbox">`)
expect(
await renderToString(
createApp({
render() {
return withDirectives(
h('input', { type: 'checkbox', value: 'foo' }),
[[vModelDynamic, ['foo']]]
)
}
})
)
).toBe(`<input type="checkbox" value="foo" checked>`)
expect(
await renderToString(
createApp({
render() {
return withDirectives(
h('input', { type: 'checkbox', value: 'foo' }),
[[vModelDynamic, []]]
)
}
})
)
).toBe(`<input type="checkbox" value="foo">`)
})
})
test('custom directive w/ getSSRProps (vdom)', async () => {
expect(
await renderToString(

View File

@ -0,0 +1,144 @@
/**
* @jest-environment node
*/
import { createApp } from 'vue'
import { renderToString } from '../src/renderToString'
const components = {
one: {
template: `<div><slot/></div>`
}
}
describe('ssr: slot', () => {
test('text slot', async () => {
expect(
await renderToString(
createApp({
components,
template: `<one>hello</one>`
})
)
).toBe(`<div><!--[-->hello<!--]--></div>`)
})
test('element slot', async () => {
expect(
await renderToString(
createApp({
components,
template: `<one><div>hi</div></one>`
})
)
).toBe(`<div><!--[--><div>hi</div><!--]--></div>`)
})
test('empty slot', async () => {
expect(
await renderToString(
createApp({
components: {
one: {
template: `<div><slot/></div>`
}
},
template: `<one><template v-if="false"/></one>`
})
)
).toBe(`<div><!--[--><!--]--></div>`)
})
test('empty slot (manual comments)', async () => {
expect(
await renderToString(
createApp({
components: {
one: {
template: `<div><slot/></div>`
}
},
template: `<one><!--hello--></one>`
})
)
).toBe(`<div><!--[--><!--]--></div>`)
})
test('empty slot (multi-line comments)', async () => {
expect(
await renderToString(
createApp({
components: {
one: {
template: `<div><slot/></div>`
}
},
template: `<one><!--he\nllo--></one>`
})
)
).toBe(`<div><!--[--><!--]--></div>`)
})
test('multiple elements', async () => {
expect(
await renderToString(
createApp({
components,
template: `<one><div>one</div><div>two</div></one>`
})
)
).toBe(`<div><!--[--><div>one</div><div>two</div><!--]--></div>`)
})
test('fragment slot (template v-if)', async () => {
expect(
await renderToString(
createApp({
components,
template: `<one><template v-if="true">hello</template></one>`
})
)
).toBe(`<div><!--[--><!--[-->hello<!--]--><!--]--></div>`)
})
test('fragment slot (template v-if + multiple elements)', async () => {
expect(
await renderToString(
createApp({
components,
template: `<one><template v-if="true"><div>one</div><div>two</div></template></one>`
})
)
).toBe(
`<div><!--[--><!--[--><div>one</div><div>two</div><!--]--><!--]--></div>`
)
})
test('transition slot', async () => {
expect(
await renderToString(
createApp({
components: {
one: {
template: `<transition><slot/></transition>`
}
},
template: `<one><div v-if="false">foo</div></one>`
})
)
).toBe(`<!---->`)
expect(
await renderToString(
createApp({
components: {
one: {
template: `<transition><slot/></transition>`
}
},
template: `<one><div v-if="true">foo</div></one>`
})
)
).toBe(`<div>foo</div>`)
})
})

View File

@ -4,6 +4,7 @@
import { createApp, h, Teleport } from 'vue'
import { renderToString } from '../src/renderToString'
import { renderToSimpleStream } from '../src/renderToStream'
import { SSRContext } from '../src/render'
import { ssrRenderTeleport } from '../src/helpers/ssrRenderTeleport'
@ -30,7 +31,9 @@ describe('ssrRenderTeleport', () => {
ctx
)
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe(`<div>content</div><!---->`)
expect(ctx.teleports!['#target']).toBe(
`<div>content</div><!--teleport anchor-->`
)
})
test('teleport rendering (compiled + disabled)', async () => {
@ -57,7 +60,7 @@ describe('ssrRenderTeleport', () => {
expect(html).toBe(
'<!--teleport start--><div>content</div><!--teleport end-->'
)
expect(ctx.teleports!['#target']).toBe(`<!---->`)
expect(ctx.teleports!['#target']).toBe(`<!--teleport anchor-->`)
})
test('teleport rendering (vnode)', async () => {
@ -73,7 +76,9 @@ describe('ssrRenderTeleport', () => {
ctx
)
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe('<span>hello</span><!---->')
expect(ctx.teleports!['#target']).toBe(
'<span>hello</span><!--teleport anchor-->'
)
})
test('teleport rendering (vnode + disabled)', async () => {
@ -92,7 +97,7 @@ describe('ssrRenderTeleport', () => {
expect(html).toBe(
'<!--teleport start--><span>hello</span><!--teleport end-->'
)
expect(ctx.teleports!['#target']).toBe(`<!---->`)
expect(ctx.teleports!['#target']).toBe(`<!--teleport anchor-->`)
})
test('multiple teleports with same target', async () => {
@ -114,7 +119,7 @@ describe('ssrRenderTeleport', () => {
'<div><!--teleport start--><!--teleport end--><!--teleport start--><!--teleport end--></div>'
)
expect(ctx.teleports!['#target']).toBe(
'<span>hello</span><!---->world<!---->'
'<span>hello</span><!--teleport anchor-->world<!--teleport anchor-->'
)
})
@ -132,6 +137,43 @@ describe('ssrRenderTeleport', () => {
ctx
)
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe(`<div>content</div><!---->`)
expect(ctx.teleports!['#target']).toBe(
`<div>content</div><!--teleport anchor-->`
)
})
test('teleport inside async component (stream)', async () => {
const ctx: SSRContext = {}
const asyncComponent = {
template: '<teleport to="#target"><div>content</div></teleport>',
async setup() {}
}
let html = ''
let resolve: any
const p = new Promise(r => (resolve = r))
renderToSimpleStream(
h({
template: '<async-component />',
components: { asyncComponent }
}),
ctx,
{
push(chunk) {
if (chunk === null) {
resolve()
} else {
html += chunk
}
},
destroy(err) {
throw err
}
}
)
await p
expect(html).toBe('<!--teleport start--><!--teleport end-->')
expect(ctx.teleports!['#target']).toBe(
`<div>content</div><!--teleport anchor-->`
)
})
})

View File

@ -1,6 +1,6 @@
{
"name": "@vue/server-renderer",
"version": "3.2.33",
"version": "3.2.37",
"description": "@vue/server-renderer",
"main": "index.js",
"module": "dist/server-renderer.esm-bundler.js",
@ -13,6 +13,7 @@
"name": "VueServerRenderer",
"formats": [
"esm-bundler",
"esm-browser",
"cjs"
]
},
@ -31,10 +32,10 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme",
"peerDependencies": {
"vue": "3.2.33"
"vue": "3.2.37"
},
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/compiler-ssr": "3.2.33"
"@vue/shared": "3.2.37",
"@vue/compiler-ssr": "3.2.37"
}
}

View File

@ -40,7 +40,8 @@ export function ssrRenderSlotInner(
fallbackRenderFn: (() => void) | null,
push: PushFn,
parentComponent: ComponentInternalInstance,
slotScopeId?: string
slotScopeId?: string,
transition?: boolean
) {
const slotFn = slots[slotName]
if (slotFn) {
@ -61,10 +62,14 @@ export function ssrRenderSlotInner(
// ssr slot.
// check if the slot renders all comments, in which case use the fallback
let isEmptySlot = true
for (let i = 0; i < slotBuffer.length; i++) {
if (!isComment(slotBuffer[i])) {
isEmptySlot = false
break
if (transition) {
isEmptySlot = false
} else {
for (let i = 0; i < slotBuffer.length; i++) {
if (!isComment(slotBuffer[i])) {
isEmptySlot = false
break
}
}
}
if (isEmptySlot) {
@ -82,7 +87,11 @@ export function ssrRenderSlotInner(
}
}
const commentRE = /^<!--.*-->$/
const commentTestRE = /^<!--.*-->$/s
const commentRE = /<!--[^]*?-->/gm
function isComment(item: SSRBufferItem) {
return typeof item === 'string' && commentRE.test(item)
if (typeof item !== 'string' || !commentTestRE.test(item)) return false
// if item is '<!---->' or '<!--[-->' or '<!--]-->', return true directly
if (item.length <= 8) return true
return !item.replace(commentRE, '').trim()
}

View File

@ -10,28 +10,28 @@ export function ssrRenderTeleport(
) {
parentPush('<!--teleport start-->')
let teleportContent: SSRBufferItem
if (disabled) {
contentRenderFn(parentPush)
teleportContent = `<!---->`
} else {
const { getBuffer, push } = createBuffer()
contentRenderFn(push)
push(`<!---->`) // teleport end anchor
teleportContent = getBuffer()
}
const context = parentComponent.appContext.provides[
ssrContextKey as any
] as SSRContext
const teleportBuffers =
context.__teleportBuffers || (context.__teleportBuffers = {})
if (teleportBuffers[target]) {
teleportBuffers[target].push(teleportContent)
const targetBuffer = teleportBuffers[target] || (teleportBuffers[target] = [])
// record current index of the target buffer to handle nested teleports
// since the parent needs to be rendered before the child
const bufferIndex = targetBuffer.length
let teleportContent: SSRBufferItem
if (disabled) {
contentRenderFn(parentPush)
teleportContent = `<!--teleport anchor-->`
} else {
teleportBuffers[target] = [teleportContent]
const { getBuffer, push } = createBuffer()
contentRenderFn(push)
push(`<!--teleport anchor-->`)
teleportContent = getBuffer()
}
targetBuffer.splice(bufferIndex, 0, teleportContent)
parentPush('<!--teleport end-->')
}

View File

@ -4,6 +4,7 @@ import {
ComponentInternalInstance,
DirectiveBinding,
Fragment,
FunctionalComponent,
mergeProps,
ssrUtils,
Static,
@ -112,12 +113,17 @@ function renderComponentSubTree(
const comp = instance.type as Component
const { getBuffer, push } = createBuffer()
if (isFunction(comp)) {
renderVNode(
push,
(instance.subTree = renderComponentRoot(instance)),
instance,
slotScopeId
)
let root = renderComponentRoot(instance)
// #5817 scope ID attrs not falling through if functional component doesn't
// have props
if (!(comp as FunctionalComponent).props) {
for (const key in instance.attrs) {
if (key.startsWith(`data-v-`)) {
;(root.props || (root.props = {}))[key] = ``
}
}
}
renderVNode(push, (instance.subTree = root), instance, slotScopeId)
} else {
if (
(!instance.render || instance.render === NOOP) &&

View File

@ -9,6 +9,7 @@ import {
import { isString, isPromise } from '@vue/shared'
import { renderComponentVNode, SSRBuffer, SSRContext } from './render'
import { Readable, Writable } from 'stream'
import { resolveTeleports } from './renderToString'
const { isVNode } = ssrUtils
@ -74,6 +75,7 @@ export function renderToSimpleStream<T extends SimpleReadable>(
Promise.resolve(renderComponentVNode(vnode))
.then(buffer => unrollBuffer(buffer, stream))
.then(() => resolveTeleports(context))
.then(() => stream.push(null))
.catch(error => {
stream.destroy(error)

View File

@ -70,7 +70,7 @@ export async function renderToString(
return result
}
async function resolveTeleports(context: SSRContext) {
export async function resolveTeleports(context: SSRContext) {
if (context.__teleportBuffers) {
context.teleports = context.teleports || {}
for (const key in context.__teleportBuffers) {

View File

@ -9,10 +9,10 @@
<script>
// process shim for old versions of @vue/compiler-sfc dependency
window.process = { env: {} }
const saved = localStorage.getItem('vue-sfc-playground-prefer-dark')
const savedPreferDark = localStorage.getItem('vue-sfc-playground-prefer-dark')
if (
saved !== 'false' ||
window.matchMedia('(prefers-color-scheme: dark)').matches
savedPreferDark === 'true' ||
(!savedPreferDark && window.matchMedia('(prefers-color-scheme: dark)').matches)
) {
document.documentElement.classList.add('dark')
}

View File

@ -1,6 +1,6 @@
{
"name": "@vue/sfc-playground",
"version": "3.2.33",
"version": "3.2.37",
"private": true,
"scripts": {
"dev": "vite",
@ -12,8 +12,8 @@
"vite": "^2.9.8"
},
"dependencies": {
"vue": "3.2.33",
"@vue/repl": "^1.0.0",
"vue": "3.2.37",
"@vue/repl": "^1.3.0",
"file-saver": "^2.0.5",
"jszip": "^3.6.0"
}

View File

@ -1,7 +1,7 @@
<script setup lang="ts">
import Header from './Header.vue'
import { Repl, ReplStore } from '@vue/repl'
import { watchEffect } from 'vue'
import { ref, watchEffect } from 'vue'
const setVH = () => {
document.documentElement.style.setProperty('--vh', window.innerHeight + `px`)
@ -9,29 +9,70 @@ const setVH = () => {
window.addEventListener('resize', setVH)
setVH()
const useDevMode = ref(false)
const useSSRMode = ref(false)
let hash = location.hash.slice(1)
if (hash.startsWith('__DEV__')) {
hash = hash.slice(7)
useDevMode.value = true
}
if (hash.startsWith('__SSR__')) {
hash = hash.slice(7)
useSSRMode.value = true
}
const store = new ReplStore({
serializedState: location.hash.slice(1),
serializedState: hash,
defaultVueRuntimeURL: import.meta.env.PROD
? `${location.origin}/vue.runtime.esm-browser.js`
: `${location.origin}/src/vue-dev-proxy`
: `${location.origin}/src/vue-dev-proxy`,
defaultVueServerRendererURL: import.meta.env.PROD
? `${location.origin}/server-renderer.esm-browser.js`
: `${location.origin}/src/vue-server-renderer-dev-proxy`
})
// enable experimental features
const sfcOptions = {
script: {
inlineTemplate: !useDevMode.value,
reactivityTransform: true
}
}
// persist state
watchEffect(() => history.replaceState({}, '', store.serialize()))
watchEffect(() => {
const newHash = store
.serialize()
.replace(/^#/, useSSRMode.value ? `#__SSR__` : `#`)
.replace(/^#/, useDevMode.value ? `#__DEV__` : `#`)
history.replaceState({}, '', newHash)
})
function toggleDevMode() {
const dev = (useDevMode.value = !useDevMode.value)
sfcOptions.script.inlineTemplate = !dev
store.setFiles(store.getFiles())
}
function toggleSSR() {
useSSRMode.value = !useSSRMode.value
store.setFiles(store.getFiles())
}
</script>
<template>
<Header :store="store" />
<Header
:store="store"
:dev="useDevMode"
:ssr="useSSRMode"
@toggle-dev="toggleDevMode"
@toggle-ssr="toggleSSR"
/>
<Repl
@keydown.ctrl.s.prevent
@keydown.meta.s.prevent
:ssr="useSSRMode"
:store="store"
:showCompileOutput="true"
:autoResize="true"
@ -41,6 +82,10 @@ watchEffect(() => history.replaceState({}, '', store.serialize()))
</template>
<style>
.dark {
color-scheme: dark;
}
body {
font-size: 13px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,

View File

@ -8,7 +8,8 @@ import Download from './icons/Download.vue'
import GitHub from './icons/GitHub.vue'
// @ts-ignore
const { store } = defineProps(['store'])
const props = defineProps(['store', 'dev', 'ssr'])
const { store } = props
const currentCommit = __COMMIT__
const activeVersion = ref(`@${currentCommit}`)
@ -53,6 +54,11 @@ onMounted(async () => {
window.addEventListener('click', () => {
expanded.value = false
})
window.addEventListener('blur', () => {
if (document.activeElement?.tagName === 'IFRAME') {
expanded.value = false
}
});
})
async function fetchVersions(): Promise<string[]> {
@ -93,7 +99,8 @@ async function fetchVersions(): Promise<string[]> {
<div class="links">
<div class="version" @click.stop>
<span class="active-version" @click="toggle">
Version: {{ activeVersion }}
Version
<span class="number">{{ activeVersion }}</span>
</span>
<ul class="versions" :class="{ expanded }">
<li v-if="!publishedVersions"><a>loading versions...</a></li>
@ -112,6 +119,22 @@ async function fetchVersions(): Promise<string[]> {
</li>
</ul>
</div>
<button
title="Toggle development production mode"
class="toggle-dev"
:class="{ dev }"
@click="$emit('toggle-dev')"
>
<span>{{ dev ? 'DEV' : 'PROD' }}</span>
</button>
<button
title="Toggle server rendering mode"
class="toggle-ssr"
:class="{ enabled: ssr }"
@click="$emit('toggle-ssr')"
>
<span>{{ ssr ? 'SSR ON' : 'SSR OFF' }}</span>
</button>
<button title="Toggle dark mode" class="toggle-dark" @click="toggleDark">
<Sun class="light" />
<Moon class="dark" />
@ -126,13 +149,10 @@ async function fetchVersions(): Promise<string[]> {
>
<Download />
</button>
<button
title="View on GitHub"
class="github"
>
<button title="View on GitHub" class="github">
<a
href="https://github.com/vuejs/core/tree/main/packages/sfc-playground"
target="_blank"
href="https://github.com/vuejs/core/tree/main/packages/sfc-playground"
target="_blank"
>
<GitHub />
</a>
@ -146,6 +166,11 @@ nav {
--bg: #fff;
--bg-light: #fff;
--border: #ddd;
--btn: #666;
--highlight: #333;
--green: #3ca877;
--purple: #904cbc;
--btn-bg: #eee;
color: var(--base);
height: var(--nav-height);
@ -164,25 +189,22 @@ nav {
--bg: #1a1a1a;
--bg-light: #242424;
--border: #383838;
--highlight: #fff;
--btn-bg: #333;
box-shadow: none;
border-bottom: 1px solid var(--border);
}
h1 {
margin: 0;
line-height: var(--nav-height);
font-weight: 500;
display: inline-block;
vertical-align: middle;
display: inline-flex;
place-items: center;
}
h1 img {
height: 24px;
vertical-align: middle;
margin-right: 10px;
position: relative;
top: -2px;
}
@media (max-width: 560px) {
@ -202,7 +224,6 @@ h1 img {
}
.version {
display: inline-block;
margin-right: 12px;
position: relative;
}
@ -210,28 +231,53 @@ h1 img {
.active-version {
cursor: pointer;
position: relative;
display: inline-block;
vertical-align: middle;
line-height: var(--nav-height);
padding-right: 15px;
display: inline-flex;
place-items: center;
}
.active-version:after {
.active-version .number {
color: var(--green);
margin-left: 4px;
}
.active-version::after {
content: '';
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 6px solid #aaa;
position: absolute;
right: 0;
top: 22px;
margin-left: 8px;
}
.toggle-dev span,
.toggle-ssr span {
font-size: 12px;
border-radius: 4px;
padding: 4px 6px;
}
.toggle-dev span {
background: var(--purple);
color: #fff;
}
.toggle-dev.dev span {
background: var(--green);
}
.toggle-ssr span {
background-color: var(--btn-bg);
}
.toggle-ssr.enabled span {
color: #fff;
background-color: var(--green);
}
.toggle-dark svg {
width: 18px;
height: 18px;
fill: #666;
}
.toggle-dark .dark,
@ -243,12 +289,22 @@ h1 img {
display: inline-block;
}
.version:hover .active-version:after {
border-top-color: var(--base);
.links button,
.links button a {
color: var(--btn);
}
.dark .version:hover .active-version:after {
border-top-color: #fff;
.links button:hover,
.links button:hover a {
color: var(--highlight);
}
.version:hover .active-version::after {
border-top-color: var(--btn);
}
.dark .version:hover .active-version::after {
border-top-color: var(--highlight);
}
.versions {
@ -276,16 +332,19 @@ h1 img {
}
.versions a:hover {
color: #3ca877;
color: var(--green);
}
.versions.expanded {
display: block;
}
.share,
.download,
.github {
margin: 0 2px;
.links > * {
display: flex;
align-items: center;
}
.links > * + * {
margin-left: 4px;
}
</style>

View File

@ -1,6 +1,6 @@
<template>
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
<g fill="#666">
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24" fill="currentColor">
<g>
<rect x="4" y="18" width="16" height="2" rx="1" ry="1" />
<rect
x="3"

View File

@ -1,7 +1,6 @@
<template>
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24">
<svg width="1.7em" height="1.7em" viewBox="0 0 24 24" fill="currentColor">
<path
fill="#666"
d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"></path>
d="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.5,4.7,2.2,8.9,6.3,10.5C8.7,21.4,9,21.2,9,20.8v-1.6c0,0-0.4,0.1-0.9,0.1 c-1.4,0-2-1.2-2.1-1.9c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1 c0.4,0,0.7-0.1,0.9-0.2c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6 c0,0,1.4,0,2.8,1.3C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4 c0.7,0.8,1.2,1.8,1.2,3c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v2.6c0,0.3,0.3,0.6,0.7,0.5c3.7-1.5,6.3-5.1,6.3-9.3 C22,6.1,16.9,1.4,10.9,2.1z"/>
</svg>
</template>

View File

@ -1,5 +1,5 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z" />
<path fill="currentColor" d="M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z" />
</svg>
</template>

View File

@ -2,7 +2,7 @@
<svg width="1.4em" height="1.4em" viewBox="0 0 24 24">
<g
fill="none"
stroke="#666"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"

View File

@ -1,13 +1,13 @@
<template>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z" />
<path d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z" />
<path d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z" />
<path d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z" />
<path d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z" />
<path d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z" />
<path d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z" />
<path d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z" />
<path d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z" />
<path fill="currentColor" d="M12,18c-3.3,0-6-2.7-6-6s2.7-6,6-6s6,2.7,6,6S15.3,18,12,18zM12,8c-2.2,0-4,1.8-4,4c0,2.2,1.8,4,4,4c2.2,0,4-1.8,4-4C16,9.8,14.2,8,12,8z" />
<path fill="currentColor" d="M12,4c-0.6,0-1-0.4-1-1V1c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,3.6,12.6,4,12,4z" />
<path fill="currentColor" d="M12,24c-0.6,0-1-0.4-1-1v-2c0-0.6,0.4-1,1-1s1,0.4,1,1v2C13,23.6,12.6,24,12,24z" />
<path fill="currentColor" d="M5.6,6.6c-0.3,0-0.5-0.1-0.7-0.3L3.5,4.9c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C6.2,6.5,5.9,6.6,5.6,6.6z" />
<path fill="currentColor" d="M19.8,20.8c-0.3,0-0.5-0.1-0.7-0.3l-1.4-1.4c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l1.4,1.4c0.4,0.4,0.4,1,0,1.4C20.3,20.7,20,20.8,19.8,20.8z" />
<path fill="currentColor" d="M3,13H1c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S3.6,13,3,13z" />
<path fill="currentColor" d="M23,13h-2c-0.6,0-1-0.4-1-1s0.4-1,1-1h2c0.6,0,1,0.4,1,1S23.6,13,23,13z" />
<path fill="currentColor" d="M4.2,20.8c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C4.7,20.7,4.5,20.8,4.2,20.8z" />
<path fill="currentColor" d="M18.4,6.6c-0.3,0-0.5-0.1-0.7-0.3c-0.4-0.4-0.4-1,0-1.4l1.4-1.4c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-1.4,1.4C18.9,6.5,18.6,6.6,18.4,6.6z" />
</svg>
</template>

View File

@ -0,0 +1,2 @@
// serve vue/server-renderer to the iframe sandbox during dev.
export * from 'vue/server-renderer'

View File

@ -21,21 +21,24 @@ function copyVuePlugin(): Plugin {
return {
name: 'copy-vue',
generateBundle() {
const filePath = path.resolve(
__dirname,
'../vue/dist/vue.runtime.esm-browser.js'
)
if (!fs.existsSync(filePath)) {
throw new Error(
`vue.runtime.esm-browser.js not built. ` +
`Run "nr build vue -f esm-browser" first.`
)
const copyFile = (file: string) => {
const filePath = path.resolve(__dirname, file)
const basename = path.basename(file)
if (!fs.existsSync(filePath)) {
throw new Error(
`${basename} not built. ` +
`Run "nr build vue -f esm-browser" first.`
)
}
this.emitFile({
type: 'asset',
fileName: basename,
source: fs.readFileSync(filePath, 'utf-8')
})
}
this.emitFile({
type: 'asset',
fileName: 'vue.runtime.esm-browser.js',
source: fs.readFileSync(filePath, 'utf-8')
})
copyFile(`../vue/dist/vue.runtime.esm-browser.js`)
copyFile(`../server-renderer/dist/server-renderer.esm-browser.js`)
}
}
}

View File

@ -1,6 +1,6 @@
{
"name": "@vue/shared",
"version": "3.2.33",
"version": "3.2.37",
"description": "internal utils shared across @vue packages",
"main": "index.js",
"module": "dist/shared.esm-bundler.js",

View File

@ -52,7 +52,7 @@ export const isMap = (val: unknown): val is Map<any, any> =>
export const isSet = (val: unknown): val is Set<any> =>
toTypeString(val) === '[object Set]'
export const isDate = (val: unknown): val is Date => val instanceof Date
export const isDate = (val: unknown): val is Date => toTypeString(val) === '[object Date]'
export const isFunction = (val: unknown): val is Function =>
typeof val === 'function'
export const isString = (val: unknown): val is string => typeof val === 'string'

View File

@ -1,6 +1,6 @@
{
"name": "@vue/size-check",
"version": "3.2.33",
"version": "3.2.37",
"private": true,
"scripts": {
"build": "vite build"

View File

@ -1,6 +1,6 @@
{
"name": "@vue/template-explorer",
"version": "3.2.33",
"version": "3.2.37",
"private": true,
"buildOptions": {
"formats": [

View File

@ -1,6 +1,6 @@
import Vue from '@vue/compat'
import { effect, isReactive } from '@vue/reactivity'
import { nextTick } from '@vue/runtime-core'
import { h, nextTick } from '@vue/runtime-core'
import {
DeprecationTypes,
deprecationData,
@ -448,3 +448,57 @@ test('global asset registration should affect apps created via createApp', () =>
expect(vm.$el.textContent).toBe('foo')
delete singletonApp._context.components.foo
})
test('post-facto global asset registration should affect apps created via createApp', () => {
const app = createApp({
template: '<foo/>'
})
Vue.component('foo', { template: 'foo' })
const vm = app.mount(document.createElement('div')) as any
expect(vm.$el.textContent).toBe('foo')
delete singletonApp._context.components.foo
})
test('local asset registration should not affect other local apps', () => {
const app1 = createApp({})
const app2 = createApp({})
app1.component('foo', {})
app2.component('foo', {})
expect(
`Component "foo" has already been registered in target app`
).not.toHaveBeenWarned()
})
test('local app-level mixin registration should not affect other local apps', () => {
const app1 = createApp({ render: () => h('div') })
const app2 = createApp({})
const mixin = { created: jest.fn() }
app1.mixin(mixin)
app2.mixin(mixin)
expect(`Mixin has already been applied`).not.toHaveBeenWarned()
app1.mount(document.createElement('div'))
expect(mixin.created).toHaveBeenCalledTimes(1)
})
// #5699
test('local app config should not affect other local apps in v3 mode', () => {
Vue.configureCompat({ MODE: 3 })
const app1 = createApp({
render: () => h('div'),
provide() {
return {
test: 123
}
}
})
app1.config.globalProperties.test = () => {}
app1.mount(document.createElement('div'))
const app2 = createApp({})
expect(app2.config.globalProperties.test).toBe(undefined)
})

View File

@ -1,6 +1,6 @@
{
"name": "@vue/compat",
"version": "3.2.33",
"version": "3.2.37",
"description": "Vue 3 compatibility build for Vue 2",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",
@ -38,6 +38,6 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/vue-compat#readme",
"peerDependencies": {
"vue": "3.2.33"
"vue": "3.2.37"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "vue",
"version": "3.2.33",
"version": "3.2.37",
"description": "The progressive JavaScript framework for building modern web UI.",
"main": "index.js",
"module": "dist/vue.runtime.esm-bundler.js",
@ -66,10 +66,10 @@
},
"homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme",
"dependencies": {
"@vue/shared": "3.2.33",
"@vue/compiler-dom": "3.2.33",
"@vue/runtime-dom": "3.2.33",
"@vue/compiler-sfc": "3.2.33",
"@vue/server-renderer": "3.2.33"
"@vue/shared": "3.2.37",
"@vue/compiler-dom": "3.2.37",
"@vue/runtime-dom": "3.2.37",
"@vue/compiler-sfc": "3.2.37",
"@vue/server-renderer": "3.2.37"
}
}

Some files were not shown because too many files have changed in this diff Show More