diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index fe5e82b04a4..11853cec30c 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -30,7 +30,7 @@ body: description: | The easiest way to provide a reproduction is by showing the bug in [The SFC Playground](https://sfc.vuejs.org/). If it cannot be reproduced in the playground and requires a proper build setup, try [StackBlitz](https://vite.new/vue). - If neither of these are suitable, you can always provide a GitHub reporistory. + If neither of these are suitable, you can always provide a GitHub repository. The reproduction should be **minimal** - i.e. it should contain only the bare minimum amount of code needed to show the bug. See [Bug Reproduction Guidelines](https://github.com/vuejs/core/blob/main/.github/bug-repro-guidelines.md) for more details. 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/CHANGELOG.md b/CHANGELOG.md index 87ac185a7ef..af5bb244e1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,67 @@ +## [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) + + +### Bug Fixes + +* **runtime-core:** avoid double firing when mounting inside a watcher callback ([6aaf8ef](https://github.com/vuejs/core/commit/6aaf8efefffdb0d4b93f178b2bb36cd3c6bc31b8)), closes [#6614](https://github.com/vuejs/core/issues/6614) +* **runtime-core:** support extends template for runtime compiler ([#6250](https://github.com/vuejs/core/issues/6250)) ([9875ecd](https://github.com/vuejs/core/commit/9875ecd762155732008e397d450edb0f8c01b05c)), closes [#6249](https://github.com/vuejs/core/issues/6249) +* **ssr:** reset current instance ([#6184](https://github.com/vuejs/core/issues/6184)) ([6493da5](https://github.com/vuejs/core/commit/6493da5bfa4624267248deb3d31dca2a4fb22aee)), closes [#6110](https://github.com/vuejs/core/issues/6110) +* **types:** support TypeScript 4.8 ([5381abc](https://github.com/vuejs/core/commit/5381abc0571e58a9be6cf482dc50c8db8300f86c)), closes [#6554](https://github.com/vuejs/core/issues/6554) + + + +## [3.2.38](https://github.com/vuejs/core/compare/v3.2.37...v3.2.38) (2022-08-30) + + +### Bug Fixes + +* **compiler-sfc:** fix template usage check edge case for v-on statements ([769e555](https://github.com/vuejs/core/commit/769e5555f9d9004ce541613341652db859881570)) +* **compiler-sfc:** only add decorators-legacy parser plugin when new decorators plugin is not used ([3ff8369](https://github.com/vuejs/core/commit/3ff83694f523e3fe148d22a469ed742b46603bb4)) +* **compiler-sfc:** rewriteDefault for class with decorators ([#6320](https://github.com/vuejs/core/issues/6320)) ([81a7819](https://github.com/vuejs/core/commit/81a7819535c4382ba7817c817722bac6d41921d8)), closes [#6318](https://github.com/vuejs/core/issues/6318) +* **custom-element:** fix event listeners with capital letter event names on custom elements ([0739f89](https://github.com/vuejs/core/commit/0739f8909a0e56ae0fa760f233dfb8c113c9bde2)) +* **hmr:** fix HMR for nested non-SFC components ([#4077](https://github.com/vuejs/core/issues/4077)) ([96eb745](https://github.com/vuejs/core/commit/96eb7452548293c343613ab778248a5da9619f45)) +* **reactivity:** fix shallow/readonly edge cases ([a95554d](https://github.com/vuejs/core/commit/a95554d35c65e5bfd0bf9d1c5b908ae789345a6d)) +* **runtime-core:** only set cache for object keys ([#6266](https://github.com/vuejs/core/issues/6266)) ([c3465c1](https://github.com/vuejs/core/commit/c3465c1e889651df925324ed2a10ac2d5f229110)) +* **slots:** ensure different branches of dynamic slots have different keys ([00036bb](https://github.com/vuejs/core/commit/00036bb52c4e641b2be7fa55c39ced9448163b0f)), closes [#6202](https://github.com/vuejs/core/issues/6202) +* **ssr:** forward helpers provided by CSS `v-bind` ([#6489](https://github.com/vuejs/core/issues/6489)) ([2024d11](https://github.com/vuejs/core/commit/2024d11db03d9c6e49e20b3355f3df0ba04bb834)), closes [#6201](https://github.com/vuejs/core/issues/6201) +* **types:** add types field for sub package exports ([c1ee6ca](https://github.com/vuejs/core/commit/c1ee6caa82da89b3a9c33e2253c07a681ebb2628)) +* **types:** fix on* props incorrect type for TS 4.7 ([#6216](https://github.com/vuejs/core/issues/6216)) ([8dcb6c7](https://github.com/vuejs/core/commit/8dcb6c7bbdd2905469e2bb11dfff27b58cc784b2)), closes [#6052](https://github.com/vuejs/core/issues/6052) +* **watch:** flush:pre watchers should not fire if state change causes ([78c199d](https://github.com/vuejs/core/commit/78c199d6dbe8931520b75d8bfe0d49366a06922a)), closes [#2291](https://github.com/vuejs/core/issues/2291) + + +### Features + +* **custom-elements:** automatically respect custom elements when compiling in browser ([9f8f07e](https://github.com/vuejs/core/commit/9f8f07ed38b2e003f308875fe3a3e4c0d5477b32)) + + +### Performance Improvements + +* **ssr:** improve isComment check ([#6078](https://github.com/vuejs/core/issues/6078)) ([25f7a16](https://github.com/vuejs/core/commit/25f7a16a6eccbfa8d857977dcf1f23fb36b830b5)) + + + ## [3.2.37](https://github.com/vuejs/core/compare/v3.2.36...v3.2.37) (2022-06-06) diff --git a/README.md b/README.md index 0742b381303..8d2342c1ea0 100644 --- a/README.md +++ b/README.md @@ -8,9 +8,19 @@ Please follow the documentation at [vuejs.org](https://vuejs.org/)! Vue.js is an MIT-licensed open source project with its ongoing development made possible entirely by the support of these awesome [backers](https://github.com/vuejs/core/blob/main/BACKERS.md). If you'd like to join them, please consider [ sponsor Vue's development](https://vuejs.org/sponsor/). +

+

Special Sponsor

+

+ +

+ + special sponsor appwrite + +

+

- sponsors + sponsors

diff --git a/package.json b/package.json index bcdb9b60675..aeff54e501e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "private": true, - "version": "3.2.37", + "version": "3.2.40", "packageManager": "pnpm@7.1.0", "scripts": { "dev": "node scripts/dev.js", @@ -51,7 +51,7 @@ "devDependencies": { "@babel/types": "^7.12.0", "@esbuild-plugins/node-modules-polyfill": "^0.1.4", - "@microsoft/api-extractor": "^7.15.1", + "@microsoft/api-extractor": "~7.20.0", "@rollup/plugin-commonjs": "^18.0.0", "@rollup/plugin-json": "^4.0.0", "@rollup/plugin-node-resolve": "^11.2.1", @@ -93,8 +93,8 @@ "todomvc-app-css": "^2.3.0", "ts-jest": "^27.0.5", "tslib": "^2.4.0", - "typescript": "^4.7.4", - "vite": "^2.9.8", + "typescript": "^4.8.0", + "vite": "^3.0.0", "vue": "workspace:*", "yorkie": "^2.0.0" } diff --git a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap index a250f325d0a..826bc4a027f 100644 --- a/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap +++ b/packages/compiler-core/__tests__/__snapshots__/scopeId.spec.ts.snap @@ -43,7 +43,8 @@ export function render(_ctx, _cache) { name: \\"foo\\", fn: _withCtx(() => [ _createElementVNode(\\"div\\") - ]) + ]), + key: \\"0\\" } : undefined, _renderList(_ctx.list, (i) => { diff --git a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap index 967f2b3f45e..55796020a47 100644 --- a/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap +++ b/packages/compiler-core/__tests__/transforms/__snapshots__/vSlot.spec.ts.snap @@ -56,7 +56,8 @@ return function render(_ctx, _cache) { (_ctx.ok) ? { name: \\"one\\", - fn: _withCtx((props) => [_toDisplayString(props)]) + fn: _withCtx((props) => [_toDisplayString(props)]), + key: \\"0\\" } : undefined ]), 1024 /* DYNAMIC_SLOTS */)) @@ -76,16 +77,19 @@ return function render(_ctx, _cache) { ok ? { name: \\"one\\", - fn: _withCtx(() => [\\"foo\\"]) + fn: _withCtx(() => [\\"foo\\"]), + key: \\"0\\" } : orNot ? { name: \\"two\\", - fn: _withCtx((props) => [\\"bar\\"]) + fn: _withCtx((props) => [\\"bar\\"]), + key: \\"1\\" } : { name: \\"one\\", - fn: _withCtx(() => [\\"baz\\"]) + fn: _withCtx(() => [\\"baz\\"]), + key: \\"2\\" } ]), 1024 /* DYNAMIC_SLOTS */)) } @@ -105,7 +109,8 @@ return function render(_ctx, _cache) { ok ? { name: \\"one\\", - fn: _withCtx(() => [\\"hello\\"]) + fn: _withCtx(() => [\\"hello\\"]), + key: \\"0\\" } : undefined ]), 1024 /* DYNAMIC_SLOTS */)) diff --git a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts index 2c4421aeb3b..eec5a76d363 100644 --- a/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts +++ b/packages/compiler-core/__tests__/transforms/hoistStatic.spec.ts @@ -13,7 +13,6 @@ import { import { FRAGMENT, RENDER_LIST, - CREATE_TEXT, NORMALIZE_CLASS } from '../../src/runtimeHelpers' import { transformElement } from '../../src/transforms/transformElement' @@ -378,36 +377,6 @@ describe('compiler: hoistStatic transform', () => { expect(generate(root).code).toMatchSnapshot() }) - test('hoist static text node between elements', () => { - const root = transformWithHoist(`
static
static
`) - expect(root.hoists).toMatchObject([ - { - callee: CREATE_TEXT, - arguments: [ - { - type: NodeTypes.TEXT, - content: `static` - } - ] - }, - { - type: NodeTypes.VNODE_CALL, - tag: `"div"` - }, - { - type: NodeTypes.JS_ARRAY_EXPRESSION, - elements: [ - { - type: NodeTypes.TEXT_CALL - }, - { - type: NodeTypes.ELEMENT - } - ] - } - ]) - }) - describe('prefixIdentifiers', () => { test('hoist nested static tree with static interpolation', () => { const root = transformWithHoist( @@ -618,7 +587,9 @@ describe('compiler: hoistStatic transform', () => { }) test('should NOT hoist SVG with directives', () => { - const root = transformWithHoist(`
`) + const root = transformWithHoist( + `
` + ) expect(root.hoists.length).toBe(2) expect(generate(root).code).toMatchSnapshot() }) diff --git a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts index bec7e119bd3..43bd2589df1 100644 --- a/packages/compiler-core/__tests__/transforms/transformElement.spec.ts +++ b/packages/compiler-core/__tests__/transforms/transformElement.spec.ts @@ -314,6 +314,37 @@ describe('compiler: element transform', () => { ) expect(root.helpers).toContain(MERGE_PROPS) + expect(node.props).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: MERGE_PROPS, + arguments: [ + createObjectMatcher({ + id: 'foo' + }), + { + type: NodeTypes.JS_CALL_EXPRESSION, + callee: TO_HANDLERS, + arguments: [ + { + type: NodeTypes.SIMPLE_EXPRESSION, + content: `obj` + }, + `true` + ] + }, + createObjectMatcher({ + class: 'bar' + }) + ] + }) + }) + + test('v-on="obj" on component', () => { + const { root, node } = parseWithElementTransform( + `` + ) + expect(root.helpers).toContain(MERGE_PROPS) + expect(node.props).toMatchObject({ type: NodeTypes.JS_CALL_EXPRESSION, callee: MERGE_PROPS, @@ -358,7 +389,8 @@ describe('compiler: element transform', () => { { type: NodeTypes.SIMPLE_EXPRESSION, content: `handlers` - } + }, + `true` ] }, { diff --git a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts index 687c4d8b358..93dafe9a25b 100644 --- a/packages/compiler-core/__tests__/transforms/vSlot.spec.ts +++ b/packages/compiler-core/__tests__/transforms/vSlot.spec.ts @@ -568,7 +568,8 @@ describe('compiler: transform component slots', () => { fn: { type: NodeTypes.JS_FUNCTION_EXPRESSION, returns: [{ type: NodeTypes.TEXT, content: `hello` }] - } + }, + key: `0` }), alternate: { content: `undefined`, @@ -616,7 +617,8 @@ describe('compiler: transform component slots', () => { content: { content: `props` } } ] - } + }, + key: `0` }), alternate: { content: `undefined`, @@ -660,7 +662,8 @@ describe('compiler: transform component slots', () => { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: undefined, returns: [{ type: NodeTypes.TEXT, content: `foo` }] - } + }, + key: `0` }), alternate: { type: NodeTypes.JS_CONDITIONAL_EXPRESSION, @@ -671,7 +674,8 @@ describe('compiler: transform component slots', () => { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: { content: `props` }, returns: [{ type: NodeTypes.TEXT, content: `bar` }] - } + }, + key: `1` }), alternate: createObjectMatcher({ name: `one`, @@ -679,7 +683,8 @@ describe('compiler: transform component slots', () => { type: NodeTypes.JS_FUNCTION_EXPRESSION, params: undefined, returns: [{ type: NodeTypes.TEXT, content: `baz` }] - } + }, + key: `2` }) } } diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 6f27ddc0e0b..1e2eb6c25f7 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.2.37", + "version": "3.2.40", "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.37", + "@vue/shared": "3.2.40", "@babel/parser": "^7.16.4", "estree-walker": "^2.0.2", "source-map": "^0.6.1" 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/transforms/hoistStatic.ts b/packages/compiler-core/src/transforms/hoistStatic.ts
index 848052581ed..b2b7f871c0a 100644
--- a/packages/compiler-core/src/transforms/hoistStatic.ts
+++ b/packages/compiler-core/src/transforms/hoistStatic.ts
@@ -97,12 +97,6 @@ function walk(
           }
         }
       }
-    } else if (
-      child.type === NodeTypes.TEXT_CALL &&
-      getConstantType(child.content, context) >= ConstantTypes.CAN_HOIST
-    ) {
-      child.codegenNode = context.hoist(child.codegenNode)
-      hoistedCount++
     }
 
     // walk further
diff --git a/packages/compiler-core/src/transforms/transformElement.ts b/packages/compiler-core/src/transforms/transformElement.ts
index 2d15227f70f..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,11 +649,11 @@ 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),
-              arguments: [exp]
+              arguments: isComponent ? [exp] : [exp, `true`]
             })
           }
         } else {
@@ -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/vOn.ts b/packages/compiler-core/src/transforms/vOn.ts
index 060a7ef9097..a9dfe77eff7 100644
--- a/packages/compiler-core/src/transforms/vOn.ts
+++ b/packages/compiler-core/src/transforms/vOn.ts
@@ -47,12 +47,17 @@ export const transformOn: DirectiveTransform = (
       if (rawName.startsWith('vue:')) {
         rawName = `vnode-${rawName.slice(4)}`
       }
-      // for all event listeners, auto convert it to camelCase. See issue #2249
-      eventName = createSimpleExpression(
-        toHandlerKey(camelize(rawName)),
-        true,
-        arg.loc
-      )
+      const eventString =
+        node.tagType === ElementTypes.COMPONENT ||
+        rawName.startsWith('vnode') ||
+        !/[A-Z]/.test(rawName)
+          ? // 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
+            // letters, as these may be custom elements' custom events
+          : `on:${rawName}`
+      eventName = createSimpleExpression(eventString, true, arg.loc)
     } else {
       // #2388
       eventName = createCompoundExpression([
diff --git a/packages/compiler-core/src/transforms/vSlot.ts b/packages/compiler-core/src/transforms/vSlot.ts
index 8fc86740eb6..c4416dd45f7 100644
--- a/packages/compiler-core/src/transforms/vSlot.ts
+++ b/packages/compiler-core/src/transforms/vSlot.ts
@@ -160,6 +160,7 @@ export function buildSlots(
   let hasNamedDefaultSlot = false
   const implicitDefaultChildren: TemplateChildNode[] = []
   const seenSlotNames = new Set()
+  let conditionalBranchIndex = 0
 
   for (let i = 0; i < children.length; i++) {
     const slotElement = children[i]
@@ -210,7 +211,7 @@ export function buildSlots(
       dynamicSlots.push(
         createConditionalExpression(
           vIf.exp!,
-          buildDynamicSlot(slotName, slotFunction),
+          buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++),
           defaultFallback
         )
       )
@@ -243,10 +244,14 @@ export function buildSlots(
         conditional.alternate = vElse.exp
           ? createConditionalExpression(
               vElse.exp,
-              buildDynamicSlot(slotName, slotFunction),
+              buildDynamicSlot(
+                slotName,
+                slotFunction,
+                conditionalBranchIndex++
+              ),
               defaultFallback
             )
-          : buildDynamicSlot(slotName, slotFunction)
+          : buildDynamicSlot(slotName, slotFunction, conditionalBranchIndex++)
       } else {
         context.onError(
           createCompilerError(ErrorCodes.X_V_ELSE_NO_ADJACENT_IF, vElse.loc)
@@ -369,12 +374,19 @@ export function buildSlots(
 
 function buildDynamicSlot(
   name: ExpressionNode,
-  fn: FunctionExpression
+  fn: FunctionExpression,
+  index?: number
 ): ObjectExpression {
-  return createObjectExpression([
+  const props = [
     createObjectProperty(`name`, name),
     createObjectProperty(`fn`, fn)
-  ])
+  ]
+  if (index != null) {
+    props.push(
+      createObjectProperty(`key`, createSimpleExpression(String(index), true))
+    )
+  }
+  return createObjectExpression(props)
 }
 
 function hasForwardedSlots(children: TemplateChildNode[]): boolean {
diff --git a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
index c737071a827..bedec9fc00a 100644
--- a/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
+++ b/packages/compiler-dom/__tests__/transforms/stringifyStatic.spec.ts
@@ -410,6 +410,29 @@ describe('stringify static html', () => {
     })
   })
 
+  // #6617
+  test('should remove boolean attribute for `false`', () => {
+    const { ast } = compileWithStringify(
+      `${repeat(
+        `
`, + StringifyThresholds.NODE_COUNT + )}` + ) + expect(ast.hoists[0]).toMatchObject({ + type: NodeTypes.JS_CALL_EXPRESSION, + callee: CREATE_STATIC, + arguments: [ + JSON.stringify( + `${repeat( + `
`, + StringifyThresholds.NODE_COUNT + )}` + ), + '21' + ] + }) + }) + test('should stringify svg', () => { const svg = `` const repeated = `` diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index db73720a1d6..cc1c3607563 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.2.37", + "version": "3.2.40", "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.37", - "@vue/compiler-core": "3.2.37" + "@vue/shared": "3.2.40", + "@vue/compiler-core": "3.2.40" } } diff --git a/packages/compiler-dom/src/transforms/stringifyStatic.ts b/packages/compiler-dom/src/transforms/stringifyStatic.ts index a268d86cd07..d3f58bdb9da 100644 --- a/packages/compiler-dom/src/transforms/stringifyStatic.ts +++ b/packages/compiler-dom/src/transforms/stringifyStatic.ts @@ -28,7 +28,8 @@ import { normalizeStyle, stringifyStyle, makeMap, - isKnownSvgAttr + isKnownSvgAttr, + isBooleanAttr } from '@vue/shared' import { DOMNamespaces } from '../parserOptions' @@ -298,6 +299,13 @@ function stringifyElement( }="__VUE_EXP_START__${exp.content}__VUE_EXP_END__"` continue } + // #6568 + if ( + isBooleanAttr((p.arg as SimpleExpressionNode).content) && + exp.content === 'false' + ) { + continue + } // constant v-bind, e.g. :foo="1" let evaluated = evaluateConstant(exp) if (evaluated != null) { 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__/compileScript.spec.ts b/packages/compiler-sfc/__tests__/compileScript.spec.ts index 08d404b9c16..f7a796949a9 100644 --- a/packages/compiler-sfc/__tests__/compileScript.spec.ts +++ b/packages/compiler-sfc/__tests__/compileScript.spec.ts @@ -471,7 +471,7 @@ defineExpose({ foo: 123 })
- + `) }) }) 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/package.json b/packages/compiler-sfc/package.json index bd148e2723a..edf8d2d901b 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.2.37", + "version": "3.2.40", "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.37", - "@vue/compiler-dom": "3.2.37", - "@vue/compiler-ssr": "3.2.37", - "@vue/reactivity-transform": "3.2.37", - "@vue/shared": "3.2.37", + "@vue/compiler-core": "3.2.40", + "@vue/compiler-dom": "3.2.40", + "@vue/compiler-ssr": "3.2.40", + "@vue/reactivity-transform": "3.2.40", + "@vue/shared": "3.2.40", "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 1a796186739..5d5191ffb44 100644 --- a/packages/compiler-ssr/__tests__/ssrComponent.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrComponent.spec.ts @@ -166,7 +166,8 @@ describe('ssr: components', () => { _createTextVNode(\\"foo\\") ] } - }) + }), + key: \\"0\\" } : undefined ]), _parent)) diff --git a/packages/compiler-ssr/__tests__/ssrFallthroughAttrs.spec.ts b/packages/compiler-ssr/__tests__/ssrFallthroughAttrs.spec.ts index 6cbb0d3a956..44b113c68be 100644 --- a/packages/compiler-ssr/__tests__/ssrFallthroughAttrs.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrFallthroughAttrs.spec.ts @@ -37,7 +37,7 @@ describe('ssr: attrs fallthrough', () => { `) }) - test('fallthrough component content (root with coomments)', () => { + test('fallthrough component content (root with comments)', () => { expect(compile(`
`).code) .toMatchInlineSnapshot(` "const { ssrRenderAttrs: _ssrRenderAttrs } = require(\\"vue/server-renderer\\") diff --git a/packages/compiler-ssr/__tests__/ssrInjectCssVars.spec.ts b/packages/compiler-ssr/__tests__/ssrInjectCssVars.spec.ts index b2e5b44518d..97df3045c67 100644 --- a/packages/compiler-ssr/__tests__/ssrInjectCssVars.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrInjectCssVars.spec.ts @@ -1,3 +1,4 @@ +import { BindingTypes } from '@vue/compiler-dom' import { compile } from '../src' describe('ssr: inject