diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e374825..e10e5632c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +## [3.4.2](https://github.com/vuejs/core/compare/v3.4.1...v3.4.2) (2023-12-30) + + +### Bug Fixes + +* **compiler-sfc:** fix dev regression for dot / namespace component usage ([dce99c1](https://github.com/vuejs/core/commit/dce99c12df981ca45a4d848c37ba8b16496025f0)), closes [#9947](https://github.com/vuejs/core/issues/9947) +* **runtime-core:** support deep: false when watch reactive ([#9928](https://github.com/vuejs/core/issues/9928)) ([4f703d1](https://github.com/vuejs/core/commit/4f703d120d76d711084346f73ea295c73e6ef6b6)), closes [#9916](https://github.com/vuejs/core/issues/9916) +* **ssr:** fix hydration error for slot outlet inside transition-group ([#9937](https://github.com/vuejs/core/issues/9937)) ([6cb00ed](https://github.com/vuejs/core/commit/6cb00ed0f9b64428ec18fada0f68467d6a813fde)), closes [#9933](https://github.com/vuejs/core/issues/9933) + + + +## [3.4.1](https://github.com/vuejs/core/compare/v3.4.0...v3.4.1) (2023-12-30) + + +### Bug Fixes + +* **compat:** correct enum value for COMPILER_FILTERS feature ([#9875](https://github.com/vuejs/core/issues/9875)) ([77d33e2](https://github.com/vuejs/core/commit/77d33e263cf19983caf4e5c53a0eb0bee374843c)) +* **defineModel:** always default modifiers to empty object ([9bc3c7e](https://github.com/vuejs/core/commit/9bc3c7e29cf15f5ca96703542d10cfd786a3fc55)), closes [#9945](https://github.com/vuejs/core/issues/9945) +* **defineModel:** support local mutation when only prop but no listener is passed ([97ce041](https://github.com/vuejs/core/commit/97ce041910b6ca4bef10f939493d6b5a06ea5b07)) +* **types:** fix defineModel watch type error ([#9942](https://github.com/vuejs/core/issues/9942)) ([4af8583](https://github.com/vuejs/core/commit/4af85835f7e593a7dffa7dc7e99f14877eb70fd1)), closes [#9939](https://github.com/vuejs/core/issues/9939) + + +### Features + +* **compiler-sfc:** support passing template parsing options when parsing sfc ([6fab855](https://github.com/vuejs/core/commit/6fab8551e4aeef4610987640de8b435b1ae321bb)) (necessary to fix https://github.com/vitejs/vite-plugin-vue/issues/322) + + + # [3.4.0 Slam Dunk](https://github.com/vuejs/core/compare/v3.4.0-rc.3...v3.4.0) (2023-12-29) > Read [this blog post](https://blog.vuejs.org/posts/vue-3-4) for an overview of the release highlights. diff --git a/packages/compiler-core/package.json b/packages/compiler-core/package.json index 15c4cad8b..ac26dcf61 100644 --- a/packages/compiler-core/package.json +++ b/packages/compiler-core/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-core", - "version": "3.4.0", + "version": "3.4.2", "description": "@vue/compiler-core", "main": "index.js", "module": "dist/compiler-core.esm-bundler.js", diff --git a/packages/compiler-dom/package.json b/packages/compiler-dom/package.json index 33323e17f..85edcc0e5 100644 --- a/packages/compiler-dom/package.json +++ b/packages/compiler-dom/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-dom", - "version": "3.4.0", + "version": "3.4.2", "description": "@vue/compiler-dom", "main": "index.js", "module": "dist/compiler-dom.esm-bundler.js", diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index a2d24599c..d63e6ec4d 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -639,190 +639,6 @@ return { foo, bar, baz, y, z } }" `; -exports[`SFC compile - - `) - // FooBar: should not be matched by plain text or incorrect case - // FooBaz: used as PascalCase component - // FooQux: used as kebab-case component - // foo: lowercase component - expect(content).toMatch( - `return { fooBar, get FooBaz() { return FooBaz }, ` + - `get FooQux() { return FooQux }, get foo() { return foo } }`, - ) - assertCode(content) - }) - - test('directive', () => { - const { content } = compile(` - - - `) - expect(content).toMatch(`return { get vMyDir() { return vMyDir } }`) - assertCode(content) - }) - - test('dynamic arguments', () => { - const { content } = compile(` - - - `) - expect(content).toMatch( - `return { get FooBar() { return FooBar }, get foo() { return foo }, ` + - `get bar() { return bar }, get baz() { return baz } }`, - ) - assertCode(content) - }) - - // https://github.com/vuejs/core/issues/4599 - test('attribute expressions', () => { - const { content } = compile(` - - - `) - expect(content).toMatch( - `return { cond, get bar() { return bar }, get baz() { return baz } }`, - ) - assertCode(content) - }) - - test('vue interpolations', () => { - const { content } = compile(` - - - `) - // x: used in interpolation - // y: should not be matched by {{ yy }} or 'y' in binding exps - // x$y: #4274 should escape special chars when creating Regex - expect(content).toMatch( - `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`, - ) - assertCode(content) - }) - - // #4340 interpolations in template strings - test('js template string interpolations', () => { - const { content } = compile(` - - - `) - // VAR2 should not be matched - expect(content).toMatch( - `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`, - ) - assertCode(content) - }) - - // edge case: last tag in template - test('last tag', () => { - const { content } = compile(` - - - `) - expect(content).toMatch( - `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`, - ) - assertCode(content) - }) - - test('TS annotations', () => { - const { content } = compile(` - - - `) - expect(content).toMatch(`return { a, b, get Baz() { return Baz } }`) - assertCode(content) - }) - - // vuejs/vue#12591 - test('v-on inline statement', () => { - // should not error - compile(` - - - `) - }) - - test('template ref', () => { - const { content } = compile(` - - - `) - expect(content).toMatch( - 'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }', - ) - assertCode(content) - }) - - // https://github.com/nuxt/nuxt/issues/22416 - test('property access', () => { - const { content } = compile(` - - - `) - expect(content).toMatch('return { get Foo() { return Foo } }') - assertCode(content) - }) - - test('spread operator', () => { - const { content } = compile(` - - - `) - expect(content).toMatch('return { get Foo() { return Foo } }') - assertCode(content) - }) - - test('property access (whitespace)', () => { - const { content } = compile(` - - - `) - expect(content).toMatch('return { get Foo() { return Foo } }') - assertCode(content) - }) - }) - describe('inlineTemplate mode', () => { test('should work', () => { const { content } = compile( diff --git a/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap new file mode 100644 index 000000000..cfdbf5f45 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/__snapshots__/importUsageCheck.spec.ts.snap @@ -0,0 +1,215 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`TS annotations 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { Foo, Bar, Baz, Qux, Fred } from './x' + const a = 1 + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + function b() {} + +return { a, b, get Baz() { return Baz } } +} + +})" +`; + +exports[`attribute expressions 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { bar, baz } from './x' + const cond = true + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { cond, get bar() { return bar }, get baz() { return baz } } +} + +})" +`; + +exports[`components 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { FooBar, FooBaz, FooQux, foo } from './x' + const fooBar: FooBar = 1 + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { fooBar, get FooBaz() { return FooBaz }, get FooQux() { return FooQux }, get foo() { return foo } } +} + +})" +`; + +exports[`directive 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { vMyDir } from './x' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get vMyDir() { return vMyDir } } +} + +})" +`; + +exports[`dynamic arguments 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { FooBar, foo, bar, unused, baz } from './x' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get FooBar() { return FooBar }, get foo() { return foo }, get bar() { return bar }, get baz() { return baz } } +} + +})" +`; + +exports[`import namespace 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import * as Foo from './foo' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get Foo() { return Foo } } +} + +})" +`; + +exports[`js template string interpolations 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { VAR, VAR2, VAR3 } from './x' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get VAR() { return VAR }, get VAR3() { return VAR3 } } +} + +})" +`; + +exports[`last tag 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { FooBaz, Last } from './x' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get FooBaz() { return FooBaz }, get Last() { return Last } } +} + +})" +`; + +exports[`namespace / dot component usage 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import * as Foo from './foo' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get Foo() { return Foo } } +} + +})" +`; + +exports[`property access (whitespace) 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { Foo, Bar, Baz } from './foo' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get Foo() { return Foo } } +} + +})" +`; + +exports[`property access 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { Foo, Bar, Baz } from './foo' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get Foo() { return Foo } } +} + +})" +`; + +exports[`spread operator 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { Foo, Bar, Baz } from './foo' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get Foo() { return Foo } } +} + +})" +`; + +exports[`template ref 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { foo, bar, Baz } from './foo' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } } +} + +})" +`; + +exports[`vue interpolations 1`] = ` +"import { defineComponent as _defineComponent } from 'vue' +import { x, y, z, x$y } from './x' + +export default /*#__PURE__*/_defineComponent({ + setup(__props, { expose: __expose }) { + __expose(); + + +return { get x() { return x }, get z() { return z }, get x$y() { return x$y } } +} + +})" +`; diff --git a/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts b/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts new file mode 100644 index 000000000..d9fd1dfe5 --- /dev/null +++ b/packages/compiler-sfc/__tests__/compileScript/importUsageCheck.spec.ts @@ -0,0 +1,235 @@ +import { assertCode, compileSFCScript as compile } from '../utils' + +// in dev mode, declared bindings are returned as an object from setup() +// when using TS, users may import types which should not be returned as +// values, so we need to check import usage in the template to determine +// what to be returned. + +test('components', () => { + const { content } = compile(` + + + `) + // FooBar: should not be matched by plain text or incorrect case + // FooBaz: used as PascalCase component + // FooQux: used as kebab-case component + // foo: lowercase component + expect(content).toMatch( + `return { fooBar, get FooBaz() { return FooBaz }, ` + + `get FooQux() { return FooQux }, get foo() { return foo } }`, + ) + assertCode(content) +}) + +test('directive', () => { + const { content } = compile(` + + + `) + expect(content).toMatch(`return { get vMyDir() { return vMyDir } }`) + assertCode(content) +}) + +test('dynamic arguments', () => { + const { content } = compile(` + + + `) + expect(content).toMatch( + `return { get FooBar() { return FooBar }, get foo() { return foo }, ` + + `get bar() { return bar }, get baz() { return baz } }`, + ) + assertCode(content) +}) + +// https://github.com/vuejs/core/issues/4599 +test('attribute expressions', () => { + const { content } = compile(` + + + `) + expect(content).toMatch( + `return { cond, get bar() { return bar }, get baz() { return baz } }`, + ) + assertCode(content) +}) + +test('vue interpolations', () => { + const { content } = compile(` + + + `) + // x: used in interpolation + // y: should not be matched by {{ yy }} or 'y' in binding exps + // x$y: #4274 should escape special chars when creating Regex + expect(content).toMatch( + `return { get x() { return x }, get z() { return z }, get x$y() { return x$y } }`, + ) + assertCode(content) +}) + +// #4340 interpolations in template strings +test('js template string interpolations', () => { + const { content } = compile(` + + + `) + // VAR2 should not be matched + expect(content).toMatch( + `return { get VAR() { return VAR }, get VAR3() { return VAR3 } }`, + ) + assertCode(content) +}) + +// edge case: last tag in template +test('last tag', () => { + const { content } = compile(` + + + `) + expect(content).toMatch( + `return { get FooBaz() { return FooBaz }, get Last() { return Last } }`, + ) + assertCode(content) +}) + +test('TS annotations', () => { + const { content } = compile(` + + + `) + expect(content).toMatch(`return { a, b, get Baz() { return Baz } }`) + assertCode(content) +}) + +// vuejs/vue#12591 +test('v-on inline statement', () => { + // should not error + compile(` + + + `) +}) + +test('template ref', () => { + const { content } = compile(` + + + `) + expect(content).toMatch( + 'return { get foo() { return foo }, get bar() { return bar }, get Baz() { return Baz } }', + ) + assertCode(content) +}) + +// https://github.com/nuxt/nuxt/issues/22416 +test('property access', () => { + const { content } = compile(` + + + `) + expect(content).toMatch('return { get Foo() { return Foo } }') + assertCode(content) +}) + +test('spread operator', () => { + const { content } = compile(` + + + `) + expect(content).toMatch('return { get Foo() { return Foo } }') + assertCode(content) +}) + +test('property access (whitespace)', () => { + const { content } = compile(` + + + `) + expect(content).toMatch('return { get Foo() { return Foo } }') + assertCode(content) +}) + +// #9974 +test('namespace / dot component usage', () => { + const { content } = compile(` + + + `) + expect(content).toMatch('return { get Foo() { return Foo } }') + assertCode(content) +}) diff --git a/packages/compiler-sfc/__tests__/parse.spec.ts b/packages/compiler-sfc/__tests__/parse.spec.ts index e650a850a..048dab693 100644 --- a/packages/compiler-sfc/__tests__/parse.spec.ts +++ b/packages/compiler-sfc/__tests__/parse.spec.ts @@ -1,5 +1,10 @@ import { parse } from '../src' -import { baseCompile, createRoot } from '@vue/compiler-core' +import { + ElementTypes, + NodeTypes, + baseCompile, + createRoot, +} from '@vue/compiler-core' import { SourceMapConsumer } from 'source-map-js' describe('compiler:sfc', () => { @@ -350,6 +355,32 @@ h1 { color: red } expect(descriptor.customBlocks[0].content).toBe(` <-& `) }) + test('should accept parser options', () => { + const { errors, descriptor } = parse(``, { + templateParseOptions: { + isCustomElement: t => t === 'hello', + }, + }) + expect(errors.length).toBe(0) + expect(descriptor.template!.ast!.children[0]).toMatchObject({ + type: NodeTypes.ELEMENT, + tag: 'hello', + tagType: ElementTypes.ELEMENT, + }) + + // test cache invalidation on different options + const { descriptor: d2 } = parse(``, { + templateParseOptions: { + isCustomElement: t => t !== 'hello', + }, + }) + expect(d2.template!.ast!.children[0]).toMatchObject({ + type: NodeTypes.ELEMENT, + tag: 'hello', + tagType: ElementTypes.COMPONENT, + }) + }) + describe('warnings', () => { function assertWarning(errors: Error[], msg: string) { expect(errors.some(e => e.message.match(msg))).toBe(true) diff --git a/packages/compiler-sfc/package.json b/packages/compiler-sfc/package.json index c4b9f3dad..70aabfb14 100644 --- a/packages/compiler-sfc/package.json +++ b/packages/compiler-sfc/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-sfc", - "version": "3.4.0", + "version": "3.4.2", "description": "@vue/compiler-sfc", "main": "dist/compiler-sfc.cjs.js", "module": "dist/compiler-sfc.esm-browser.js", diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index 0c891e821..00c97867c 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -3,6 +3,7 @@ import { type CompilerError, type ElementNode, NodeTypes, + type ParserOptions, type RootNode, type SourceLocation, createRoot, @@ -24,6 +25,11 @@ export interface SFCParseOptions { pad?: boolean | 'line' | 'space' ignoreEmpty?: boolean compiler?: TemplateCompiler + templateParseOptions?: ParserOptions + /** + * TODO remove in 3.5 + * @deprecated use `templateParseOptions: { prefixIdentifiers: false }` instead + */ parseExpressions?: boolean } @@ -97,24 +103,39 @@ export interface SFCParseResult { export const parseCache = createCache() +function genCacheKey(source: string, options: SFCParseOptions): string { + return ( + source + + JSON.stringify( + { + ...options, + compiler: { parse: options.compiler?.parse }, + }, + (_, val) => (typeof val === 'function' ? val.toString() : val), + ) + ) +} + export function parse( source: string, - { + options: SFCParseOptions = {}, +): SFCParseResult { + const sourceKey = genCacheKey(source, options) + const cache = parseCache.get(sourceKey) + if (cache) { + return cache + } + + const { sourceMap = true, filename = DEFAULT_FILENAME, sourceRoot = '', pad = false, ignoreEmpty = true, compiler = CompilerDOM, + templateParseOptions = {}, parseExpressions = true, - }: SFCParseOptions = {}, -): SFCParseResult { - const sourceKey = - source + sourceMap + filename + sourceRoot + pad + compiler.parse - const cache = parseCache.get(sourceKey) - if (cache) { - return cache - } + } = options const descriptor: SFCDescriptor = { filename, @@ -133,6 +154,7 @@ export function parse( const ast = compiler.parse(source, { parseMode: 'sfc', prefixIdentifiers: parseExpressions, + ...templateParseOptions, onError: e => { errors.push(e) }, diff --git a/packages/compiler-sfc/src/script/importUsageCheck.ts b/packages/compiler-sfc/src/script/importUsageCheck.ts index 5ea11f915..211efc490 100644 --- a/packages/compiler-sfc/src/script/importUsageCheck.ts +++ b/packages/compiler-sfc/src/script/importUsageCheck.ts @@ -16,12 +16,12 @@ import { camelize, capitalize, isBuiltInDirective } from '@vue/shared' * when not using inline mode. */ export function isImportUsed(local: string, sfc: SFCDescriptor): boolean { - return resolveTemplateUsageCheckString(sfc).has(local) + return resolveTemplateUsedIdentifiers(sfc).has(local) } const templateUsageCheckCache = createCache>() -function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { +function resolveTemplateUsedIdentifiers(sfc: SFCDescriptor): Set { const { content, ast } = sfc.template! const cached = templateUsageCheckCache.get(content) if (cached) { @@ -35,12 +35,14 @@ function resolveTemplateUsageCheckString(sfc: SFCDescriptor) { function walk(node: TemplateChildNode) { switch (node.type) { case NodeTypes.ELEMENT: + let tag = node.tag + if (tag.includes('.')) tag = tag.split('.')[0].trim() if ( - !parserOptions.isNativeTag!(node.tag) && - !parserOptions.isBuiltInComponent!(node.tag) + !parserOptions.isNativeTag!(tag) && + !parserOptions.isBuiltInComponent!(tag) ) { - ids.add(camelize(node.tag)) - ids.add(capitalize(camelize(node.tag))) + ids.add(camelize(tag)) + ids.add(capitalize(camelize(tag))) } for (let i = 0; i < node.props.length; i++) { const prop = node.props[i] diff --git a/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts b/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts index 977cc8027..655d68efb 100644 --- a/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts +++ b/packages/compiler-ssr/__tests__/ssrSlotOutlet.spec.ts @@ -127,4 +127,20 @@ describe('ssr: ', () => { }" `) }) + + test('inside transition-group', () => { + const { code } = compile( + ``, + ) + expect(code).toMatch(ssrHelpers[SSR_RENDER_SLOT_INNER]) + expect(code).toMatchInlineSnapshot(` + "const { ssrRenderSlotInner: _ssrRenderSlotInner, ssrRenderAttrs: _ssrRenderAttrs } = require("vue/server-renderer") + + return function ssrRender(_ctx, _push, _parent, _attrs) { + _push(\`\`) + _ssrRenderSlotInner(_ctx.$slots, "default", {}, null, _push, _parent, null, true) + _push(\`\`) + }" + `) + }) }) diff --git a/packages/compiler-ssr/package.json b/packages/compiler-ssr/package.json index 9b0f1dd48..45d1c1754 100644 --- a/packages/compiler-ssr/package.json +++ b/packages/compiler-ssr/package.json @@ -1,6 +1,6 @@ { "name": "@vue/compiler-ssr", - "version": "3.4.0", + "version": "3.4.2", "description": "@vue/compiler-ssr", "main": "dist/compiler-ssr.cjs.js", "types": "dist/compiler-ssr.d.ts", diff --git a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts index b75b4d033..ad08a23a4 100644 --- a/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts +++ b/packages/compiler-ssr/src/transforms/ssrTransformSlotOutlet.ts @@ -4,6 +4,7 @@ import { NodeTypes, type SlotOutletNode, TRANSITION, + TRANSITION_GROUP, createCallExpression, createFunctionExpression, isSlotOutlet, @@ -37,16 +38,19 @@ export const ssrTransformSlotOutlet: NodeTransform = (node, context) => { let method = SSR_RENDER_SLOT - // #3989 + // #3989, #9933 // check if this is a single slot inside a transition wrapper - since - // transition will unwrap the slot fragment into a single vnode at runtime, + // transition/transition-group will unwrap the slot fragment into vnode(s) at runtime, // we need to avoid rendering the slot as a fragment. const parent = context.parent + let componentType if ( parent && parent.type === NodeTypes.ELEMENT && parent.tagType === ElementTypes.COMPONENT && - resolveComponentType(parent, context, true) === TRANSITION && + ((componentType = resolveComponentType(parent, context, true)) === + TRANSITION || + componentType === TRANSITION_GROUP) && parent.children.filter(c => c.type === NodeTypes.ELEMENT).length === 1 ) { method = SSR_RENDER_SLOT_INNER diff --git a/packages/dts-test/watch.test-d.ts b/packages/dts-test/watch.test-d.ts index 323716d8a..5986d3d30 100644 --- a/packages/dts-test/watch.test-d.ts +++ b/packages/dts-test/watch.test-d.ts @@ -1,4 +1,11 @@ -import { computed, defineComponent, ref, shallowRef, watch } from 'vue' +import { + computed, + defineComponent, + defineModel, + ref, + shallowRef, + watch, +} from 'vue' import { expectType } from './utils' const source = ref('foo') @@ -106,3 +113,31 @@ defineComponent({ expectType(value) }) } + +{ + // defineModel + const bool = defineModel({ default: false }) + watch(bool, value => { + expectType(value) + }) + + const bool1 = defineModel() + watch(bool1, value => { + expectType(value) + }) + + const msg = defineModel({ required: true }) + watch(msg, value => { + expectType(value) + }) + + const arr = defineModel({ required: true }) + watch(arr, value => { + expectType(value) + }) + + const obj = defineModel<{ foo: string }>({ required: true }) + watch(obj, value => { + expectType<{ foo: string }>(value) + }) +} diff --git a/packages/reactivity/package.json b/packages/reactivity/package.json index 981cf8eba..7d6a792b1 100644 --- a/packages/reactivity/package.json +++ b/packages/reactivity/package.json @@ -1,6 +1,6 @@ { "name": "@vue/reactivity", - "version": "3.4.0", + "version": "3.4.2", "description": "@vue/reactivity", "main": "index.js", "module": "dist/reactivity.esm-bundler.js", diff --git a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts index 0528a1457..b50da90d4 100644 --- a/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts +++ b/packages/runtime-core/__tests__/apiSetupHelpers.spec.ts @@ -279,6 +279,41 @@ describe('SFC