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( + '' + ) + + list.value.splice(0, 1) + await nextTick() + expect(String(listRefs.value)).toBe('[object Object],[object Object]') + expect(serializeInner(root)).toBe( + '[object Object],[object Object],[object Object]
- 1
- 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[object Object],[object Object]
- 2
- 3
} 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 @@ -33,7 +36,7 @@ export function createSlots( const res = slot.fn(...args) // attach branch key so each conditional branch is considered a // different fragment - ;(res as any).key = slot.key + if (res) (res as any).key = slot.key return res } : slot.fn diff --git a/packages/runtime-core/src/helpers/withMemo.ts b/packages/runtime-core/src/helpers/withMemo.ts index 002b9e27a88..76b0f055f39 100644 --- a/packages/runtime-core/src/helpers/withMemo.ts +++ b/packages/runtime-core/src/helpers/withMemo.ts @@ -23,7 +23,7 @@ export function isMemoSame(cached: VNode, memo: any[]) { if (prev.length != memo.length) { return false } - + for (let i = 0; i < prev.length; i++) { if (hasChanged(prev[i], memo[i])) { return false diff --git a/packages/runtime-core/src/hydration.ts b/packages/runtime-core/src/hydration.ts index 8ada97b166b..2170a9192cf 100644 --- a/packages/runtime-core/src/hydration.ts +++ b/packages/runtime-core/src/hydration.ts @@ -108,7 +108,7 @@ export function createHydrationFunctions( ) const { type, ref, shapeFlag, patchFlag } = vnode - const domType = node.nodeType + let domType = node.nodeType vnode.el = node if (patchFlag === PatchFlags.BAIL) { @@ -150,9 +150,12 @@ export function createHydrationFunctions( } break case Static: - if (domType !== DOMNodeTypes.ELEMENT && domType !== DOMNodeTypes.TEXT) { - nextNode = onMismatch() - } else { + if (isFragmentStart) { + // entire template is static but SSRed as a fragment + node = nextSibling(node)! + domType = node.nodeType + } + if (domType === DOMNodeTypes.ELEMENT || domType === DOMNodeTypes.TEXT) { // determine anchor, adopt content nextNode = node // if the static vnode has its content stripped during build, @@ -169,7 +172,9 @@ export function createHydrationFunctions( } nextNode = nextSibling(nextNode)! } - return nextNode + return isFragmentStart ? nextSibling(nextNode) : nextNode + } else { + onMismatch() } break case Fragment: diff --git a/packages/runtime-core/src/rendererTemplateRef.ts b/packages/runtime-core/src/rendererTemplateRef.ts index 1fe432d5019..30f96228d1f 100644 --- a/packages/runtime-core/src/rendererTemplateRef.ts +++ b/packages/runtime-core/src/rendererTemplateRef.ts @@ -84,7 +84,11 @@ export function setRef( if (_isString || _isRef) { const doSet = () => { if (rawRef.f) { - const existing = _isString ? refs[ref] : ref.value + const existing = _isString + ? hasOwn(setupState, ref) + ? setupState[ref] + : refs[ref] + : ref.value if (isUnmount) { isArray(existing) && remove(existing, refValue) } else { diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index b731dc0b51f..2122f9f855a 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -24,6 +24,7 @@ import { RawSlots } from './componentSlots' import { isProxy, Ref, toRaw, ReactiveFlags, isRef } from '@vue/reactivity' import { AppContext } from './apiCreateApp' import { + Suspense, SuspenseImpl, isSuspense, SuspenseBoundary @@ -31,7 +32,7 @@ import { import { DirectiveBinding } from './directives' import { TransitionHooks } from './components/BaseTransition' import { warn } from './warning' -import { TeleportImpl, isTeleport } from './components/Teleport' +import { Teleport, TeleportImpl, isTeleport } from './components/Teleport' import { currentRenderingInstance, currentScopeId @@ -63,7 +64,9 @@ export type VNodeTypes = | typeof Static | typeof Comment | typeof Fragment + | typeof Teleport | typeof TeleportImpl + | typeof Suspense | typeof SuspenseImpl export type VNodeRef = diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index e29c36123f3..300cc2322ce 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -135,14 +135,14 @@ describe('defineCustomElement', () => { test('attribute -> prop type casting', async () => { const E = defineCustomElement({ props: { - foo: Number, + fooBar: Number, // test casting of camelCase prop names bar: Boolean, baz: String }, render() { return [ - this.foo, - typeof this.foo, + this.fooBar, + typeof this.fooBar, this.bar, typeof this.bar, this.baz, @@ -151,7 +151,7 @@ describe('defineCustomElement', () => { } }) customElements.define('my-el-props-cast', E) - container.innerHTML = ` ` + container.innerHTML = ` ` const e = container.childNodes[0] as VueElement expect(e.shadowRoot!.innerHTML).toBe( `1 number false boolean 12345 string` @@ -161,7 +161,7 @@ describe('defineCustomElement', () => { await nextTick() expect(e.shadowRoot!.innerHTML).toBe(`1 number true boolean 12345 string`) - e.setAttribute('foo', '2e1') + e.setAttribute('foo-bar', '2e1') await nextTick() expect(e.shadowRoot!.innerHTML).toBe( `20 number true boolean 12345 string` diff --git a/packages/runtime-dom/__tests__/patchEvents.spec.ts b/packages/runtime-dom/__tests__/patchEvents.spec.ts index 9c30616a24a..32466f29a7b 100644 --- a/packages/runtime-dom/__tests__/patchEvents.spec.ts +++ b/packages/runtime-dom/__tests__/patchEvents.spec.ts @@ -5,30 +5,28 @@ const timeout = () => new Promise(r => setTimeout(r)) describe(`runtime-dom: events patching`, () => { it('should assign event handler', async () => { const el = document.createElement('div') - const event = new Event('click') const fn = jest.fn() patchProp(el, 'onClick', null, fn) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(fn).toHaveBeenCalledTimes(3) }) it('should update event handler', async () => { const el = document.createElement('div') - const event = new Event('click') const prevFn = jest.fn() const nextFn = jest.fn() patchProp(el, 'onClick', null, prevFn) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) patchProp(el, 'onClick', prevFn, nextFn) await timeout() - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(prevFn).toHaveBeenCalledTimes(1) expect(nextFn).toHaveBeenCalledTimes(2) @@ -36,11 +34,10 @@ describe(`runtime-dom: events patching`, () => { it('should support multiple event handlers', async () => { const el = document.createElement('div') - const event = new Event('click') const fn1 = jest.fn() const fn2 = jest.fn() patchProp(el, 'onClick', null, [fn1, fn2]) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(fn1).toHaveBeenCalledTimes(1) expect(fn2).toHaveBeenCalledTimes(1) @@ -48,58 +45,55 @@ describe(`runtime-dom: events patching`, () => { it('should unassign event handler', async () => { const el = document.createElement('div') - const event = new Event('click') const fn = jest.fn() patchProp(el, 'onClick', null, fn) patchProp(el, 'onClick', fn, null) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(fn).not.toHaveBeenCalled() }) it('should support event option modifiers', async () => { const el = document.createElement('div') - const event = new Event('click') const fn = jest.fn() patchProp(el, 'onClickOnceCapture', null, fn) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(fn).toHaveBeenCalledTimes(1) }) it('should unassign event handler with options', async () => { const el = document.createElement('div') - const event = new Event('click') const fn = jest.fn() patchProp(el, 'onClickCapture', null, fn) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(fn).toHaveBeenCalledTimes(1) patchProp(el, 'onClickCapture', fn, null) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(fn).toHaveBeenCalledTimes(1) }) it('should support native onclick', async () => { const el = document.createElement('div') - const event = new Event('click') // string should be set as attribute const fn = ((window as any).__globalSpy = jest.fn()) patchProp(el, 'onclick', null, '__globalSpy(1)') - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() delete (window as any).__globalSpy expect(fn).toHaveBeenCalledWith(1) const fn2 = jest.fn() patchProp(el, 'onclick', '__globalSpy(1)', fn2) + const event = new Event('click') el.dispatchEvent(event) await timeout() expect(fn).toHaveBeenCalledTimes(1) @@ -108,13 +102,12 @@ describe(`runtime-dom: events patching`, () => { it('should support stopImmediatePropagation on multiple listeners', async () => { const el = document.createElement('div') - const event = new Event('click') const fn1 = jest.fn((e: Event) => { e.stopImmediatePropagation() }) const fn2 = jest.fn() patchProp(el, 'onClick', null, [fn1, fn2]) - el.dispatchEvent(event) + el.dispatchEvent(new Event('click')) await timeout() expect(fn1).toHaveBeenCalledTimes(1) expect(fn2).toHaveBeenCalledTimes(0) @@ -125,15 +118,15 @@ describe(`runtime-dom: events patching`, () => { const el1 = document.createElement('div') const el2 = document.createElement('div') - const event = new Event('click') + // const event = new Event('click') const prevFn = jest.fn() const nextFn = jest.fn() patchProp(el1, 'onClick', null, prevFn) patchProp(el2, 'onClick', null, prevFn) - el1.dispatchEvent(event) - el2.dispatchEvent(event) + el1.dispatchEvent(new Event('click')) + el2.dispatchEvent(new Event('click')) await timeout() expect(prevFn).toHaveBeenCalledTimes(2) expect(nextFn).toHaveBeenCalledTimes(0) @@ -141,19 +134,39 @@ describe(`runtime-dom: events patching`, () => { patchProp(el1, 'onClick', prevFn, nextFn) patchProp(el2, 'onClick', prevFn, nextFn) - el1.dispatchEvent(event) - el2.dispatchEvent(event) + el1.dispatchEvent(new Event('click')) + el2.dispatchEvent(new Event('click')) await timeout() expect(prevFn).toHaveBeenCalledTimes(2) expect(nextFn).toHaveBeenCalledTimes(2) - el1.dispatchEvent(event) - el2.dispatchEvent(event) + el1.dispatchEvent(new Event('click')) + el2.dispatchEvent(new Event('click')) await timeout() expect(prevFn).toHaveBeenCalledTimes(2) expect(nextFn).toHaveBeenCalledTimes(4) }) + // vuejs/vue#6566 + it('should not fire handler attached by the event itself', async () => { + const el = document.createElement('div') + const child = document.createElement('div') + el.appendChild(child) + document.body.appendChild(el) + const childFn = jest.fn() + const parentFn = jest.fn() + + patchProp(child, 'onClick', null, () => { + childFn() + patchProp(el, 'onClick', null, parentFn) + }) + child.dispatchEvent(new Event('click', { bubbles: true })) + + await timeout() + expect(childFn).toHaveBeenCalled() + expect(parentFn).not.toHaveBeenCalled() + }) + // #2841 test('should patch event correctly in web-components', async () => { class TestElement extends HTMLElement { diff --git a/packages/runtime-dom/package.json b/packages/runtime-dom/package.json index 1e90a6f1027..d623453072e 100644 --- a/packages/runtime-dom/package.json +++ b/packages/runtime-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-dom", - "version": "3.2.39", + "version": "3.2.41", "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.39", - "@vue/runtime-core": "3.2.39", + "@vue/shared": "3.2.41", + "@vue/runtime-core": "3.2.41", "csstype": "^2.6.8" } } diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index eabe83b6b9f..5ff45d652f2 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -268,10 +268,11 @@ export class VueElement extends BaseClass { protected _setAttr(key: string) { let value = this.getAttribute(key) - if (this._numberProps && this._numberProps[key]) { + const camelKey = camelize(key) + if (this._numberProps && this._numberProps[camelKey]) { value = toNumber(value) } - this._setProp(camelize(key), value, false) + this._setProp(camelKey, value, false) } /** diff --git a/packages/runtime-dom/src/modules/events.ts b/packages/runtime-dom/src/modules/events.ts index d0f8d364a29..8dbccadef1a 100644 --- a/packages/runtime-dom/src/modules/events.ts +++ b/packages/runtime-dom/src/modules/events.ts @@ -12,38 +12,6 @@ interface Invoker extends EventListener { type EventValue = Function | Function[] -// Async edge case fix requires storing an event listener's attach timestamp. -const [_getNow, skipTimestampCheck] = /*#__PURE__*/ (() => { - let _getNow = Date.now - let skipTimestampCheck = false - if (typeof window !== 'undefined') { - // Determine what event timestamp the browser is using. Annoyingly, the - // timestamp can either be hi-res (relative to page load) or low-res - // (relative to UNIX epoch), so in order to compare time we have to use the - // same timestamp type when saving the flush timestamp. - if (Date.now() > document.createEvent('Event').timeStamp) { - // 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.bind(performance) - } - // #3485: Firefox <= 53 has incorrect Event.timeStamp implementation - // and does not fire microtasks in between event propagation, so safe to exclude. - const ffMatch = navigator.userAgent.match(/firefox\/(\d+)/i) - skipTimestampCheck = !!(ffMatch && Number(ffMatch[1]) <= 53) - } - return [_getNow, skipTimestampCheck] -})() - -// To avoid the overhead of repeatedly calling performance.now(), we cache -// and use the same timestamp for all event listeners attached in the same tick. -let cachedNow: number = 0 -const p = /*#__PURE__*/ Promise.resolve() -const reset = () => { - cachedNow = 0 -} -const getNow = () => cachedNow || (p.then(reset), (cachedNow = _getNow())) - export function addEventListener( el: Element, event: string, @@ -105,27 +73,41 @@ function parseName(name: string): [string, EventListenerOptions | undefined] { return [event, options] } +// To avoid the overhead of repeatedly calling Date.now(), we cache +// and use the same timestamp for all event listeners attached in the same tick. +let cachedNow: number = 0 +const p = /*#__PURE__*/ Promise.resolve() +const getNow = () => + cachedNow || (p.then(() => (cachedNow = 0)), (cachedNow = Date.now())) + function createInvoker( initialValue: EventValue, instance: ComponentInternalInstance | null ) { - const invoker: Invoker = (e: Event) => { - // async edge case #6566: inner click event triggers patch, event handler + const invoker: Invoker = (e: Event & { _vts?: number }) => { + // async edge case vuejs/vue#6566 + // inner click event triggers patch, event handler // attached to outer element during patch, and triggered again. This // happens because browsers fire microtask ticks between event propagation. - // the solution is simple: we save the timestamp when a handler is attached, - // and the handler would only fire if the event passed to it was fired + // this no longer happens for templates in Vue 3, but could still be + // theoretically possible for hand-written render functions. + // the solution: we save the timestamp when a handler is attached, + // and also attach the timestamp to any event that was handled by vue + // for the first time (to avoid inconsistent event timestamp implementations + // or events fired from iframes, e.g. #2513) + // The handler would only fire if the event passed to it was fired // AFTER it was attached. - const timeStamp = e.timeStamp || _getNow() - - if (skipTimestampCheck || timeStamp >= invoker.attached - 1) { - callWithAsyncErrorHandling( - patchStopImmediatePropagation(e, invoker.value), - instance, - ErrorCodes.NATIVE_EVENT_HANDLER, - [e] - ) + if (!e._vts) { + e._vts = Date.now() + } else if (e._vts <= invoker.attached) { + return } + callWithAsyncErrorHandling( + patchStopImmediatePropagation(e, invoker.value), + instance, + ErrorCodes.NATIVE_EVENT_HANDLER, + [e] + ) } invoker.value = initialValue invoker.attached = getNow() diff --git a/packages/runtime-test/package.json b/packages/runtime-test/package.json index 27c9bc3324b..bac2bd9c434 100644 --- a/packages/runtime-test/package.json +++ b/packages/runtime-test/package.json @@ -1,6 +1,6 @@ { "name": "@vue/runtime-test", - "version": "3.2.39", + "version": "3.2.41", "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.39", - "@vue/runtime-core": "3.2.39" + "@vue/shared": "3.2.41", + "@vue/runtime-core": "3.2.41" } } diff --git a/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts b/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts index 62ccdb59974..e9dfb0dbd59 100644 --- a/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts +++ b/packages/server-renderer/__tests__/ssrRenderAttrs.spec.ts @@ -98,6 +98,17 @@ describe('ssr: renderAttrs', () => { ) ).toBe(` fooBar="ok"`) }) + + test('preserve name on svg elements', () => { + expect( + ssrRenderAttrs( + { + viewBox: 'foo' + }, + 'svg' + ) + ).toBe(` viewBox="foo"`) + }) }) describe('ssr: renderAttr', () => { diff --git a/packages/server-renderer/package.json b/packages/server-renderer/package.json index ef56974d9cd..d310cead06f 100644 --- a/packages/server-renderer/package.json +++ b/packages/server-renderer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/server-renderer", - "version": "3.2.39", + "version": "3.2.41", "description": "@vue/server-renderer", "main": "index.js", "module": "dist/server-renderer.esm-bundler.js", @@ -32,10 +32,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/server-renderer#readme", "peerDependencies": { - "vue": "3.2.39" + "vue": "3.2.41" }, "dependencies": { - "@vue/shared": "3.2.39", - "@vue/compiler-ssr": "3.2.39" + "@vue/shared": "3.2.41", + "@vue/compiler-ssr": "3.2.41" } } diff --git a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts index 746ffa10729..6bbf83a1eaa 100644 --- a/packages/server-renderer/src/helpers/ssrRenderAttrs.ts +++ b/packages/server-renderer/src/helpers/ssrRenderAttrs.ts @@ -1,4 +1,4 @@ -import { escapeHtml, stringifyStyle } from '@vue/shared' +import { escapeHtml, isSVGTag, stringifyStyle } from '@vue/shared' import { normalizeClass, normalizeStyle, @@ -51,8 +51,8 @@ export function ssrRenderDynamicAttr( return `` } const attrKey = - tag && tag.indexOf('-') > 0 - ? key // preserve raw name on custom elements + tag && (tag.indexOf('-') > 0 || isSVGTag(tag)) + ? key // preserve raw name on custom elements and svg : propsToAttrMap[key] || key.toLowerCase() if (isBooleanAttr(attrKey)) { return includeBooleanAttr(value) ? ` ${attrKey}` : `` diff --git a/packages/sfc-playground/package.json b/packages/sfc-playground/package.json index 509b077aec5..c361b00b2b2 100644 --- a/packages/sfc-playground/package.json +++ b/packages/sfc-playground/package.json @@ -1,6 +1,6 @@ { "name": "@vue/sfc-playground", - "version": "3.2.39", + "version": "3.2.41", "private": true, "scripts": { "dev": "vite", @@ -12,7 +12,7 @@ "vite": "^3.0.0" }, "dependencies": { - "vue": "3.2.39", + "vue": "3.2.41", "@vue/repl": "^1.3.0", "file-saver": "^2.0.5", "jszip": "^3.6.0" diff --git a/packages/shared/package.json b/packages/shared/package.json index e966fe4a7a8..46e8673bd34 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -1,6 +1,6 @@ { "name": "@vue/shared", - "version": "3.2.39", + "version": "3.2.41", "description": "internal utils shared across @vue packages", "main": "index.js", "module": "dist/shared.esm-bundler.js", diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts index f3fee4c45e7..8e3020f9e37 100644 --- a/packages/shared/src/index.ts +++ b/packages/shared/src/index.ts @@ -52,7 +52,8 @@ export const isMap = (val: unknown): val is Map => export const isSet = (val: unknown): val is Set => toTypeString(val) === '[object Set]' -export const isDate = (val: unknown): val is Date => toTypeString(val) === '[object 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' @@ -99,7 +100,7 @@ const cacheStringFunction = string>(fn: T): T => { return ((str: string) => { const hit = cache[str] return hit || (cache[str] = fn(str)) - }) as any + }) as T } const camelizeRE = /-(\w)/g diff --git a/packages/shared/src/typeUtils.ts b/packages/shared/src/typeUtils.ts index 8730d7f38bf..3f3620c6672 100644 --- a/packages/shared/src/typeUtils.ts +++ b/packages/shared/src/typeUtils.ts @@ -5,7 +5,7 @@ export type UnionToIntersection = ( : never // make keys required but keep undefined values -export type LooseRequired = { [P in string & keyof T]: T[P] } +export type LooseRequired = { [P in keyof (T & Required )]: T[P] } // If the the type T accepts type "any", output type Y, otherwise output type N. // https://stackoverflow.com/questions/49927523/disallow-call-with-any/49928360#49928360 diff --git a/packages/size-check/package.json b/packages/size-check/package.json index 012994e1890..a9f2de07995 100644 --- a/packages/size-check/package.json +++ b/packages/size-check/package.json @@ -1,6 +1,6 @@ { "name": "@vue/size-check", - "version": "3.2.39", + "version": "3.2.41", "private": true, "scripts": { "build": "vite build" diff --git a/packages/template-explorer/package.json b/packages/template-explorer/package.json index cec92e47587..f8d3892e79e 100644 --- a/packages/template-explorer/package.json +++ b/packages/template-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@vue/template-explorer", - "version": "3.2.39", + "version": "3.2.41", "private": true, "buildOptions": { "formats": [ diff --git a/packages/template-explorer/src/index.ts b/packages/template-explorer/src/index.ts index d676c717373..3cf9c6b52cf 100644 --- a/packages/template-explorer/src/index.ts +++ b/packages/template-explorer/src/index.ts @@ -275,5 +275,5 @@ function debounce any>( fn(...args) prevTimer = null }, delay) - }) as any + }) as T } diff --git a/packages/vue-compat/package.json b/packages/vue-compat/package.json index 2f126f8ecf6..df789070bf8 100644 --- a/packages/vue-compat/package.json +++ b/packages/vue-compat/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compat", - "version": "3.2.39", + "version": "3.2.41", "description": "Vue 3 compatibility build for Vue 2", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -37,7 +37,12 @@ "url": "https://github.com/vuejs/core/issues" }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue-compat#readme", + "dependencies": { + "@babel/parser": "^7.16.4", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + }, "peerDependencies": { - "vue": "3.2.39" + "vue": "3.2.41" } } diff --git a/packages/vue/package.json b/packages/vue/package.json index c356fb9220e..4749c984f61 100644 --- a/packages/vue/package.json +++ b/packages/vue/package.json @@ -1,6 +1,6 @@ { "name": "vue", - "version": "3.2.39", + "version": "3.2.41", "description": "The progressive JavaScript framework for building modern web UI.", "main": "index.js", "module": "dist/vue.runtime.esm-bundler.js", @@ -68,10 +68,10 @@ }, "homepage": "https://github.com/vuejs/core/tree/main/packages/vue#readme", "dependencies": { - "@vue/shared": "3.2.39", - "@vue/compiler-dom": "3.2.39", - "@vue/runtime-dom": "3.2.39", - "@vue/compiler-sfc": "3.2.39", - "@vue/server-renderer": "3.2.39" + "@vue/shared": "3.2.41", + "@vue/compiler-dom": "3.2.41", + "@vue/runtime-dom": "3.2.41", + "@vue/compiler-sfc": "3.2.41", + "@vue/server-renderer": "3.2.41" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90bc778d991..b13c39b66b9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,7 +35,7 @@ importers: 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 @@ -84,7 +84,7 @@ importers: marked: 4.0.10 minimist: 1.2.5 npm-run-all: 4.1.5 - prettier: 2.5.1 + prettier: 2.7.1 puppeteer: 10.4.0 rollup: 2.38.5 rollup-plugin-node-builtins: 2.1.2 @@ -106,7 +106,7 @@ importers: specifiers: '@babel/parser': ^7.16.4 '@babel/types': ^7.16.0 - '@vue/shared': 3.2.39 + '@vue/shared': 3.2.41 estree-walker: ^2.0.2 source-map: ^0.6.1 dependencies: @@ -119,8 +119,8 @@ importers: packages/compiler-dom: specifiers: - '@vue/compiler-core': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/compiler-core': 3.2.41 + '@vue/shared': 3.2.41 dependencies: '@vue/compiler-core': link:../compiler-core '@vue/shared': link:../shared @@ -131,12 +131,12 @@ importers: '@babel/types': ^7.16.0 '@types/estree': ^0.0.48 '@types/lru-cache': ^5.1.0 - '@vue/compiler-core': 3.2.39 - '@vue/compiler-dom': 3.2.39 - '@vue/compiler-ssr': 3.2.39 + '@vue/compiler-core': 3.2.41 + '@vue/compiler-dom': 3.2.41 + '@vue/compiler-ssr': 3.2.41 '@vue/consolidate': ^0.17.3 - '@vue/reactivity-transform': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/reactivity-transform': 3.2.41 + '@vue/shared': 3.2.41 estree-walker: ^2.0.2 hash-sum: ^2.0.0 lru-cache: ^5.1.1 @@ -174,15 +174,15 @@ importers: packages/compiler-ssr: specifiers: - '@vue/compiler-dom': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/compiler-dom': 3.2.41 + '@vue/shared': 3.2.41 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/shared': link:../shared packages/reactivity: specifiers: - '@vue/shared': 3.2.39 + '@vue/shared': 3.2.41 dependencies: '@vue/shared': link:../shared @@ -191,8 +191,8 @@ importers: '@babel/core': ^7.16.0 '@babel/parser': ^7.16.4 '@babel/types': ^7.16.0 - '@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 dependencies: @@ -207,16 +207,16 @@ importers: packages/runtime-core: specifiers: - '@vue/reactivity': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/reactivity': 3.2.41 + '@vue/shared': 3.2.41 dependencies: '@vue/reactivity': link:../reactivity '@vue/shared': link:../shared packages/runtime-dom: specifiers: - '@vue/runtime-core': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/runtime-core': 3.2.41 + '@vue/shared': 3.2.41 csstype: ^2.6.8 dependencies: '@vue/runtime-core': link:../runtime-core @@ -225,16 +225,16 @@ importers: packages/runtime-test: specifiers: - '@vue/runtime-core': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/runtime-core': 3.2.41 + '@vue/shared': 3.2.41 dependencies: '@vue/runtime-core': link:../runtime-core '@vue/shared': link:../shared packages/server-renderer: specifiers: - '@vue/compiler-ssr': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/compiler-ssr': 3.2.41 + '@vue/shared': 3.2.41 dependencies: '@vue/compiler-ssr': link:../compiler-ssr '@vue/shared': link:../shared @@ -246,7 +246,7 @@ importers: file-saver: ^2.0.5 jszip: ^3.6.0 vite: ^3.0.0 - vue: 3.2.39 + vue: 3.2.41 dependencies: '@vue/repl': 1.3.0_vue@packages+vue file-saver: 2.0.5 @@ -272,11 +272,11 @@ importers: packages/vue: specifiers: - '@vue/compiler-dom': 3.2.39 - '@vue/compiler-sfc': 3.2.39 - '@vue/runtime-dom': 3.2.39 - '@vue/server-renderer': 3.2.39 - '@vue/shared': 3.2.39 + '@vue/compiler-dom': 3.2.41 + '@vue/compiler-sfc': 3.2.41 + '@vue/runtime-dom': 3.2.41 + '@vue/server-renderer': 3.2.41 + '@vue/shared': 3.2.41 dependencies: '@vue/compiler-dom': link:../compiler-dom '@vue/compiler-sfc': link:../compiler-sfc @@ -285,7 +285,14 @@ importers: '@vue/shared': link:../shared packages/vue-compat: - specifiers: {} + specifiers: + '@babel/parser': ^7.16.4 + estree-walker: ^2.0.2 + source-map: ^0.6.1 + dependencies: + '@babel/parser': 7.16.4 + estree-walker: 2.0.2 + source-map: 0.6.1 packages: @@ -5834,8 +5841,8 @@ packages: engines: {node: '>= 0.8.0'} dev: true - /prettier/2.5.1: - resolution: {integrity: sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==} + /prettier/2.7.1: + resolution: {integrity: sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==} engines: {node: '>=10.13.0'} hasBin: true dev: true @@ -6590,7 +6597,7 @@ packages: dev: true /source-map/0.5.7: - resolution: {integrity: sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=} + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} engines: {node: '>=0.10.0'} dev: true diff --git a/test-dts/compiler.test-d.ts b/test-dts/compiler.test-d.ts new file mode 100644 index 00000000000..974b49492a4 --- /dev/null +++ b/test-dts/compiler.test-d.ts @@ -0,0 +1,20 @@ +import { + expectType, + createBlock, + VNode, + Teleport, + Text, + Static, + Comment, + Fragment, + Suspense, + defineComponent +} from './index' + +expectType (createBlock(Teleport)) +expectType (createBlock(Text)) +expectType (createBlock(Static)) +expectType (createBlock(Comment)) +expectType (createBlock(Fragment)) +expectType (createBlock(Suspense)) +expectType (createBlock(defineComponent({}))) diff --git a/test-dts/h.test-d.ts b/test-dts/h.test-d.ts index c71b54a2aa8..6116fff74a9 100644 --- a/test-dts/h.test-d.ts +++ b/test-dts/h.test-d.ts @@ -47,6 +47,7 @@ describe('h inference w/ Fragment', () => { describe('h inference w/ Teleport', () => { h(Teleport, { to: '#foo' }, 'hello') + h(Teleport, { to: '#foo' }, { default() {} }) // @ts-expect-error expectError(h(Teleport)) // @ts-expect-error @@ -144,12 +145,11 @@ describe('h inference w/ defineComponent', () => { // expectError(h(Foo, { bar: 1, foo: 1 })) // }) -// #922 +// #922 and #3218 describe('h support for generic component type', () => { function foo(bar: Component) { h(bar) h(bar, 'hello') - // @ts-expect-error h(bar, { id: 'ok' }, 'hello') } foo({}) diff --git a/tsconfig.json b/tsconfig.json index 506f4a07ad9..518f13edfcd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "outDir": "dist", "sourceMap": false, "target": "es2016", + "newLine": "LF", "useDefineForClassFields": false, "module": "esnext", "moduleResolution": "node",