refactor(compiler-sfc): collapse genRuntimeProps logic

This commit is contained in:
Evan You 2023-04-11 14:06:54 +08:00
parent d0ac57872c
commit 9f5692e052
4 changed files with 144 additions and 138 deletions

View File

@ -55,8 +55,7 @@ import { ScriptCompileContext } from './script/context'
import { import {
processDefineProps, processDefineProps,
DEFINE_PROPS, DEFINE_PROPS,
WITH_DEFAULTS, WITH_DEFAULTS
extractRuntimeProps
} from './script/defineProps' } from './script/defineProps'
import { import {
resolveObjectKey, resolveObjectKey,
@ -279,7 +278,7 @@ export function compileScript(
} }
// metadata that needs to be returned // metadata that needs to be returned
const bindingMetadata: BindingMetadata = {} // const ctx.bindingMetadata: BindingMetadata = {}
const userImports: Record<string, ImportBinding> = Object.create(null) const userImports: Record<string, ImportBinding> = Object.create(null)
const scriptBindings: Record<string, BindingTypes> = Object.create(null) const scriptBindings: Record<string, BindingTypes> = Object.create(null)
const setupBindings: Record<string, BindingTypes> = Object.create(null) const setupBindings: Record<string, BindingTypes> = Object.create(null)
@ -1231,7 +1230,6 @@ export function compileScript(
} }
// 4. extract runtime props/emits code from setup context type // 4. extract runtime props/emits code from setup context type
extractRuntimeProps(ctx)
if (emitsTypeDecl) { if (emitsTypeDecl) {
extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error) extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error)
} }
@ -1265,31 +1263,28 @@ export function compileScript(
// 7. analyze binding metadata // 7. analyze binding metadata
if (scriptAst) { if (scriptAst) {
Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body)) Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
} }
if (ctx.propsRuntimeDecl) { if (ctx.propsRuntimeDecl) {
for (const key of getObjectOrArrayExpressionKeys(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) { for (const key in ctx.modelDecls) {
bindingMetadata[key] = BindingTypes.PROPS ctx.bindingMetadata[key] = BindingTypes.PROPS
} }
// props aliases // props aliases
if (ctx.propsDestructureDecl) { if (ctx.propsDestructureDecl) {
if (ctx.propsDestructureRestId) { if (ctx.propsDestructureRestId) {
bindingMetadata[ctx.propsDestructureRestId] = ctx.bindingMetadata[ctx.propsDestructureRestId] =
BindingTypes.SETUP_REACTIVE_CONST BindingTypes.SETUP_REACTIVE_CONST
} }
for (const key in ctx.propsDestructuredBindings) { for (const key in ctx.propsDestructuredBindings) {
const { local } = ctx.propsDestructuredBindings[key] const { local } = ctx.propsDestructuredBindings[key]
if (local !== key) { if (local !== key) {
bindingMetadata[local] = BindingTypes.PROPS_ALIASED ctx.bindingMetadata[local] = BindingTypes.PROPS_ALIASED
;(bindingMetadata.__propsAliases || ;(ctx.bindingMetadata.__propsAliases ||
(bindingMetadata.__propsAliases = {}))[local] = key (ctx.bindingMetadata.__propsAliases = {}))[local] = key
} }
} }
} }
@ -1297,7 +1292,7 @@ export function compileScript(
userImports userImports
)) { )) {
if (isType) continue if (isType) continue
bindingMetadata[key] = ctx.bindingMetadata[key] =
imported === '*' || imported === '*' ||
(imported === 'default' && source.endsWith('.vue')) || (imported === 'default' && source.endsWith('.vue')) ||
source === 'vue' source === 'vue'
@ -1305,15 +1300,15 @@ export function compileScript(
: BindingTypes.SETUP_MAYBE_REF : BindingTypes.SETUP_MAYBE_REF
} }
for (const key in scriptBindings) { for (const key in scriptBindings) {
bindingMetadata[key] = scriptBindings[key] ctx.bindingMetadata[key] = scriptBindings[key]
} }
for (const key in setupBindings) { for (const key in setupBindings) {
bindingMetadata[key] = setupBindings[key] ctx.bindingMetadata[key] = setupBindings[key]
} }
// known ref bindings // known ref bindings
if (refBindings) { if (refBindings) {
for (const key of 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') ctx.helperImports.add('unref')
s.prependLeft( s.prependLeft(
startOffset, 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 // generate getter for import bindings
// skip vue imports since we know they will never change // skip vue imports since we know they will never change
returned += `get ${key}() { return ${key} }, ` 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 // local let binding, also add setter
const setArg = key === 'v' ? `_v` : `v` const setArg = key === 'v' ? `_v` : `v`
returned += returned +=
@ -1434,7 +1429,7 @@ export function compileScript(
options.templateOptions.compilerOptions), options.templateOptions.compilerOptions),
inline: true, inline: true,
isTS, isTS,
bindingMetadata bindingMetadata: ctx.bindingMetadata
} }
}) })
if (tips.length) { if (tips.length) {
@ -1570,7 +1565,7 @@ export function compileScript(
return { return {
...scriptSetup, ...scriptSetup,
bindings: bindingMetadata, bindings: ctx.bindingMetadata,
imports: userImports, imports: userImports,
content: s.toString(), content: s.toString(),
map: genSourceMap map: genSourceMap

View File

@ -3,12 +3,9 @@ import { SFCDescriptor } from '../parse'
import { generateCodeFrame } from '@vue/shared' import { generateCodeFrame } from '@vue/shared'
import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser' import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
import { SFCScriptCompileOptions } from '../compileScript' import { SFCScriptCompileOptions } from '../compileScript'
import { import { PropsDeclType, PropsDestructureBindings } from './defineProps'
PropsDeclType,
PropTypeData,
PropsDestructureBindings
} from './defineProps'
import { ModelDecl } from './defineModel' import { ModelDecl } from './defineModel'
import { BindingMetadata } from '../../../compiler-core/src'
export class ScriptCompileContext { export class ScriptCompileContext {
isJS: boolean isJS: boolean
@ -23,12 +20,6 @@ export class ScriptCompileContext {
scriptStartOffset = this.descriptor.script?.loc.start.offset scriptStartOffset = this.descriptor.script?.loc.start.offset
scriptEndOffset = this.descriptor.script?.loc.end.offset scriptEndOffset = this.descriptor.script?.loc.end.offset
helperImports: Set<string> = new Set()
helper(key: string): string {
this.helperImports.add(key)
return `_${key}`
}
declaredTypes: Record<string, string[]> = Object.create(null) declaredTypes: Record<string, string[]> = Object.create(null)
// macros presence check // macros presence check
@ -49,11 +40,19 @@ export class ScriptCompileContext {
propsDestructuredBindings: PropsDestructureBindings = Object.create(null) propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
propsDestructureRestId: string | undefined propsDestructureRestId: string | undefined
propsRuntimeDefaults: Node | undefined propsRuntimeDefaults: Node | undefined
typeDeclaredProps: Record<string, PropTypeData> = {}
// defineModel // defineModel
modelDecls: Record<string, ModelDecl> = {} modelDecls: Record<string, ModelDecl> = {}
// codegen
bindingMetadata: BindingMetadata = {}
helperImports: Set<string> = new Set()
helper(key: string): string {
this.helperImports.add(key)
return `_${key}`
}
constructor( constructor(
public descriptor: SFCDescriptor, public descriptor: SFCDescriptor,
public options: SFCScriptCompileOptions public options: SFCScriptCompileOptions

View File

@ -9,7 +9,7 @@ export interface ModelDecl {
identifier: string | undefined identifier: string | undefined
} }
export function genModels(ctx: ScriptCompileContext) { export function genModelProps(ctx: ScriptCompileContext) {
if (!ctx.hasDefineModelCall) return if (!ctx.hasDefineModelCall) return
const isProd = !!ctx.options.isProd const isProd = !!ctx.options.isProd

View File

@ -9,7 +9,7 @@ import {
ObjectExpression, ObjectExpression,
Expression Expression
} from '@babel/types' } from '@babel/types'
import { isFunctionType } from '@vue/compiler-dom' import { BindingTypes, isFunctionType } from '@vue/compiler-dom'
import { ScriptCompileContext } from './context' import { ScriptCompileContext } from './context'
import { inferRuntimeType, resolveQualifiedType } from './resolveType' import { inferRuntimeType, resolveQualifiedType } from './resolveType'
import { import {
@ -22,7 +22,7 @@ import {
unwrapTSNode, unwrapTSNode,
toRuntimeTypeString toRuntimeTypeString
} from './utils' } from './utils'
import { genModels } from './defineModel' import { genModelProps } from './defineModel'
export const DEFINE_PROPS = 'defineProps' export const DEFINE_PROPS = 'defineProps'
export const WITH_DEFAULTS = 'withDefaults' export const WITH_DEFAULTS = 'withDefaults'
@ -174,44 +174,6 @@ function processWithDefaults(
return true 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 { export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
let propsDecls: undefined | string let propsDecls: undefined | string
if (ctx.propsRuntimeDecl) { if (ctx.propsRuntimeDecl) {
@ -234,10 +196,10 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
} }
} }
} else if (ctx.propsTypeDecl) { } else if (ctx.propsTypeDecl) {
propsDecls = genPropsFromTS(ctx) propsDecls = genRuntimePropsFromTypes(ctx)
} }
const modelsDecls = genModels(ctx) const modelsDecls = genModelProps(ctx)
if (propsDecls && modelsDecls) { if (propsDecls && modelsDecls) {
return `${ctx.helper('mergeModels')}(${propsDecls}, ${modelsDecls})` return `${ctx.helper('mergeModels')}(${propsDecls}, ${modelsDecls})`
@ -246,31 +208,92 @@ export function genRuntimeProps(ctx: ScriptCompileContext): string | undefined {
} }
} }
function genPropsFromTS(ctx: ScriptCompileContext) { function genRuntimePropsFromTypes(ctx: ScriptCompileContext) {
const keys = Object.keys(ctx.typeDeclaredProps) const propStrings: string[] = []
if (!keys.length) return
const hasStaticDefaults = hasStaticWithDefaults(ctx) const hasStaticDefaults = hasStaticWithDefaults(ctx)
let propsDecls = `{
${keys // this is only called if propsTypeDecl exists
.map(key => { const node = ctx.propsTypeDecl!
let defaultString: string | undefined const members = node.type === 'TSTypeLiteral' ? node.members : node.body
const destructured = genDestructuredDefaultValue( 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
)
// 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']
}
}
}
propStrings.push(
genRuntimePropFromType(
ctx, ctx,
key, key,
ctx.typeDeclaredProps[key].type !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(
ctx.propsRuntimeDefaults
)})`
}
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) { if (destructured) {
defaultString = `default: ${destructured.valueString}${ defaultString = `default: ${destructured.valueString}${
destructured.needSkipFactory ? `, skipFactory: true` : `` destructured.needSkipFactory ? `, skipFactory: true` : ``
}` }`
} else if (hasStaticDefaults) { } else if (hasStaticDefaults) {
const prop = ( const prop = (ctx.propsRuntimeDefaults as ObjectExpression).properties.find(
ctx.propsRuntimeDefaults as ObjectExpression node => {
).properties.find(node => {
if (node.type === 'SpreadElement') return false if (node.type === 'SpreadElement') return false
return resolveObjectKey(node.key, node.computed) === key return resolveObjectKey(node.key, node.computed) === key
}) as ObjectProperty | ObjectMethod }
) as ObjectProperty | ObjectMethod
if (prop) { if (prop) {
if (prop.type === 'ObjectProperty') { if (prop.type === 'ObjectProperty') {
// prop has corresponding static default value // prop has corresponding static default value
@ -283,7 +306,6 @@ function genPropsFromTS(ctx: ScriptCompileContext) {
} }
} }
const { type, required, skipCheck } = ctx.typeDeclaredProps[key]
if (!ctx.options.isProd) { if (!ctx.options.isProd) {
return `${key}: { ${concatStrings([ return `${key}: { ${concatStrings([
`type: ${toRuntimeTypeString(type)}`, `type: ${toRuntimeTypeString(type)}`,
@ -309,16 +331,6 @@ function genPropsFromTS(ctx: ScriptCompileContext) {
// production: checks are useless // production: checks are useless
return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}` return `${key}: ${defaultString ? `{ ${defaultString} }` : `{}`}`
} }
})
.join(',\n ')}\n }`
if (ctx.propsRuntimeDefaults && !hasStaticDefaults) {
propsDecls = `${ctx.helper('mergeDefaults')}(${propsDecls}, ${ctx.getString(
ctx.propsRuntimeDefaults
)})`
}
return propsDecls
} }
/** /**
@ -327,7 +339,7 @@ function genPropsFromTS(ctx: ScriptCompileContext) {
* declarations. Otherwise we will have to fallback to runtime merging. * declarations. Otherwise we will have to fallback to runtime merging.
*/ */
function hasStaticWithDefaults(ctx: ScriptCompileContext) { function hasStaticWithDefaults(ctx: ScriptCompileContext) {
return ( return !!(
ctx.propsRuntimeDefaults && ctx.propsRuntimeDefaults &&
ctx.propsRuntimeDefaults.type === 'ObjectExpression' && ctx.propsRuntimeDefaults.type === 'ObjectExpression' &&
ctx.propsRuntimeDefaults.properties.every( ctx.propsRuntimeDefaults.properties.every(