From 24db9516d8b4857182ec1a3af86cb7346691679b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 12 Jul 2023 11:03:14 +0800 Subject: [PATCH 1/4] fix(compiler-sfc): don't hoist props and emit (#8535) fix #7805 close #7812 --- .../__snapshots__/compileScript.spec.ts.snap | 87 ++++--------------- .../__tests__/compileScript.spec.ts | 76 ++++------------ .../__snapshots__/defineEmits.spec.ts.snap | 64 +++++++------- .../__snapshots__/defineProps.spec.ts.snap | 48 +++------- .../definePropsDestructure.spec.ts.snap | 59 ++++++++++++- .../compileScript/defineEmits.spec.ts | 6 +- .../definePropsDestructure.spec.ts | 52 +++++++++++ packages/compiler-sfc/src/compileScript.ts | 55 +++++++----- packages/compiler-sfc/src/script/context.ts | 7 +- .../compiler-sfc/src/script/defineEmits.ts | 5 +- .../compiler-sfc/src/script/defineProps.ts | 55 ++++++------ .../src/script/definePropsDestructure.ts | 1 - 12 files changed, 260 insertions(+), 255 deletions(-) diff --git a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap index b7925ada895..949c9946d9f 100644 --- a/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap +++ b/packages/compiler-sfc/__tests__/__snapshots__/compileScript.spec.ts.snap @@ -62,6 +62,24 @@ return { fn } })" `; +exports[`SFC compile - `) - assertCode(content) - expect(content).toMatch(`const a = 1;`) // test correct removal - expect(content).toMatch(`props: ['item'],`) - expect(content).toMatch(`emits: ['a'],`) - }) - - // #6757 - test('defineProps/defineEmits in multi-variable declaration fix #6757 ', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`const a = 1;`) // test correct removal - expect(content).toMatch(`props: ['item'],`) - expect(content).toMatch(`emits: ['a'],`) - }) - - // #7422 - test('defineProps/defineEmits in multi-variable declaration fix #7422', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`props: ['item'],`) - expect(content).toMatch(`emits: ['foo'],`) - expect(content).toMatch(`const a = 0,`) - expect(content).toMatch(`b = 0;`) - }) - - test('defineProps/defineEmits in multi-variable declaration (full removal)', () => { - const { content } = compile(` - - `) - assertCode(content) - expect(content).toMatch(`props: ['item'],`) - expect(content).toMatch(`emits: ['a'],`) - }) - describe(' + `) + assertCode(content) + + expect(content).toMatch(`console.log('test')`) + expect(content).toMatch(`const props = __props;`) + expect(content).toMatch(`const emit = __emit;`) + expect(content).toMatch(`(function () {})()`) + }) + test('script setup first, named default export', () => { const { content } = compile(` + `) + assertCode(content) + expect(content).toMatch(`const a = 1;`) + expect(content).toMatch(`props: ['item'],`) + }) + + // #6757 + test('multi-variable declaration fix #6757 ', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const a = 1;`) + expect(content).toMatch(`props: ['item'],`) + }) + + // #7422 + test('multi-variable declaration fix #7422', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`const a = 0,`) + expect(content).toMatch(`b = 0;`) + expect(content).toMatch(`props: ['item'],`) + }) + + test('defineProps/defineEmits in multi-variable declaration (full removal)', () => { + const { content } = compile(` + + `) + assertCode(content) + expect(content).toMatch(`props: ['item'],`) + expect(content).toMatch(`emits: ['a'],`) + }) + describe('errors', () => { test('should error on deep destructure', () => { expect(() => diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 046797cfbe5..cfcc607c72d 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -552,7 +552,11 @@ export function compileScript( (processDefineSlots(ctx, init, decl.id) || processDefineModel(ctx, init, decl.id)) - if (isDefineProps || isDefineEmits) { + if ( + isDefineProps && + !ctx.propsDestructureRestId && + ctx.propsDestructureDecl + ) { if (left === 1) { ctx.s.remove(node.start! + startOffset, node.end! + startOffset) } else { @@ -570,6 +574,12 @@ export function compileScript( ctx.s.remove(start, end) left-- } + } else if (isDefineEmits) { + ctx.s.overwrite( + startOffset + init.start!, + startOffset + init.end!, + '__emit' + ) } else { lastNonRemoved = i } @@ -781,22 +791,29 @@ export function compileScript( // inject user assignment of props // we use a default __props so that template expressions referencing props // can use it directly - if (ctx.propsIdentifier) { - ctx.s.prependLeft( - startOffset, - `\nconst ${ctx.propsIdentifier} = __props;\n` - ) - } - if (ctx.propsDestructureRestId) { - ctx.s.prependLeft( - startOffset, - `\nconst ${ctx.propsDestructureRestId} = ${ctx.helper( - `createPropsRestProxy` - )}(__props, ${JSON.stringify( - Object.keys(ctx.propsDestructuredBindings) - )});\n` - ) + if (ctx.propsDecl) { + if (ctx.propsDestructureRestId) { + ctx.s.overwrite( + startOffset + ctx.propsCall!.start!, + startOffset + ctx.propsCall!.end!, + `${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify( + Object.keys(ctx.propsDestructuredBindings) + )})` + ) + ctx.s.overwrite( + startOffset + ctx.propsDestructureDecl!.start!, + startOffset + ctx.propsDestructureDecl!.end!, + ctx.propsDestructureRestId + ) + } else if (!ctx.propsDestructureDecl) { + ctx.s.overwrite( + startOffset + ctx.propsCall!.start!, + startOffset + ctx.propsCall!.end!, + '__props' + ) + } } + // inject temp variables for async context preservation if (hasAwait) { const any = ctx.isTS ? `: any` : `` @@ -807,10 +824,8 @@ export function compileScript( ctx.hasDefineExposeCall || !options.inlineTemplate ? [`expose: __expose`] : [] - if (ctx.emitIdentifier) { - destructureElements.push( - ctx.emitIdentifier === `emit` ? `emit` : `emit: ${ctx.emitIdentifier}` - ) + if (ctx.emitDecl) { + destructureElements.push(`emit: __emit`) } if (destructureElements.length) { args += `, { ${destructureElements.join(', ')} }` diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index af2dee16568..5fe09d28a42 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -1,4 +1,4 @@ -import { Node, ObjectPattern, Program } from '@babel/types' +import { CallExpression, Node, ObjectPattern, Program } from '@babel/types' import { SFCDescriptor } from '../parse' import { generateCodeFrame } from '@vue/shared' import { parse as babelParse, ParserPlugin } from '@babel/parser' @@ -38,7 +38,8 @@ export class ScriptCompileContext { hasDefineModelCall = false // defineProps - propsIdentifier: string | undefined + propsCall: CallExpression | undefined + propsDecl: Node | undefined propsRuntimeDecl: Node | undefined propsTypeDecl: Node | undefined propsDestructureDecl: ObjectPattern | undefined @@ -49,7 +50,7 @@ export class ScriptCompileContext { // defineEmits emitsRuntimeDecl: Node | undefined emitsTypeDecl: Node | undefined - emitIdentifier: string | undefined + emitDecl: Node | undefined // defineModel modelDecls: Record = {} diff --git a/packages/compiler-sfc/src/script/defineEmits.ts b/packages/compiler-sfc/src/script/defineEmits.ts index a50cf91fc4a..02014d1b276 100644 --- a/packages/compiler-sfc/src/script/defineEmits.ts +++ b/packages/compiler-sfc/src/script/defineEmits.ts @@ -29,10 +29,7 @@ export function processDefineEmits( ctx.emitsTypeDecl = node.typeParameters.params[0] } - if (declId) { - ctx.emitIdentifier = - declId.type === 'Identifier' ? declId.name : ctx.getString(declId) - } + ctx.emitDecl = declId return true } diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index 1ae5a16e3d6..5004e314da1 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -77,15 +77,14 @@ export function processDefineProps( ctx.propsTypeDecl = node.typeParameters.params[0] } - if (declId) { - // handle props destructure - if (declId.type === 'ObjectPattern') { - processPropsDestructure(ctx, declId) - } else { - ctx.propsIdentifier = ctx.getString(declId) - } + // handle props destructure + if (declId && declId.type === 'ObjectPattern') { + processPropsDestructure(ctx, declId) } + ctx.propsCall = node + ctx.propsDecl = declId + return true } @@ -97,31 +96,33 @@ function processWithDefaults( if (!isCallOf(node, WITH_DEFAULTS)) { return false } - if (processDefineProps(ctx, node.arguments[0], declId)) { - if (ctx.propsRuntimeDecl) { - ctx.error( - `${WITH_DEFAULTS} can only be used with type-based ` + - `${DEFINE_PROPS} declaration.`, - node - ) - } - if (ctx.propsDestructureDecl) { - ctx.error( - `${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` + - `Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`, - node.callee - ) - } - ctx.propsRuntimeDefaults = node.arguments[1] - if (!ctx.propsRuntimeDefaults) { - ctx.error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node) - } - } else { + if (!processDefineProps(ctx, node.arguments[0], declId)) { ctx.error( `${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`, node.arguments[0] || node ) } + + if (ctx.propsRuntimeDecl) { + ctx.error( + `${WITH_DEFAULTS} can only be used with type-based ` + + `${DEFINE_PROPS} declaration.`, + node + ) + } + if (ctx.propsDestructureDecl) { + ctx.error( + `${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` + + `Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`, + node.callee + ) + } + ctx.propsRuntimeDefaults = node.arguments[1] + if (!ctx.propsRuntimeDefaults) { + ctx.error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node) + } + ctx.propsCall = node + return true } diff --git a/packages/compiler-sfc/src/script/definePropsDestructure.ts b/packages/compiler-sfc/src/script/definePropsDestructure.ts index 5965262f3c3..5aa895bc7fe 100644 --- a/packages/compiler-sfc/src/script/definePropsDestructure.ts +++ b/packages/compiler-sfc/src/script/definePropsDestructure.ts @@ -28,7 +28,6 @@ export function processPropsDestructure( declId: ObjectPattern ) { if (!ctx.options.propsDestructure && !ctx.options.reactivityTransform) { - ctx.propsIdentifier = ctx.getString(declId) return } From 70c3ac746d584d20956628bec185d24e0e90cef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=89=E5=92=B2=E6=99=BA=E5=AD=90=20Kevin=20Deng?= Date: Wed, 12 Jul 2023 11:05:43 +0800 Subject: [PATCH 2/4] dx(compiler-sfc): warn when disabled defineModel (#8534) --- packages/compiler-sfc/src/script/defineModel.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/compiler-sfc/src/script/defineModel.ts b/packages/compiler-sfc/src/script/defineModel.ts index 987e67fc147..432b8676fbd 100644 --- a/packages/compiler-sfc/src/script/defineModel.ts +++ b/packages/compiler-sfc/src/script/defineModel.ts @@ -24,7 +24,15 @@ export function processDefineModel( node: Node, declId?: LVal ): boolean { - if (!ctx.options.defineModel || !isCallOf(node, DEFINE_MODEL)) { + if (!isCallOf(node, DEFINE_MODEL)) { + return false + } + + if (!ctx.options.defineModel) { + warnOnce( + `defineModel() is an experimental feature and disabled by default.\n` + + `To enable it, follow the RFC at https://github.com/vuejs/rfcs/discussions/503.` + ) return false } From 24d98f03276de5b0fbced5a4c9d61b24e7d9d084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=B6=E8=BF=9C=E6=96=B9?= Date: Wed, 12 Jul 2023 11:13:20 +0800 Subject: [PATCH 3/4] perf(custom-element): cancel `MutationObserver` listener when disconnected (#8666) --- packages/runtime-dom/src/apiCustomElement.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 2add422586d..5662b0b535b 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -178,7 +178,7 @@ export class VueElement extends BaseClass { private _resolved = false private _numberProps: Record | null = null private _styles?: HTMLStyleElement[] - + private _ob?: MutationObserver | null = null constructor( private _def: InnerComponentDef, private _props: Record = {}, @@ -215,6 +215,10 @@ export class VueElement extends BaseClass { disconnectedCallback() { this._connected = false + if (this._ob) { + this._ob.disconnect() + this._ob = null + } nextTick(() => { if (!this._connected) { render(null, this.shadowRoot!) @@ -235,11 +239,13 @@ export class VueElement extends BaseClass { } // watch future attr changes - new MutationObserver(mutations => { + this._ob = new MutationObserver(mutations => { for (const m of mutations) { this._setAttr(m.attributeName!) } - }).observe(this, { attributes: true }) + }) + + this._ob.observe(this, { attributes: true }) const resolve = (def: InnerComponentDef, isAsync = false) => { const { props, styles } = def From 37a14a5dae9999bbe684c6de400afc63658ffe90 Mon Sep 17 00:00:00 2001 From: Evan You Date: Wed, 12 Jul 2023 11:38:59 +0800 Subject: [PATCH 4/4] Revert "fix(types): propagate type parameter constraints for TypeScript 4.8 (#6351)" This reverts commit 516fabb725cdf29e948b8b83dd6db9b80fbd706d. --- packages/runtime-core/src/apiCreateApp.ts | 4 ++-- packages/runtime-core/src/directives.ts | 16 ++++------------ packages/runtime-core/src/renderer.ts | 8 ++++---- packages/runtime-core/src/vnode.ts | 4 ++-- 4 files changed, 12 insertions(+), 20 deletions(-) diff --git a/packages/runtime-core/src/apiCreateApp.ts b/packages/runtime-core/src/apiCreateApp.ts index 971b406cf01..c5ac9d68a52 100644 --- a/packages/runtime-core/src/apiCreateApp.ts +++ b/packages/runtime-core/src/apiCreateApp.ts @@ -16,7 +16,7 @@ import { ComponentPublicInstance } from './componentPublicInstance' import { Directive, validateDirectiveName } from './directives' -import { RendererElement, RootRenderFunction } from './renderer' +import { RootRenderFunction } from './renderer' import { InjectionKey } from './apiInject' import { warn } from './warning' import { createVNode, cloneVNode, VNode } from './vnode' @@ -196,7 +196,7 @@ export type CreateAppFunction = ( let uid = 0 -export function createAppAPI( +export function createAppAPI( render: RootRenderFunction, hydrate?: RootHydrateFunction ): CreateAppFunction { diff --git a/packages/runtime-core/src/directives.ts b/packages/runtime-core/src/directives.ts index eb80cd495f6..18c3352b002 100644 --- a/packages/runtime-core/src/directives.ts +++ b/packages/runtime-core/src/directives.ts @@ -21,7 +21,6 @@ import { ComponentPublicInstance } from './componentPublicInstance' import { mapCompatDirectiveHook } from './compat/customDirective' import { pauseTracking, resetTracking } from '@vue/reactivity' import { traverse } from './apiWatch' -import { RendererElement } from './renderer' export interface DirectiveBinding { instance: ComponentPublicInstance | null @@ -32,11 +31,7 @@ export interface DirectiveBinding { dir: ObjectDirective } -export type DirectiveHook< - T extends RendererElement = any, - Prev = VNode | null, - V = any -> = ( +export type DirectiveHook | null, V = any> = ( el: T, binding: DirectiveBinding, vnode: VNode, @@ -48,7 +43,7 @@ export type SSRDirectiveHook = ( vnode: VNode ) => Data | undefined -export interface ObjectDirective { +export interface ObjectDirective { created?: DirectiveHook beforeMount?: DirectiveHook mounted?: DirectiveHook @@ -60,12 +55,9 @@ export interface ObjectDirective { deep?: boolean } -export type FunctionDirective< - T extends RendererElement = any, - V = any -> = DirectiveHook +export type FunctionDirective = DirectiveHook -export type Directive = +export type Directive = | ObjectDirective | FunctionDirective diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index 3ab81cbf3ba..383e17fb0f5 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -90,7 +90,7 @@ export type RootRenderFunction = ( export interface RendererOptions< HostNode = RendererNode, - HostElement extends RendererElement = RendererElement + HostElement = RendererElement > { patchProp( el: HostElement, @@ -145,7 +145,7 @@ export interface RendererElement extends RendererNode {} // to optimize bundle size. export interface RendererInternals< HostNode = RendererNode, - HostElement extends RendererElement = RendererElement + HostElement = RendererElement > { p: PatchFn um: UnmountFn @@ -295,7 +295,7 @@ export const queuePostRenderEffect = __FEATURE_SUSPENSE__ */ export function createRenderer< HostNode = RendererNode, - HostElement extends RendererElement = RendererElement + HostElement = RendererElement >(options: RendererOptions) { return baseCreateRenderer(options) } @@ -312,7 +312,7 @@ export function createHydrationRenderer( // overload 1: no hydration function baseCreateRenderer< HostNode = RendererNode, - HostElement extends RendererElement = RendererElement + HostElement = RendererElement >(options: RendererOptions): Renderer // overload 2: with hydration diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 89242b94247..f8cf6652d31 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -133,7 +133,7 @@ export type VNodeNormalizedChildren = export interface VNode< HostNode = RendererNode, - HostElement extends RendererElement = RendererElement, + HostElement = RendererElement, ExtraProps = { [key: string]: any } > { /** @@ -613,7 +613,7 @@ export function guardReactiveProps(props: (Data & VNodeProps) | null) { : props } -export function cloneVNode( +export function cloneVNode( vnode: VNode, extraProps?: (Data & VNodeProps) | null, mergeRef = false