diff --git a/.eslintrc.js b/.eslintrc.js index 8485d76f53e..cdbb1dffd91 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -69,6 +69,14 @@ module.exports = { 'no-restricted-globals': ['error', ...NodeGlobals], 'no-restricted-syntax': 'off' } + }, + // Node scripts + { + files: ['scripts/**', './*.js', 'packages/**/index.js', 'packages/size-check/**'], + rules: { + 'no-restricted-globals': 'off', + 'no-restricted-syntax': 'off' + } } ] } diff --git a/.github/contributing.md b/.github/contributing.md index 5f4e64b7e1e..fe2ac7c114b 100644 --- a/.github/contributing.md +++ b/.github/contributing.md @@ -40,6 +40,20 @@ Hi! I'm really excited that you are interested in contributing to Vue.js. Before - No need to worry about code style as long as you have installed the dev dependencies - modified files are automatically formatted with Prettier on commit (by invoking [Git Hooks](https://git-scm.com/docs/githooks) via [yorkie](https://github.com/yyx990803/yorkie)). +### Advanced Pull Request Tips + +- The PR should fix the intended bug **only** and not introduce unrelated changes. This includes unnecessary refactors - a PR should focus on the fix and not code style, this makes it easier to trace changes in the future. + +- Consider the performance / size impact of the changes, and whether the bug being fixes justifies the cost. If the bug being fixed is a very niche edge case, we should try to minimize the size / perf cost to make it worthwhile. + + - Is the code perf-sensitive (e.g. in "hot paths" like component updates or the vdom patch function?) + - If the branch is dev-only, performance is less of a concern. + + - Check how much extra bundle size the change introduces. + - Make sure to put dev-only code in `__DEV__` branches so they are tree-shakable. + - Runtime code is more sensitive to size increase than compiler code. + - Make sure it doesn't accidentally cause dev-only or compiler-only code branches to be included in the runtime build. Notable case is that some functions in `@vue/shared` are compiler-only and should not be used in runtime code, e.g. `isHTMLTag` and `isSVGTag`. + ## Development Setup You will need [Node.js](https://nodejs.org) **version 16+**, and [PNPM](https://pnpm.io) **version 7+**. diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eadcd94f6d8..3128dd2cb4f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,10 @@ on: pull_request: branches: - main + +permissions: + contents: read # to fetch code (actions/checkout) + jobs: unit-test: runs-on: ubuntu-latest diff --git a/.github/workflows/release-tag.yml b/.github/workflows/release-tag.yml index d9ea7a07f72..16c6c9c5c10 100644 --- a/.github/workflows/release-tag.yml +++ b/.github/workflows/release-tag.yml @@ -5,8 +5,12 @@ on: name: Create Release +permissions: {} jobs: build: + permissions: + contents: write # to create release (yyx990803/release-tag) + name: Create Release runs-on: ubuntu-latest steps: diff --git a/.gitignore b/.gitignore index 1487e3b7c21..75c8139bd9b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ explorations TODOs.md *.log .idea +.eslintcache diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e55d92841..2fef732631a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,39 @@ +## [3.2.41](https://github.com/vuejs/core/compare/v3.2.40...v3.2.41) (2022-10-14) + + +### Bug Fixes + +* **devtools:** avoid memory leak caused by devtools event buffer ([24f4c47](https://github.com/vuejs/core/commit/24f4c479d661698afd967cf428f9439be4578a04)), closes [#6591](https://github.com/vuejs/core/issues/6591) +* **devtools:** use cleanupBuffer instead of modifying _buffer ([#6812](https://github.com/vuejs/core/issues/6812)) ([35a113e](https://github.com/vuejs/core/commit/35a113eda43a49e921a6eb60d45db81dc847d665)) +* **effectScope:** calling off() of a detached scope should not break currentScope ([a71f9ac](https://github.com/vuejs/core/commit/a71f9ac41af464fdb69220e69c50739dd3a8f365)) +* **runtime-core:** ensure that errors in slot function execution do not affect block tracking ([#5670](https://github.com/vuejs/core/issues/5670)) ([82a73da](https://github.com/vuejs/core/commit/82a73da351bb5d26735f734ae2540a3033c00c9e)), closes [#5657](https://github.com/vuejs/core/issues/5657) +* **runtime-core:** fix v-for ref reactivity behavior difference between prod and dev ([#6714](https://github.com/vuejs/core/issues/6714)) ([9ae796d](https://github.com/vuejs/core/commit/9ae796d1567f1b0acb08659a2363a54b525a9ee4)), closes [#6697](https://github.com/vuejs/core/issues/6697) +* **runtime-dom:** fix event timestamp check in iframes ([5ee4053](https://github.com/vuejs/core/commit/5ee40532a63e0b792e0c1eccf3cf68546a4e23e9)), closes [#2513](https://github.com/vuejs/core/issues/2513) [#3933](https://github.com/vuejs/core/issues/3933) [#5474](https://github.com/vuejs/core/issues/5474) + + + +## [3.2.40](https://github.com/vuejs/core/compare/v3.2.39...v3.2.40) (2022-09-28) + + +### Bug Fixes + +* **compat:** list cjs dependencies for compat build ([96cd924](https://github.com/vuejs/core/commit/96cd924e440984a37e4759673f3c16921b69affe)), closes [#6602](https://github.com/vuejs/core/issues/6602) +* **compiler-dom:** remove v-bind boolean attribute with literal false value when stringifying ([#6635](https://github.com/vuejs/core/issues/6635)) ([6c6fe2c](https://github.com/vuejs/core/commit/6c6fe2c0cd89ce513503b1f85e0ddb696fd81e54)), closes [#6617](https://github.com/vuejs/core/issues/6617) +* **compiler-sfc:** fix expression check for v-on with object literal value ([#6652](https://github.com/vuejs/core/issues/6652)) ([6958ec1](https://github.com/vuejs/core/commit/6958ec1b37fb4a9244ae222a35fcac032d26ad8a)), closes [#6650](https://github.com/vuejs/core/issues/6650) [#6674](https://github.com/vuejs/core/issues/6674) +* **compilre-core:** dynamic v-on and static v-on should be merged ([#6747](https://github.com/vuejs/core/issues/6747)) ([f9d43b9](https://github.com/vuejs/core/commit/f9d43b99f83af7fc140938a1d8d2db89666fb4e1)), closes [#6742](https://github.com/vuejs/core/issues/6742) +* **runtime-core:** avoid hoisted vnodes retaining detached DOM nodes ([fc5bdb3](https://github.com/vuejs/core/commit/fc5bdb36ed429d6c3c956f373206ce75467adaf3)), closes [#6591](https://github.com/vuejs/core/issues/6591) +* **runtime-core:** Lifecycle hooks should support callbacks shared by reference ([#6687](https://github.com/vuejs/core/issues/6687)) ([c71a08e](https://github.com/vuejs/core/commit/c71a08e6fd44ee06c6b4f61d67727a7b7503605e)), closes [#6686](https://github.com/vuejs/core/issues/6686) +* **runtime-core:** remove prod-only hoisted clone behavior for manual DOM manipulation compat ([aa70188](https://github.com/vuejs/core/commit/aa70188c41fab1a4139748dd7b7c71532d063f3a)), closes [#6727](https://github.com/vuejs/core/issues/6727) [#6739](https://github.com/vuejs/core/issues/6739) +* **runtime-core:** unset removed props first in full diff mode ([c0d8db8](https://github.com/vuejs/core/commit/c0d8db81a636f0ad1e725b7c04608d3a211cf163)), closes [#6571](https://github.com/vuejs/core/issues/6571) +* **runtime-dom:** fix unnecessary warning when setting coerced dom property value ([b1817fe](https://github.com/vuejs/core/commit/b1817fe9eeb66a18f405ada9072149515654a9bd)), closes [#6616](https://github.com/vuejs/core/issues/6616) +* **ssr:** avoid ast.helpers duplication ([#6664](https://github.com/vuejs/core/issues/6664)) ([57ffc3e](https://github.com/vuejs/core/commit/57ffc3e546395ba048009396a4b82d3f968cca2c)) +* **ssr:** fix dynamic slot regression in ssr ([8963c55](https://github.com/vuejs/core/commit/8963c5508cde3a0c990b2748787ffb582b16f23f)), closes [#6651](https://github.com/vuejs/core/issues/6651) +* **ssr:** fix hydration mismatch when entire multi-root template is stringified ([9698dd3](https://github.com/vuejs/core/commit/9698dd3cf1dfdb95d4dc4b4f7bd24ff94b4b5d84)), closes [#6637](https://github.com/vuejs/core/issues/6637) +* **ssr:** fix pre tag windows newline hydration mismatch ([0382019](https://github.com/vuejs/core/commit/03820193a8f768293d665ca2753439fe73aed0fd)), closes [#6410](https://github.com/vuejs/core/issues/6410) +* **ssr:** respect case when rendering dynamic attrs on svg ([121eb32](https://github.com/vuejs/core/commit/121eb32fb0a21cf9988d788cfad1b4249b15997b)), closes [#6755](https://github.com/vuejs/core/issues/6755) + + + ## [3.2.39](https://github.com/vuejs/core/compare/v3.2.38...v3.2.39) (2022-09-08) diff --git a/package.json b/package.json index f0a2b538978..f6dff7e7d7a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.2.39", + "version": "3.2.41", "packageManager": "pnpm@7.1.0", "scripts": { "dev": "node scripts/dev.js", @@ -8,8 +8,8 @@ "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,__tests__}/**.ts", - "format": "prettier --write --parser typescript \"packages/**/*.ts?(x)\"", + "lint": "eslint --cache --ext .ts packages/*/{src,__tests__}/**.ts", + "format": "prettier --write --cache --parser typescript \"packages/**/*.ts?(x)\"", "test": "run-s \"test-unit {@}\" \"test-e2e {@}\"", "test-unit": "jest --filter ./scripts/filter-unit.js", "test-e2e": "node scripts/build.js vue -f global -d && jest --filter ./scripts/filter-e2e.js --runInBand", @@ -80,7 +80,7 @@ "marked": "^4.0.10", "minimist": "^1.2.0", "npm-run-all": "^4.1.5", - "prettier": "^2.3.1", + "prettier": "^2.7.1", "puppeteer": "^10.0.0", "rollup": "~2.38.5", "rollup-plugin-node-builtins": "^2.1.2", diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index bce9000feca..f9415296ace 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.2.39", + "version": "3.2.41", "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.39", + "@vue/shared": "3.2.41", "@babel/parser": "^7.16.4", "estree-walker": "^2.0.2", "source-map": "^0.6.1" diff --git a/packages/compiler-core/src/index.ts b/packages/compiler-core/src/index.ts index 6ed7aa5b897..6a1f8b63b57 100644 --- a/packages/compiler-core/src/index.ts +++ b/packages/compiler-core/src/index.ts @@ -43,7 +43,8 @@ export { processIf } from './transforms/vIf' export { processFor, createForLoopParams } from './transforms/vFor' export { transformExpression, - processExpression + processExpression, + stringifyExpression } from './transforms/transformExpression' export { buildSlots, diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts index 5cdd9ae3eda..2f904098e60 100644 --- a/packages/compiler-core/src/parse.ts +++ b/packages/compiler-core/src/parse.ts @@ -257,34 +257,41 @@ function parseChildren( const shouldCondense = context.options.whitespace !== 'preserve' for (let i = 0; i < nodes.length; i++) { const node = nodes[i] - if (!context.inPre && node.type === NodeTypes.TEXT) { - if (!/[^\t\r\n\f ]/.test(node.content)) { - const prev = nodes[i - 1] - const next = nodes[i + 1] - // Remove if: - // - the whitespace is the first or last node, or: - // - (condense mode) the whitespace is adjacent to a comment, or: - // - (condense mode) the whitespace is between two elements AND contains newline - if ( - !prev || - !next || - (shouldCondense && - (prev.type === NodeTypes.COMMENT || - next.type === NodeTypes.COMMENT || - (prev.type === NodeTypes.ELEMENT && - next.type === NodeTypes.ELEMENT && - /[\r\n]/.test(node.content)))) - ) { - removedWhitespace = true - nodes[i] = null as any - } else { - // Otherwise, the whitespace is condensed into a single space - node.content = ' ' + if (node.type === NodeTypes.TEXT) { + if (!context.inPre) { + if (!/[^\t\r\n\f ]/.test(node.content)) { + const prev = nodes[i - 1] + const next = nodes[i + 1] + // Remove if: + // - the whitespace is the first or last node, or: + // - (condense mode) the whitespace is adjacent to a comment, or: + // - (condense mode) the whitespace is between two elements AND contains newline + if ( + !prev || + !next || + (shouldCondense && + (prev.type === NodeTypes.COMMENT || + next.type === NodeTypes.COMMENT || + (prev.type === NodeTypes.ELEMENT && + next.type === NodeTypes.ELEMENT && + /[\r\n]/.test(node.content)))) + ) { + removedWhitespace = true + nodes[i] = null as any + } else { + // Otherwise, the whitespace is condensed into a single space + node.content = ' ' + } + } else if (shouldCondense) { + // in condense mode, consecutive whitespaces in text are condensed + // down to a single space. + node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ') } - } else if (shouldCondense) { - // in condense mode, consecutive whitespaces in text are condensed - // down to a single space. - node.content = node.content.replace(/[\t\r\n\f ]+/g, ' ') + } else { + // #6410 normalize windows newlines in
:
+          // in SSR, browsers normalize server-rendered \r\n into a single \n
+          // in the DOM
+          node.content = node.content.replace(/\r\n/g, '\n')
         }
       }
       // Remove comment nodes if desired by configuration.
diff --git a/packages/compiler-core/src/runtimeHelpers.ts b/packages/compiler-core/src/runtimeHelpers.ts
index 3bfe73935b8..3f5ef024797 100644
--- a/packages/compiler-core/src/runtimeHelpers.ts
+++ b/packages/compiler-core/src/runtimeHelpers.ts
@@ -42,8 +42,7 @@ export const IS_MEMO_SAME = Symbol(__DEV__ ? `isMemoSame` : ``)
 
 // Name mapping for runtime helpers that need to be imported from 'vue' in
 // generated code. Make sure these are correctly exported in the runtime!
-// Using `any` here because TS doesn't allow symbols as index type.
-export const helperNameMap: any = {
+export const helperNameMap: Record = {
   [FRAGMENT]: `Fragment`,
   [TELEPORT]: `Teleport`,
   [SUSPENSE]: `Suspense`,
@@ -85,7 +84,7 @@ export const helperNameMap: any = {
   [IS_MEMO_SAME]: `isMemoSame`
 }
 
-export function registerRuntimeHelpers(helpers: any) {
+export function registerRuntimeHelpers(helpers: Record) {
   Object.getOwnPropertySymbols(helpers).forEach(s => {
     helperNameMap[s] = helpers[s]
   })
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 0eb3bb57628..7b53b24822c 100644
--- a/packages/compiler-core/src/transforms/transformElement.ts
+++ b/packages/compiler-core/src/transforms/transformElement.ts
@@ -413,6 +413,16 @@ export function buildProps(
   let hasVnodeHook = false
   const dynamicPropNames: string[] = []
 
+  const pushMergeArg = (arg?: PropsExpression) => {
+    if (properties.length) {
+      mergeArgs.push(
+        createObjectExpression(dedupeProperties(properties), elementLoc)
+      )
+      properties = []
+    }
+    if (arg) mergeArgs.push(arg)
+  }
+
   const analyzePatchFlag = ({ key, value }: Property) => {
     if (isStaticExp(key)) {
       const name = key.content
@@ -590,13 +600,9 @@ export function buildProps(
       if (!arg && (isVBind || isVOn)) {
         hasDynamicKeys = true
         if (exp) {
-          if (properties.length) {
-            mergeArgs.push(
-              createObjectExpression(dedupeProperties(properties), elementLoc)
-            )
-            properties = []
-          }
           if (isVBind) {
+            // have to merge early for compat build check
+            pushMergeArg()
             if (__COMPAT__) {
               // 2.x v-bind object order compat
               if (__DEV__) {
@@ -643,7 +649,7 @@ export function buildProps(
             mergeArgs.push(exp)
           } else {
             // v-on="obj" -> toHandlers(obj)
-            mergeArgs.push({
+            pushMergeArg({
               type: NodeTypes.JS_CALL_EXPRESSION,
               loc,
               callee: context.helper(TO_HANDLERS),
@@ -668,7 +674,11 @@ export function buildProps(
         // has built-in directive transform.
         const { props, needRuntime } = directiveTransform(prop, node, context)
         !ssr && props.forEach(analyzePatchFlag)
-        properties.push(...props)
+        if (isVOn && arg && !isStaticExp(arg)) {
+          pushMergeArg(createObjectExpression(props, elementLoc))
+        } else {
+          properties.push(...props)
+        }
         if (needRuntime) {
           runtimeDirectives.push(prop)
           if (isSymbol(needRuntime)) {
@@ -691,11 +701,8 @@ export function buildProps(
 
   // has v-bind="object" or v-on="object", wrap with mergeProps
   if (mergeArgs.length) {
-    if (properties.length) {
-      mergeArgs.push(
-        createObjectExpression(dedupeProperties(properties), elementLoc)
-      )
-    }
+    // close up any not-yet-merged props
+    pushMergeArg()
     if (mergeArgs.length > 1) {
       propsExpression = createCallExpression(
         context.helper(MERGE_PROPS),
diff --git a/packages/compiler-core/src/transforms/transformExpression.ts b/packages/compiler-core/src/transforms/transformExpression.ts
index e4311ad4f88..43c69559688 100644
--- a/packages/compiler-core/src/transforms/transformExpression.ts
+++ b/packages/compiler-core/src/transforms/transformExpression.ts
@@ -361,7 +361,7 @@ function canPrefix(id: Identifier) {
   return true
 }
 
-function stringifyExpression(exp: ExpressionNode | string): string {
+export function stringifyExpression(exp: ExpressionNode | string): string {
   if (isString(exp)) {
     return exp
   } else if (exp.type === NodeTypes.SIMPLE_EXPRESSION) {
diff --git a/packages/compiler-core/src/transforms/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts
index a9dfe77eff7..ed39ecee12f 100644
--- a/packages/compiler-core/src/transforms/vOn.ts
+++ b/packages/compiler-core/src/transforms/vOn.ts
@@ -54,9 +54,9 @@ export const transformOn: DirectiveTransform = (
           ? // for component and vnode lifecycle event listeners, auto convert
             // it to camelCase. See issue #2249
             toHandlerKey(camelize(rawName))
-            // preserve case for plain element listeners that have uppercase
+          : // preserve case for plain element listeners that have uppercase
             // letters, as these may be custom elements' custom events
-          : `on:${rawName}`
+            `on:${rawName}`
       eventName = createSimpleExpression(eventString, true, arg.loc)
     } else {
       // #2388
diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json
index 5980182cc34..359be350e3a 100644
--- a/packages/compiler-dom/package.json
+++ b/packages/compiler-dom/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-dom",
-  "version": "3.2.39",
+  "version": "3.2.41",
   "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.39",
-    "@vue/compiler-core": "3.2.39"
+    "@vue/shared": "3.2.41",
+    "@vue/compiler-core": "3.2.41"
   }
 }
diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap
index 4dfc212e02a..9d04c5159a9 100644
--- a/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap
+++ b/packages/compiler-sfc/__tests__/__snapshots__/compileTemplate.spec.ts.snap
@@ -1,5 +1,17 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`dynamic v-on + static v-on should merged 1`] = `
+"import { toHandlerKey as _toHandlerKey, mergeProps as _mergeProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from \\"vue\\"
+
+export function render(_ctx, _cache) {
+  return (_openBlock(), _createElementBlock(\\"input\\", _mergeProps({
+    onBlur: _cache[0] || (_cache[0] = (...args) => (_ctx.onBlur && _ctx.onBlur(...args)))
+  }, {
+    [_toHandlerKey(_ctx.validateEvent)]: _cache[1] || (_cache[1] = (...args) => (_ctx.onValidateEvent && _ctx.onValidateEvent(...args)))
+  }), null, 16 /* FULL_PROPS */))
+}"
+`;
+
 exports[`should not hoist srcset URLs in SSR mode 1`] = `
 "import { resolveComponent as _resolveComponent, withCtx as _withCtx, createVNode as _createVNode } from \\"vue\\"
 import { ssrRenderAttr as _ssrRenderAttr, ssrRenderComponent as _ssrRenderComponent } from \\"vue/server-renderer\\"
diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
index 2beda880b5b..f58b6338de9 100644
--- a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
@@ -174,3 +174,12 @@ test('should not hoist srcset URLs in SSR mode', () => {
   })
   expect(code).toMatchSnapshot()
 })
+
+// #6742
+test('dynamic v-on + static v-on should merged', () => {
+  const source = ``
+
+  const result = compile({ filename: 'example.vue', source })
+
+  expect(result.code).toMatchSnapshot()
+})
diff --git a/packages/compiler-sfc/__tests__/rewriteDefault.spec.ts b/packages/compiler-sfc/__tests__/rewriteDefault.spec.ts
index 9fb4c64bbb3..40561da17db 100644
--- a/packages/compiler-sfc/__tests__/rewriteDefault.spec.ts
+++ b/packages/compiler-sfc/__tests__/rewriteDefault.spec.ts
@@ -206,7 +206,10 @@ describe('compiler sfc: rewriteDefault', () => {
 
   test('@Component\nexport default class w/ comments', async () => {
     expect(
-      rewriteDefault(`// export default\n@Component\nexport default class Foo {}`, 'script')
+      rewriteDefault(
+        `// export default\n@Component\nexport default class Foo {}`,
+        'script'
+      )
     ).toMatchInlineSnapshot(`
       "// export default
       @Component
@@ -231,7 +234,8 @@ describe('compiler sfc: rewriteDefault', () => {
   test('@Component\nexport default class w/ comments 3', async () => {
     expect(
       rewriteDefault(
-        `/*\n@Component\nexport default class Foo {}*/\n` + `export default class Bar {}`,
+        `/*\n@Component\nexport default class Foo {}*/\n` +
+          `export default class Bar {}`,
         'script'
       )
     ).toMatchInlineSnapshot(`
diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json
index 5c8bd86eb25..ca50683bbe4 100644
--- a/packages/compiler-sfc/package.json
+++ b/packages/compiler-sfc/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-sfc",
-  "version": "3.2.39",
+  "version": "3.2.41",
   "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.39",
-    "@vue/compiler-dom": "3.2.39",
-    "@vue/compiler-ssr": "3.2.39",
-    "@vue/reactivity-transform": "3.2.39",
-    "@vue/shared": "3.2.39",
+    "@vue/compiler-core": "3.2.41",
+    "@vue/compiler-dom": "3.2.41",
+    "@vue/compiler-ssr": "3.2.41",
+    "@vue/reactivity-transform": "3.2.41",
+    "@vue/shared": "3.2.41",
     "estree-walker": "^2.0.2",
     "magic-string": "^0.25.7",
     "source-map": "^0.6.1",
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index 5a77e8a9463..a21f94cba71 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -2135,7 +2135,7 @@ function processExp(exp: string, dir?: string): string {
     if (dir === 'slot') {
       exp = `(${exp})=>{}`
     } else if (dir === 'on') {
-      exp = `()=>{${exp}}`
+      exp = `()=>{return ${exp}}`
     } else if (dir === 'for') {
       const inMatch = exp.match(forAliasRE)
       if (inMatch) {
diff --git a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
index 5d5191ffb44..cd21e48cb9a 100644
--- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
+++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts
@@ -104,6 +104,11 @@ describe('ssr: components', () => {
       `)
     })
 
+    test('empty attribute should not produce syntax error', () => {
+      // previously this would produce syntax error `default: _withCtx((, _push, ...)`
+      expect(compile(`foo`).code).not.toMatch(`(,`)
+    })
+
     test('named slots', () => {
       expect(
         compile(`
diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json
index 5eb7a2b3b2d..f7f40544f2a 100644
--- a/packages/compiler-ssr/package.json
+++ b/packages/compiler-ssr/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@vue/compiler-ssr",
-  "version": "3.2.39",
+  "version": "3.2.41",
   "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.39",
-    "@vue/compiler-dom": "3.2.39"
+    "@vue/shared": "3.2.41",
+    "@vue/compiler-dom": "3.2.41"
   }
 }
diff --git a/packages/compiler-ssr/src/ssrCodegenTransform.ts b/packages/compiler-ssr/src/ssrCodegenTransform.ts
index 4d142162073..d7d1645a0e7 100644
--- a/packages/compiler-ssr/src/ssrCodegenTransform.ts
+++ b/packages/compiler-ssr/src/ssrCodegenTransform.ts
@@ -49,8 +49,7 @@ export function ssrCodegenTransform(ast: RootNode, options: CompilerOptions) {
       createCompoundExpression([`const _cssVars = { style: `, varsExp, `}`])
     )
     Array.from(cssContext.helpers.keys()).forEach(helper => {
-      if (!ast.helpers.includes(helper))
-        ast.helpers.push(helper)
+      if (!ast.helpers.includes(helper)) ast.helpers.push(helper)
     })
   }
 
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
index df190c768a8..dc8c6a4ae4f 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformComponent.ts
@@ -36,7 +36,8 @@ import {
   CallExpression,
   JSChildNode,
   RESOLVE_DYNAMIC_COMPONENT,
-  TRANSITION
+  TRANSITION,
+  stringifyExpression
 } from '@vue/compiler-dom'
 import { SSR_RENDER_COMPONENT, SSR_RENDER_VNODE } from '../runtimeHelpers'
 import {
@@ -145,8 +146,9 @@ export const ssrTransformComponent: NodeTransform = (node, context) => {
     wipMap.set(node, wipEntries)
 
     const buildSSRSlotFn: SlotFnBuilder = (props, children, loc) => {
+      const param0 = (props && stringifyExpression(props)) || `_`
       const fn = createFunctionExpression(
-        [props || `_`, `_push`, `_parent`, `_scopeId`],
+        [param0, `_push`, `_parent`, `_scopeId`],
         undefined, // no return, assign body later
         true, // newline
         true, // isSlot
diff --git a/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts b/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts
index dedf1f64075..00b0d9dd45a 100644
--- a/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts
+++ b/packages/compiler-ssr/src/transforms/ssrTransformTransitionGroup.ts
@@ -33,8 +33,8 @@ export function ssrTransformTransitionGroup(
         node,
         context,
         otherProps,
-        true, /* isComponent */
-        false, /* isDynamicComponent */
+        true /* isComponent */,
+        false /* isDynamicComponent */,
         true /* ssr (skip event listeners) */
       )
       let propsExp = null
diff --git a/packages/reactivity-transform/package.json b/packages/reactivity-transform/package.json
index 27c5c6d1e54..0cf8340031d 100644
--- a/packages/reactivity-transform/package.json
+++ b/packages/reactivity-transform/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@vue/reactivity-transform",
-  "version": "3.2.39",
+  "version": "3.2.41",
   "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.39",
-    "@vue/shared": "3.2.39",
+    "@vue/compiler-core": "3.2.41",
+    "@vue/shared": "3.2.41",
     "estree-walker": "^2.0.2",
     "magic-string": "^0.25.7"
   },
diff --git a/packages/reactivity/__tests__/effect.spec.ts b/packages/reactivity/__tests__/effect.spec.ts
index a66aff278a4..a322c7209f4 100644
--- a/packages/reactivity/__tests__/effect.spec.ts
+++ b/packages/reactivity/__tests__/effect.spec.ts
@@ -922,6 +922,22 @@ describe('reactivity/effect', () => {
     expect(fnSpy2).toHaveBeenCalledTimes(1)
   })
 
+  it('should be triggered when set length with string', () => {
+    let ret1 = 'idle'
+    let ret2 = 'idle'
+    const arr1 = reactive(new Array(11).fill(0))
+    const arr2 = reactive(new Array(11).fill(0))
+    effect(() => {
+      ret1 = arr1[10] === undefined ? 'arr[10] is set to empty' : 'idle'
+    })
+    effect(() => {
+      ret2 = arr2[10] === undefined ? 'arr[10] is set to empty' : 'idle'
+    })
+    arr1.length = 2
+    arr2.length = '2' as any
+    expect(ret1).toBe(ret2)
+  })
+
   describe('readonly + reactive for Map', () => {
     test('should work with readonly(reactive(Map))', () => {
       const m = reactive(new Map())
diff --git a/packages/reactivity/__tests__/effectScope.spec.ts b/packages/reactivity/__tests__/effectScope.spec.ts
index af92c97f94c..28579fef028 100644
--- a/packages/reactivity/__tests__/effectScope.spec.ts
+++ b/packages/reactivity/__tests__/effectScope.spec.ts
@@ -277,4 +277,15 @@ describe('reactivity/effect/scope', () => {
       expect(getCurrentScope()).toBe(currentScope)
     })
   })
+
+  it('calling .off() of a detached scope inside an active scope should not break currentScope', () => {
+    const parentScope = new EffectScope()
+
+    parentScope.run(() => {
+      const childScope = new EffectScope(true)
+      childScope.on()
+      childScope.off()
+      expect(getCurrentScope()).toBe(parentScope)
+    })
+  })
 })
diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json
index d0bbf289aa5..df1b379801e 100644
--- a/packages/reactivity/package.json
+++ b/packages/reactivity/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@vue/reactivity",
-  "version": "3.2.39",
+  "version": "3.2.41",
   "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.39"
+    "@vue/shared": "3.2.41"
   }
 }
diff --git a/packages/reactivity/src/effect.ts b/packages/reactivity/src/effect.ts
index 34b53eb8fef..f1799a62d3a 100644
--- a/packages/reactivity/src/effect.ts
+++ b/packages/reactivity/src/effect.ts
@@ -1,5 +1,5 @@
 import { TrackOpTypes, TriggerOpTypes } from './operations'
-import { extend, isArray, isIntegerKey, isMap } from '@vue/shared'
+import { extend, isArray, isIntegerKey, isMap, toNumber } from '@vue/shared'
 import { EffectScope, recordEffectScope } from './effectScope'
 import {
   createDep,
@@ -276,8 +276,9 @@ export function trigger(
     // trigger all effects for target
     deps = [...depsMap.values()]
   } else if (key === 'length' && isArray(target)) {
+    const newLength = toNumber(newValue)
     depsMap.forEach((dep, key) => {
-      if (key === 'length' || key >= (newValue as number)) {
+      if (key === 'length' || key >= newLength) {
         deps.push(dep)
       }
     })
diff --git a/packages/reactivity/src/effectScope.ts b/packages/reactivity/src/effectScope.ts
index f4396e8f3ab..557a1379492 100644
--- a/packages/reactivity/src/effectScope.ts
+++ b/packages/reactivity/src/effectScope.ts
@@ -34,9 +34,9 @@ export class EffectScope {
    */
   private index: number | undefined
 
-  constructor(detached = false) {
+  constructor(public detached = false) {
+    this.parent = activeEffectScope
     if (!detached && activeEffectScope) {
-      this.parent = activeEffectScope
       this.index =
         (activeEffectScope.scopes || (activeEffectScope.scopes = [])).push(
           this
@@ -89,7 +89,7 @@ export class EffectScope {
         }
       }
       // nested scope, dereference from parent to avoid memory leaks
-      if (this.parent && !fromParent) {
+      if (!this.detached && this.parent && !fromParent) {
         // optimized O(1) removal
         const last = this.parent.scopes!.pop()
         if (last && last !== this) {
@@ -97,6 +97,7 @@ export class EffectScope {
           last.index = this.index!
         }
       }
+      this.parent = undefined
       this.active = false
     }
   }
diff --git a/packages/runtime-core/__tests__/apiOptions.spec.ts b/packages/runtime-core/__tests__/apiOptions.spec.ts
index da5fe346c32..96601b7870f 100644
--- a/packages/runtime-core/__tests__/apiOptions.spec.ts
+++ b/packages/runtime-core/__tests__/apiOptions.spec.ts
@@ -1048,7 +1048,6 @@ describe('api: options', () => {
     expect(root.innerHTML).toBe(`

Foo

`) }) - test('options defined in component have higher priority', async () => { const Mixin = { msg1: 'base' diff --git a/packages/runtime-core/__tests__/apiWatch.spec.ts b/packages/runtime-core/__tests__/apiWatch.spec.ts index 86ad948adf6..c9db3657367 100644 --- a/packages/runtime-core/__tests__/apiWatch.spec.ts +++ b/packages/runtime-core/__tests__/apiWatch.spec.ts @@ -177,6 +177,23 @@ describe('api: watch', () => { ]) }) + it('watching multiple sources: undefined initial values and immediate: true', async () => { + const a = ref() + const b = ref() + let called = false + watch( + [a, b], + (newVal, oldVal) => { + called = true + expect(newVal).toMatchObject([undefined, undefined]) + expect(oldVal).toBeUndefined() + }, + { immediate: true } + ) + await nextTick() + expect(called).toBe(true) + }) + it('watching multiple sources: readonly array', async () => { const state = reactive({ count: 1 }) const status = ref(false) diff --git a/packages/runtime-core/__tests__/componentProps.spec.ts b/packages/runtime-core/__tests__/componentProps.spec.ts index 40725dfcd13..cdb77838e31 100644 --- a/packages/runtime-core/__tests__/componentProps.spec.ts +++ b/packages/runtime-core/__tests__/componentProps.spec.ts @@ -595,4 +595,23 @@ describe('component props', () => { JSON.stringify(attrs) + Object.keys(attrs) ) }) + + // #691ef + test('should not mutate original props long-form definition object', () => { + const props = { + msg: { + type: String + } + } + const Comp = defineComponent({ + props, + render() {} + }) + + const root = nodeOps.createElement('div') + + render(h(Comp, { msg: 'test' }), root) + + expect(Object.keys(props.msg).length).toBe(1) + }) }) diff --git a/packages/runtime-core/__tests__/helpers/createSlots.spec.ts b/packages/runtime-core/__tests__/helpers/createSlots.spec.ts index 891789d048a..2af24380cca 100644 --- a/packages/runtime-core/__tests__/helpers/createSlots.spec.ts +++ b/packages/runtime-core/__tests__/helpers/createSlots.spec.ts @@ -26,6 +26,14 @@ describe('createSlot', () => { expect(ret.key).toBe('1') }) + it('should check nullability', () => { + const slot = (() => {}) as Slot + const dynamicSlot = [{ name: 'descriptor', fn: slot, key: '1' }] + + const actual = createSlots(record, dynamicSlot) + expect(actual).toHaveProperty('descriptor') + }) + it('should add all slots to the record', () => { const dynamicSlot = [ { name: 'descriptor', fn: slot }, diff --git a/packages/runtime-core/__tests__/hydration.spec.ts b/packages/runtime-core/__tests__/hydration.spec.ts index 17ffbb40092..493405b8ab8 100644 --- a/packages/runtime-core/__tests__/hydration.spec.ts +++ b/packages/runtime-core/__tests__/hydration.spec.ts @@ -982,6 +982,14 @@ describe('SSR hydration', () => { expect((container as any)._vnode).toBe(null) }) + // #6637 + test('stringified root fragment', () => { + mountWithHydration(`
`, () => + createStaticVNode(`
`, 1) + ) + expect(`mismatch`).not.toHaveBeenWarned() + }) + describe('mismatch handling', () => { test('text node', () => { const { container } = mountWithHydration(`foo`, () => 'bar') diff --git a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts index 6a03e7a8eb5..668391c9185 100644 --- a/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts +++ b/packages/runtime-core/__tests__/rendererTemplateRef.spec.ts @@ -493,4 +493,50 @@ describe('api: template refs', () => { await nextTick() expect(mapRefs()).toMatchObject(['2', '3', '4']) }) + + // #6697 v-for ref behaves differently under production and development + test('named ref in v-for , should be responsive when rendering', async () => { + const list = ref([1, 2, 3]) + const listRefs = ref([]) + const App = { + setup() { + return { listRefs } + }, + render() { + return h('div', null, [ + h('div', null, String(listRefs.value)), + h( + 'ul', + list.value.map(i => + h( + 'li', + { + ref: 'listRefs', + ref_for: true + }, + i + ) + ) + ) + ]) + } + } + const root = nodeOps.createElement('div') + render(h(App), root) + + await nextTick() + expect(String(listRefs.value)).toBe( + '[object Object],[object Object],[object Object]' + ) + expect(serializeInner(root)).toBe( + '
[object Object],[object Object],[object Object]
  • 1
  • 2
  • 3
' + ) + + list.value.splice(0, 1) + await nextTick() + expect(String(listRefs.value)).toBe('[object Object],[object Object]') + expect(serializeInner(root)).toBe( + '
[object Object],[object Object]
  • 2
  • 3
' + ) + }) }) diff --git a/packages/runtime-core/__tests__/vnode.spec.ts b/packages/runtime-core/__tests__/vnode.spec.ts index fdd23797b10..54cdc725b4d 100644 --- a/packages/runtime-core/__tests__/vnode.spec.ts +++ b/packages/runtime-core/__tests__/vnode.spec.ts @@ -8,11 +8,12 @@ import { cloneVNode, mergeProps, normalizeVNode, - transformVNodeArgs + transformVNodeArgs, + isBlockTreeEnabled } from '../src/vnode' import { Data } from '../src/component' import { ShapeFlags, PatchFlags } from '@vue/shared' -import { h, reactive, isReactive, setBlockTracking, ref } from '../src' +import { h, reactive, isReactive, setBlockTracking, ref, withCtx } from '../src' import { createApp, nodeOps, serializeInner } from '@vue/runtime-test' import { setCurrentRenderingInstance } from '../src/componentRenderContext' @@ -614,6 +615,29 @@ describe('vnode', () => { ])) expect(vnode.dynamicChildren).toStrictEqual([]) }) + // #5657 + test('error of slot function execution should not affect block tracking', () => { + expect(isBlockTreeEnabled).toStrictEqual(1) + const slotFn = withCtx( + () => { + throw new Error('slot execution error') + }, + { type: {}, appContext: {} } as any + ) + const Parent = { + setup(_: any, { slots }: any) { + return () => { + try { + slots.default() + } catch (e) {} + } + } + } + const vnode = + (openBlock(), createBlock(Parent, null, { default: slotFn })) + createApp(vnode).mount(nodeOps.createElement('div')) + expect(isBlockTreeEnabled).toStrictEqual(1) + }) }) describe('transformVNodeArgs', () => { diff --git a/packages/runtime-core/package.json b/packages/runtime-core/package.json index 9b29f2f6216..7cb199b7b22 100644 --- a/packages/runtime-core/package.json +++ b/packages/runtime-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-core", - "version": "3.2.39", + "version": "3.2.41", "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.39", - "@vue/reactivity": "3.2.39" + "@vue/shared": "3.2.41", + "@vue/reactivity": "3.2.41" } } diff --git a/packages/runtime-core/src/apiWatch.ts b/packages/runtime-core/src/apiWatch.ts index 27215f342b3..19026cf645d 100644 --- a/packages/runtime-core/src/apiWatch.ts +++ b/packages/runtime-core/src/apiWatch.ts @@ -296,7 +296,9 @@ function doWatch( return NOOP } - let oldValue = isMultiSource ? [] : INITIAL_WATCHER_VALUE + let oldValue: any = isMultiSource + ? new Array((source as []).length).fill(INITIAL_WATCHER_VALUE) + : INITIAL_WATCHER_VALUE const job: SchedulerJob = () => { if (!effect.active) { return @@ -323,7 +325,10 @@ function doWatch( callWithAsyncErrorHandling(cb, instance, ErrorCodes.WATCH_CALLBACK, [ newValue, // pass undefined as the old value when it's changed for the first time - oldValue === INITIAL_WATCHER_VALUE ? undefined : oldValue, + oldValue === INITIAL_WATCHER_VALUE || + (isMultiSource && oldValue[0] === INITIAL_WATCHER_VALUE) + ? undefined + : oldValue, onCleanup ]) oldValue = newValue diff --git a/packages/runtime-core/src/componentProps.ts b/packages/runtime-core/src/componentProps.ts index 09b487811b5..d59a4e94699 100644 --- a/packages/runtime-core/src/componentProps.ts +++ b/packages/runtime-core/src/componentProps.ts @@ -522,7 +522,7 @@ export function normalizePropsOptions( if (validatePropName(normalizedKey)) { const opt = raw[key] const prop: NormalizedProp = (normalized[normalizedKey] = - isArray(opt) || isFunction(opt) ? { type: opt } : opt) + isArray(opt) || isFunction(opt) ? { type: opt } : { ...opt }) if (prop) { const booleanIndex = getTypeIndex(Boolean, prop.type) const stringIndex = getTypeIndex(String, prop.type) diff --git a/packages/runtime-core/src/componentRenderContext.ts b/packages/runtime-core/src/componentRenderContext.ts index 8f49ec251d5..8097dc5fe2b 100644 --- a/packages/runtime-core/src/componentRenderContext.ts +++ b/packages/runtime-core/src/componentRenderContext.ts @@ -89,10 +89,14 @@ export function withCtx( setBlockTracking(-1) } const prevInstance = setCurrentRenderingInstance(ctx) - const res = fn(...args) - setCurrentRenderingInstance(prevInstance) - if (renderFnWithContext._d) { - setBlockTracking(1) + let res + try { + res = fn(...args) + } finally { + setCurrentRenderingInstance(prevInstance) + if (renderFnWithContext._d) { + setBlockTracking(1) + } } if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) { diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index a671bef6560..5e5e216d34a 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -274,7 +274,7 @@ if (__COMPAT__) { // export the public type for h/tsx inference // also to avoid inline import() in generated d.ts files -export const BaseTransition = BaseTransitionImpl as any as { +export const BaseTransition = BaseTransitionImpl as unknown as { new (): { $props: BaseTransitionProps } diff --git a/packages/runtime-core/src/components/Suspense.ts b/packages/runtime-core/src/components/Suspense.ts index 8408cab388d..baf57088626 100644 --- a/packages/runtime-core/src/components/Suspense.ts +++ b/packages/runtime-core/src/components/Suspense.ts @@ -89,7 +89,9 @@ export const SuspenseImpl = { } // Force-casted public typing for h and TSX props inference -export const Suspense = (__FEATURE_SUSPENSE__ ? SuspenseImpl : null) as any as { +export const Suspense = (__FEATURE_SUSPENSE__ + ? SuspenseImpl + : null) as unknown as { __isSuspense: true new (): { $props: VNodeProps & SuspenseProps } } diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index 06b69aff4ec..e519aa2bb3a 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -52,13 +52,13 @@ const resolveTarget = ( `ideally should be outside of the entire Vue component tree.` ) } - return target as any + return target as T } } else { if (__DEV__ && !targetSelector && !isTeleportDisabled(props)) { warn(`Invalid Teleport target: ${targetSelector}`) } - return targetSelector as any + return targetSelector as T } } @@ -388,7 +388,7 @@ function hydrateTeleport( } // Force-casted public typing for h and TSX props inference -export const Teleport = TeleportImpl as any as { +export const Teleport = TeleportImpl as unknown as { __isTeleport: true new (): { $props: VNodeProps & TeleportProps } } diff --git a/packages/runtime-core/src/devtools.ts b/packages/runtime-core/src/devtools.ts index 36c5763dcf1..f05128d47aa 100644 --- a/packages/runtime-core/src/devtools.ts +++ b/packages/runtime-core/src/devtools.ts @@ -28,6 +28,11 @@ interface DevtoolsHook { once: (event: string, handler: Function) => void off: (event: string, handler: Function) => void appRecords: AppRecord[] + /** + * Added at https://github.com/vuejs/devtools/commit/f2ad51eea789006ab66942e5a27c0f0986a257f9 + * Returns wether the arg was buffered or not + */ + cleanupBuffer?: (matchArg: unknown) => boolean } export let devtools: DevtoolsHook @@ -101,8 +106,22 @@ export const devtoolsComponentAdded = /*#__PURE__*/ createDevtoolsComponentHook( export const devtoolsComponentUpdated = /*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_UPDATED) -export const devtoolsComponentRemoved = - /*#__PURE__*/ createDevtoolsComponentHook(DevtoolsHooks.COMPONENT_REMOVED) +const _devtoolsComponentRemoved = /*#__PURE__*/ createDevtoolsComponentHook( + DevtoolsHooks.COMPONENT_REMOVED +) + +export const devtoolsComponentRemoved = ( + component: ComponentInternalInstance +) => { + if ( + devtools && + typeof devtools.cleanupBuffer === 'function' && + // remove the component if it wasn't buffered + !devtools.cleanupBuffer(component) + ) { + _devtoolsComponentRemoved(component) + } +} function createDevtoolsComponentHook(hook: DevtoolsHooks) { return (component: ComponentInternalInstance) => { diff --git a/packages/runtime-core/src/h.ts b/packages/runtime-core/src/h.ts index f22e4bb30d0..520f568e7a8 100644 --- a/packages/runtime-core/src/h.ts +++ b/packages/runtime-core/src/h.ts @@ -108,7 +108,7 @@ export function h( export function h( type: typeof Teleport, props: RawProps & TeleportProps, - children: RawChildren + children: RawChildren | RawSlots ): VNode // suspense @@ -141,9 +141,9 @@ export function h

( ): VNode // component without props -export function h( - type: Component, - props: null, +export function h

( + type: Component

, + props?: (RawProps & P) | null, children?: RawChildren | RawSlots ): VNode diff --git a/packages/runtime-core/src/helpers/createSlots.ts b/packages/runtime-core/src/helpers/createSlots.ts index 89ea7ac77c8..f370f5ca803 100644 --- a/packages/runtime-core/src/helpers/createSlots.ts +++ b/packages/runtime-core/src/helpers/createSlots.ts @@ -1,9 +1,12 @@ -import { Slot } from '../componentSlots' import { isArray } from '@vue/shared' +import { VNode } from '../vnode' + +// #6651 res can be undefined in SSR in string push mode +type SSRSlot = (...args: any[]) => VNode[] | undefined interface CompiledSlotDescriptor { name: string - fn: Slot + fn: SSRSlot key?: string } @@ -12,13 +15,13 @@ interface CompiledSlotDescriptor { * @private */ export function createSlots( - slots: Record, + slots: Record, dynamicSlots: ( | CompiledSlotDescriptor | CompiledSlotDescriptor[] | undefined )[] -): Record { +): Record { for (let i = 0; i < dynamicSlots.length; i++) { const slot = dynamicSlots[i] // array of dynamic slot generated by