diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts index 05bece0ed..b648def88 100644 --- a/packages/compiler-sfc/src/compileScript.ts +++ b/packages/compiler-sfc/src/compileScript.ts @@ -55,8 +55,7 @@ import { ScriptCompileContext } from './script/context' import { processDefineProps, DEFINE_PROPS, - WITH_DEFAULTS, - extractRuntimeProps + WITH_DEFAULTS } from './script/defineProps' import { resolveObjectKey, @@ -279,7 +278,7 @@ export function compileScript( } // metadata that needs to be returned - const bindingMetadata: BindingMetadata = {} + // const ctx.bindingMetadata: BindingMetadata = {} const userImports: Record = Object.create(null) const scriptBindings: Record = Object.create(null) const setupBindings: Record = Object.create(null) @@ -1231,7 +1230,6 @@ export function compileScript( } // 4. extract runtime props/emits code from setup context type - extractRuntimeProps(ctx) if (emitsTypeDecl) { extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error) } @@ -1265,31 +1263,28 @@ export function compileScript( // 7. analyze binding metadata if (scriptAst) { - Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body)) + Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body)) } if (ctx.propsRuntimeDecl) { for (const key of getObjectOrArrayExpressionKeys(ctx.propsRuntimeDecl)) { - bindingMetadata[key] = BindingTypes.PROPS + ctx.bindingMetadata[key] = BindingTypes.PROPS } } - for (const key in ctx.typeDeclaredProps) { - bindingMetadata[key] = BindingTypes.PROPS - } for (const key in ctx.modelDecls) { - bindingMetadata[key] = BindingTypes.PROPS + ctx.bindingMetadata[key] = BindingTypes.PROPS } // props aliases if (ctx.propsDestructureDecl) { if (ctx.propsDestructureRestId) { - bindingMetadata[ctx.propsDestructureRestId] = + ctx.bindingMetadata[ctx.propsDestructureRestId] = BindingTypes.SETUP_REACTIVE_CONST } for (const key in ctx.propsDestructuredBindings) { const { local } = ctx.propsDestructuredBindings[key] if (local !== key) { - bindingMetadata[local] = BindingTypes.PROPS_ALIASED - ;(bindingMetadata.__propsAliases || - (bindingMetadata.__propsAliases = {}))[local] = key + ctx.bindingMetadata[local] = BindingTypes.PROPS_ALIASED + ;(ctx.bindingMetadata.__propsAliases || + (ctx.bindingMetadata.__propsAliases = {}))[local] = key } } } @@ -1297,7 +1292,7 @@ export function compileScript( userImports )) { if (isType) continue - bindingMetadata[key] = + ctx.bindingMetadata[key] = imported === '*' || (imported === 'default' && source.endsWith('.vue')) || source === 'vue' @@ -1305,15 +1300,15 @@ export function compileScript( : BindingTypes.SETUP_MAYBE_REF } for (const key in scriptBindings) { - bindingMetadata[key] = scriptBindings[key] + ctx.bindingMetadata[key] = scriptBindings[key] } for (const key in setupBindings) { - bindingMetadata[key] = setupBindings[key] + ctx.bindingMetadata[key] = setupBindings[key] } // known ref bindings if (refBindings) { for (const key of refBindings) { - bindingMetadata[key] = BindingTypes.SETUP_REF + ctx.bindingMetadata[key] = BindingTypes.SETUP_REF } } @@ -1327,7 +1322,7 @@ export function compileScript( ctx.helperImports.add('unref') s.prependLeft( startOffset, - `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n` + `\n${genCssVarsCode(cssVars, ctx.bindingMetadata, scopeId, isProd)}\n` ) } @@ -1401,7 +1396,7 @@ export function compileScript( // generate getter for import bindings // skip vue imports since we know they will never change returned += `get ${key}() { return ${key} }, ` - } else if (bindingMetadata[key] === BindingTypes.SETUP_LET) { + } else if (ctx.bindingMetadata[key] === BindingTypes.SETUP_LET) { // local let binding, also add setter const setArg = key === 'v' ? `_v` : `v` returned += @@ -1434,7 +1429,7 @@ export function compileScript( options.templateOptions.compilerOptions), inline: true, isTS, - bindingMetadata + bindingMetadata: ctx.bindingMetadata } }) if (tips.length) { @@ -1570,7 +1565,7 @@ export function compileScript( return { ...scriptSetup, - bindings: bindingMetadata, + bindings: ctx.bindingMetadata, imports: userImports, content: s.toString(), map: genSourceMap diff --git a/packages/compiler-sfc/src/script/context.ts b/packages/compiler-sfc/src/script/context.ts index 0dc94a537..007db8620 100644 --- a/packages/compiler-sfc/src/script/context.ts +++ b/packages/compiler-sfc/src/script/context.ts @@ -3,12 +3,9 @@ import { SFCDescriptor } from '../parse' import { generateCodeFrame } from '@vue/shared' import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser' import { SFCScriptCompileOptions } from '../compileScript' -import { - PropsDeclType, - PropTypeData, - PropsDestructureBindings -} from './defineProps' +import { PropsDeclType, PropsDestructureBindings } from './defineProps' import { ModelDecl } from './defineModel' +import { BindingMetadata } from '../../../compiler-core/src' export class ScriptCompileContext { isJS: boolean @@ -23,12 +20,6 @@ export class ScriptCompileContext { scriptStartOffset = this.descriptor.script?.loc.start.offset scriptEndOffset = this.descriptor.script?.loc.end.offset - helperImports: Set = new Set() - helper(key: string): string { - this.helperImports.add(key) - return `_${key}` - } - declaredTypes: Record = Object.create(null) // macros presence check @@ -49,11 +40,19 @@ export class ScriptCompileContext { propsDestructuredBindings: PropsDestructureBindings = Object.create(null) propsDestructureRestId: string | undefined propsRuntimeDefaults: Node | undefined - typeDeclaredProps: Record = {} // defineModel modelDecls: Record = {} + // codegen + bindingMetadata: BindingMetadata = {} + + helperImports: Set = new Set() + helper(key: string): string { + this.helperImports.add(key) + return `_${key}` + } + constructor( public descriptor: SFCDescriptor, public options: SFCScriptCompileOptions diff --git a/packages/compiler-sfc/src/script/defineModel.ts b/packages/compiler-sfc/src/script/defineModel.ts index befe0d95c..1aa5c55ec 100644 --- a/packages/compiler-sfc/src/script/defineModel.ts +++ b/packages/compiler-sfc/src/script/defineModel.ts @@ -9,7 +9,7 @@ export interface ModelDecl { identifier: string | undefined } -export function genModels(ctx: ScriptCompileContext) { +export function genModelProps(ctx: ScriptCompileContext) { if (!ctx.hasDefineModelCall) return const isProd = !!ctx.options.isProd diff --git a/packages/compiler-sfc/src/script/defineProps.ts b/packages/compiler-sfc/src/script/defineProps.ts index 9def99cf3..61339373e 100644 --- a/packages/compiler-sfc/src/script/defineProps.ts +++ b/packages/compiler-sfc/src/script/defineProps.ts @@ -9,7 +9,7 @@ import { ObjectExpression, Expression } from '@babel/types' -import { isFunctionType } from '@vue/compiler-dom' +import { BindingTypes, isFunctionType } from '@vue/compiler-dom' import { ScriptCompileContext } from './context' import { inferRuntimeType, resolveQualifiedType } from './resolveType' import { @@ -22,7 +22,7 @@ import { unwrapTSNode, toRuntimeTypeString } from './utils' -import { genModels } from './defineModel' +import { genModelProps } from './defineModel' export const DEFINE_PROPS = 'defineProps' export const WITH_DEFAULTS = 'withDefaults' @@ -174,44 +174,6 @@ function processWithDefaults( return true } -export function extractRuntimeProps(ctx: ScriptCompileContext) { - const node = ctx.propsTypeDecl - if (!node) return - const members = node.type === 'TSTypeLiteral' ? node.members : node.body - for (const m of members) { - if ( - (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') && - m.key.type === 'Identifier' - ) { - let type: string[] | undefined - let skipCheck = false - if (m.type === 'TSMethodSignature') { - type = ['Function'] - } else if (m.typeAnnotation) { - type = inferRuntimeType( - m.typeAnnotation.typeAnnotation, - ctx.declaredTypes - ) - // skip check for result containing unknown types - if (type.includes(UNKNOWN_TYPE)) { - if (type.includes('Boolean') || type.includes('Function')) { - type = type.filter(t => t !== UNKNOWN_TYPE) - skipCheck = true - } else { - type = ['null'] - } - } - } - ctx.typeDeclaredProps[m.key.name] = { - key: m.key.name, - required: !m.optional, - type: type || [`null`], - skipCheck - } - } - } -} - export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { let propsDecls: undefined | string if (ctx.propsRuntimeDecl) { @@ -234,10 +196,10 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } } else if (ctx.propsTypeDecl) { - propsDecls = genPropsFromTS(ctx) + propsDecls = genRuntimePropsFromTypes(ctx) } - const modelsDecls = genModels(ctx) + const modelsDecls = genModelProps(ctx) if (propsDecls && modelsDecls) { return `${ctx.helper('mergeModels')}(${propsDecls}, ${modelsDecls})` @@ -246,71 +208,61 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined { } } -function genPropsFromTS(ctx: ScriptCompileContext) { - const keys = Object.keys(ctx.typeDeclaredProps) - if (!keys.length) return - +function genRuntimePropsFromTypes(ctx: ScriptCompileContext) { + const propStrings: string[] = [] const hasStaticDefaults = hasStaticWithDefaults(ctx) - let propsDecls = `{ - ${keys - .map(key => { - let defaultString: string | undefined - const destructured = genDestructuredDefaultValue( - ctx, - key, - ctx.typeDeclaredProps[key].type + + // this is only called if propsTypeDecl exists + const node = ctx.propsTypeDecl! + const members = node.type === 'TSTypeLiteral' ? node.members : node.body + for (const m of members) { + if ( + (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') && + m.key.type === 'Identifier' + ) { + const key = m.key.name + let type: string[] | undefined + let skipCheck = false + if (m.type === 'TSMethodSignature') { + type = ['Function'] + } else if (m.typeAnnotation) { + type = inferRuntimeType( + m.typeAnnotation.typeAnnotation, + ctx.declaredTypes ) - if (destructured) { - defaultString = `default: ${destructured.valueString}${ - destructured.needSkipFactory ? `, skipFactory: true` : `` - }` - } else if (hasStaticDefaults) { - const prop = ( - ctx.propsRuntimeDefaults as ObjectExpression - ).properties.find(node => { - if (node.type === 'SpreadElement') return false - return resolveObjectKey(node.key, node.computed) === key - }) as ObjectProperty | ObjectMethod - if (prop) { - if (prop.type === 'ObjectProperty') { - // prop has corresponding static default value - defaultString = `default: ${ctx.getString(prop.value)}` - } else { - defaultString = `${prop.async ? 'async ' : ''}${ - prop.kind !== 'method' ? `${prop.kind} ` : '' - }default() ${ctx.getString(prop.body)}` - } + // skip check for result containing unknown types + if (type.includes(UNKNOWN_TYPE)) { + if (type.includes('Boolean') || type.includes('Function')) { + type = type.filter(t => t !== UNKNOWN_TYPE) + skipCheck = true + } else { + type = ['null'] } } + } - const { type, required, skipCheck } = ctx.typeDeclaredProps[key] - if (!ctx.options.isProd) { - return `${key}: { ${concatStrings([ - `type: ${toRuntimeTypeString(type)}`, - `required: ${required}`, - skipCheck && 'skipCheck: true', - defaultString - ])} }` - } else if ( - type.some( - el => - el === 'Boolean' || - ((!hasStaticDefaults || defaultString) && el === 'Function') - ) - ) { - // #4783 for boolean, should keep the type - // #7111 for function, if default value exists or it's not static, should keep it - // in production - return `${key}: { ${concatStrings([ - `type: ${toRuntimeTypeString(type)}`, - defaultString - ])} }` - } else { - // production: checks are useless - return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}` - } - }) - .join(',\n ')}\n }` + propStrings.push( + genRuntimePropFromType( + ctx, + key, + !m.optional, + type || [`null`], + skipCheck, + hasStaticDefaults + ) + ) + + // register bindings + ctx.bindingMetadata[key] = BindingTypes.PROPS + } + } + + if (!propStrings.length) { + return + } + + let propsDecls = `{ + ${propStrings.join(',\n ')}\n }` if (ctx.propsRuntimeDefaults && !hasStaticDefaults) { propsDecls = `${ctx.helper('mergeDefaults')}(${propsDecls}, ${ctx.getString( @@ -321,13 +273,73 @@ function genPropsFromTS(ctx: ScriptCompileContext) { return propsDecls } +function genRuntimePropFromType( + ctx: ScriptCompileContext, + key: string, + required: boolean, + type: string[], + skipCheck: boolean, + hasStaticDefaults: boolean +): string { + let defaultString: string | undefined + const destructured = genDestructuredDefaultValue(ctx, key, type) + if (destructured) { + defaultString = `default: ${destructured.valueString}${ + destructured.needSkipFactory ? `, skipFactory: true` : `` + }` + } else if (hasStaticDefaults) { + const prop = (ctx.propsRuntimeDefaults as ObjectExpression).properties.find( + node => { + if (node.type === 'SpreadElement') return false + return resolveObjectKey(node.key, node.computed) === key + } + ) as ObjectProperty | ObjectMethod + if (prop) { + if (prop.type === 'ObjectProperty') { + // prop has corresponding static default value + defaultString = `default: ${ctx.getString(prop.value)}` + } else { + defaultString = `${prop.async ? 'async ' : ''}${ + prop.kind !== 'method' ? `${prop.kind} ` : '' + }default() ${ctx.getString(prop.body)}` + } + } + } + + if (!ctx.options.isProd) { + return `${key}: { ${concatStrings([ + `type: ${toRuntimeTypeString(type)}`, + `required: ${required}`, + skipCheck && 'skipCheck: true', + defaultString + ])} }` + } else if ( + type.some( + el => + el === 'Boolean' || + ((!hasStaticDefaults || defaultString) && el === 'Function') + ) + ) { + // #4783 for boolean, should keep the type + // #7111 for function, if default value exists or it's not static, should keep it + // in production + return `${key}: { ${concatStrings([ + `type: ${toRuntimeTypeString(type)}`, + defaultString + ])} }` + } else { + // production: checks are useless + return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}` + } +} + /** * check defaults. If the default object is an object literal with only * static properties, we can directly generate more optimized default * declarations. Otherwise we will have to fallback to runtime merging. */ function hasStaticWithDefaults(ctx: ScriptCompileContext) { - return ( + return !!( ctx.propsRuntimeDefaults && ctx.propsRuntimeDefaults.type === 'ObjectExpression' && ctx.propsRuntimeDefaults.properties.every(