mirror of https://github.com/vuejs/core.git
refactor(compiler-sfc): (wip) split compileScript logic, use context, move defineProps
This commit is contained in:
parent
b16866d56b
commit
acd7eb22cf
|
@ -15,12 +15,7 @@ import {
|
||||||
isCallOf
|
isCallOf
|
||||||
} from '@vue/compiler-dom'
|
} from '@vue/compiler-dom'
|
||||||
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
|
import { DEFAULT_FILENAME, SFCDescriptor, SFCScriptBlock } from './parse'
|
||||||
import {
|
import { parse as _parse, parseExpression, ParserPlugin } from '@babel/parser'
|
||||||
parse as _parse,
|
|
||||||
parseExpression,
|
|
||||||
ParserOptions,
|
|
||||||
ParserPlugin
|
|
||||||
} from '@babel/parser'
|
|
||||||
import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared'
|
import { camelize, capitalize, generateCodeFrame, makeMap } from '@vue/shared'
|
||||||
import {
|
import {
|
||||||
Node,
|
Node,
|
||||||
|
@ -41,7 +36,6 @@ import {
|
||||||
TSInterfaceBody,
|
TSInterfaceBody,
|
||||||
TSTypeElement,
|
TSTypeElement,
|
||||||
AwaitExpression,
|
AwaitExpression,
|
||||||
Program,
|
|
||||||
ObjectMethod,
|
ObjectMethod,
|
||||||
LVal,
|
LVal,
|
||||||
Expression,
|
Expression,
|
||||||
|
@ -59,13 +53,19 @@ import { warnOnce } from './warn'
|
||||||
import { rewriteDefaultAST } from './rewriteDefault'
|
import { rewriteDefaultAST } from './rewriteDefault'
|
||||||
import { createCache } from './cache'
|
import { createCache } from './cache'
|
||||||
import { shouldTransform, transformAST } from '@vue/reactivity-transform'
|
import { shouldTransform, transformAST } from '@vue/reactivity-transform'
|
||||||
import { transformDestructuredProps } from './script/propsDestructure'
|
import { transformDestructuredProps } from './script/definePropsDestructure'
|
||||||
|
import { resolveObjectKey, FromNormalScript } from './script/utils'
|
||||||
|
import { ScriptCompileContext } from './script/context'
|
||||||
|
import {
|
||||||
|
processDefineProps,
|
||||||
|
DEFINE_PROPS,
|
||||||
|
WITH_DEFAULTS,
|
||||||
|
PropsDeclType
|
||||||
|
} from './script/defineProps'
|
||||||
|
|
||||||
// Special compiler macros
|
// Special compiler macros
|
||||||
const DEFINE_PROPS = 'defineProps'
|
|
||||||
const DEFINE_EMITS = 'defineEmits'
|
const DEFINE_EMITS = 'defineEmits'
|
||||||
const DEFINE_EXPOSE = 'defineExpose'
|
const DEFINE_EXPOSE = 'defineExpose'
|
||||||
const WITH_DEFAULTS = 'withDefaults'
|
|
||||||
const DEFINE_OPTIONS = 'defineOptions'
|
const DEFINE_OPTIONS = 'defineOptions'
|
||||||
const DEFINE_SLOTS = 'defineSlots'
|
const DEFINE_SLOTS = 'defineSlots'
|
||||||
const DEFINE_MODEL = 'defineModel'
|
const DEFINE_MODEL = 'defineModel'
|
||||||
|
@ -149,8 +149,6 @@ export type PropsDestructureBindings = Record<
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|
||||||
type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
|
|
||||||
type PropsDeclType = FromNormalScript<TSTypeLiteral | TSInterfaceBody>
|
|
||||||
type EmitsDeclType = FromNormalScript<
|
type EmitsDeclType = FromNormalScript<
|
||||||
TSFunctionType | TSTypeLiteral | TSInterfaceBody
|
TSFunctionType | TSTypeLiteral | TSInterfaceBody
|
||||||
>
|
>
|
||||||
|
@ -195,41 +193,15 @@ export function compileScript(
|
||||||
? `const ${options.genDefaultAs} =`
|
? `const ${options.genDefaultAs} =`
|
||||||
: `export default`
|
: `export default`
|
||||||
const normalScriptDefaultVar = `__default__`
|
const normalScriptDefaultVar = `__default__`
|
||||||
const isJS =
|
|
||||||
scriptLang === 'js' ||
|
|
||||||
scriptLang === 'jsx' ||
|
|
||||||
scriptSetupLang === 'js' ||
|
|
||||||
scriptSetupLang === 'jsx'
|
|
||||||
const isTS =
|
|
||||||
scriptLang === 'ts' ||
|
|
||||||
scriptLang === 'tsx' ||
|
|
||||||
scriptSetupLang === 'ts' ||
|
|
||||||
scriptSetupLang === 'tsx'
|
|
||||||
|
|
||||||
// resolve parser plugins
|
const ctx = new ScriptCompileContext(sfc, options)
|
||||||
const plugins: ParserPlugin[] = []
|
const { isTS } = ctx
|
||||||
if (!isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {
|
|
||||||
plugins.push('jsx')
|
|
||||||
} else {
|
|
||||||
// If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins
|
|
||||||
if (options.babelParserPlugins)
|
|
||||||
options.babelParserPlugins = options.babelParserPlugins.filter(
|
|
||||||
n => n !== 'jsx'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
|
|
||||||
if (isTS) {
|
|
||||||
plugins.push('typescript')
|
|
||||||
if (!plugins.includes('decorators')) {
|
|
||||||
plugins.push('decorators-legacy')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!scriptSetup) {
|
if (!scriptSetup) {
|
||||||
if (!script) {
|
if (!script) {
|
||||||
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
|
||||||
}
|
}
|
||||||
if (scriptLang && !isJS && !isTS) {
|
if (scriptLang && !ctx.isJS && !ctx.isTS) {
|
||||||
// do not process non js/ts script blocks
|
// do not process non js/ts script blocks
|
||||||
return script
|
return script
|
||||||
}
|
}
|
||||||
|
@ -237,10 +209,7 @@ export function compileScript(
|
||||||
try {
|
try {
|
||||||
let content = script.content
|
let content = script.content
|
||||||
let map = script.map
|
let map = script.map
|
||||||
const scriptAst = _parse(content, {
|
const scriptAst = ctx.scriptAst!
|
||||||
plugins,
|
|
||||||
sourceType: 'module'
|
|
||||||
}).program
|
|
||||||
const bindings = analyzeScriptBindings(scriptAst.body)
|
const bindings = analyzeScriptBindings(scriptAst.body)
|
||||||
if (enableReactivityTransform && shouldTransform(content)) {
|
if (enableReactivityTransform && shouldTransform(content)) {
|
||||||
const s = new MagicString(source)
|
const s = new MagicString(source)
|
||||||
|
@ -268,7 +237,7 @@ export function compileScript(
|
||||||
if (cssVars.length || options.genDefaultAs) {
|
if (cssVars.length || options.genDefaultAs) {
|
||||||
const defaultVar = options.genDefaultAs || normalScriptDefaultVar
|
const defaultVar = options.genDefaultAs || normalScriptDefaultVar
|
||||||
const s = new MagicString(content)
|
const s = new MagicString(content)
|
||||||
rewriteDefaultAST(scriptAst.body, s, defaultVar)
|
rewriteDefaultAST(ctx.scriptAst!.body, s, defaultVar)
|
||||||
content = s.toString()
|
content = s.toString()
|
||||||
if (cssVars.length) {
|
if (cssVars.length) {
|
||||||
content += genNormalScriptCssVarsCode(
|
content += genNormalScriptCssVarsCode(
|
||||||
|
@ -304,7 +273,7 @@ export function compileScript(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (scriptSetupLang && !isJS && !isTS) {
|
if (scriptSetupLang && !ctx.isJS && !ctx.isTS) {
|
||||||
// do not process non js/ts script blocks
|
// do not process non js/ts script blocks
|
||||||
return scriptSetup
|
return scriptSetup
|
||||||
}
|
}
|
||||||
|
@ -317,21 +286,7 @@ export function compileScript(
|
||||||
const setupBindings: Record<string, BindingTypes> = Object.create(null)
|
const setupBindings: Record<string, BindingTypes> = Object.create(null)
|
||||||
|
|
||||||
let defaultExport: Node | undefined
|
let defaultExport: Node | undefined
|
||||||
let hasDefinePropsCall = false
|
// let propsRuntimeDefaults: Node | undefined
|
||||||
let hasDefineEmitCall = false
|
|
||||||
let hasDefineExposeCall = false
|
|
||||||
let hasDefaultExportName = false
|
|
||||||
let hasDefaultExportRender = false
|
|
||||||
let hasDefineOptionsCall = false
|
|
||||||
let hasDefineSlotsCall = false
|
|
||||||
let hasDefineModelCall = false
|
|
||||||
let propsRuntimeDecl: Node | undefined
|
|
||||||
let propsRuntimeDefaults: Node | undefined
|
|
||||||
let propsDestructureDecl: Node | undefined
|
|
||||||
let propsDestructureRestId: string | undefined
|
|
||||||
let propsTypeDecl: PropsDeclType | undefined
|
|
||||||
let propsTypeDeclRaw: Node | undefined
|
|
||||||
let propsIdentifier: string | undefined
|
|
||||||
let emitsRuntimeDecl: Node | undefined
|
let emitsRuntimeDecl: Node | undefined
|
||||||
let emitsTypeDecl: EmitsDeclType | undefined
|
let emitsTypeDecl: EmitsDeclType | undefined
|
||||||
let emitIdentifier: string | undefined
|
let emitIdentifier: string | undefined
|
||||||
|
@ -344,9 +299,6 @@ export function compileScript(
|
||||||
const typeDeclaredEmits: Set<string> = new Set()
|
const typeDeclaredEmits: Set<string> = new Set()
|
||||||
// record declared types for runtime props type generation
|
// record declared types for runtime props type generation
|
||||||
const declaredTypes: Record<string, string[]> = {}
|
const declaredTypes: Record<string, string[]> = {}
|
||||||
// props destructure data
|
|
||||||
const propsDestructuredBindings: PropsDestructureBindings =
|
|
||||||
Object.create(null)
|
|
||||||
|
|
||||||
// magic-string state
|
// magic-string state
|
||||||
const s = new MagicString(source)
|
const s = new MagicString(source)
|
||||||
|
@ -360,21 +312,6 @@ export function compileScript(
|
||||||
return `_${key}`
|
return `_${key}`
|
||||||
}
|
}
|
||||||
|
|
||||||
function parse(
|
|
||||||
input: string,
|
|
||||||
options: ParserOptions,
|
|
||||||
offset: number
|
|
||||||
): Program {
|
|
||||||
try {
|
|
||||||
return _parse(input, options).program
|
|
||||||
} catch (e: any) {
|
|
||||||
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
|
|
||||||
sfc.filename
|
|
||||||
}\n${generateCodeFrame(source, e.pos + offset, e.pos + offset + 1)}`
|
|
||||||
throw e
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function error(
|
function error(
|
||||||
msg: string,
|
msg: string,
|
||||||
node: Node,
|
node: Node,
|
||||||
|
@ -439,136 +376,14 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function processDefineProps(node: Node, declId?: LVal): boolean {
|
|
||||||
if (!isCallOf(node, DEFINE_PROPS)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasDefinePropsCall) {
|
|
||||||
error(`duplicate ${DEFINE_PROPS}() call`, node)
|
|
||||||
}
|
|
||||||
hasDefinePropsCall = true
|
|
||||||
|
|
||||||
propsRuntimeDecl = node.arguments[0]
|
|
||||||
|
|
||||||
// call has type parameters - infer runtime types from it
|
|
||||||
if (node.typeParameters) {
|
|
||||||
if (propsRuntimeDecl) {
|
|
||||||
error(
|
|
||||||
`${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
|
|
||||||
`at the same time. Use one or the other.`,
|
|
||||||
node
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
propsTypeDeclRaw = node.typeParameters.params[0]
|
|
||||||
propsTypeDecl = resolveQualifiedType(
|
|
||||||
propsTypeDeclRaw,
|
|
||||||
node => node.type === 'TSTypeLiteral'
|
|
||||||
) as PropsDeclType | undefined
|
|
||||||
|
|
||||||
if (!propsTypeDecl) {
|
|
||||||
error(
|
|
||||||
`type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
|
|
||||||
`or a reference to an interface or literal type.`,
|
|
||||||
propsTypeDeclRaw
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (declId) {
|
|
||||||
// handle props destructure
|
|
||||||
if (declId.type === 'ObjectPattern') {
|
|
||||||
propsDestructureDecl = declId
|
|
||||||
for (const prop of declId.properties) {
|
|
||||||
if (prop.type === 'ObjectProperty') {
|
|
||||||
const propKey = resolveObjectKey(prop.key, prop.computed)
|
|
||||||
|
|
||||||
if (!propKey) {
|
|
||||||
error(
|
|
||||||
`${DEFINE_PROPS}() destructure cannot use computed key.`,
|
|
||||||
prop.key
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (prop.value.type === 'AssignmentPattern') {
|
|
||||||
// default value { foo = 123 }
|
|
||||||
const { left, right } = prop.value
|
|
||||||
if (left.type !== 'Identifier') {
|
|
||||||
error(
|
|
||||||
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
|
||||||
left
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// store default value
|
|
||||||
propsDestructuredBindings[propKey] = {
|
|
||||||
local: left.name,
|
|
||||||
default: right
|
|
||||||
}
|
|
||||||
} else if (prop.value.type === 'Identifier') {
|
|
||||||
// simple destructure
|
|
||||||
propsDestructuredBindings[propKey] = {
|
|
||||||
local: prop.value.name
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error(
|
|
||||||
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
|
||||||
prop.value
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// rest spread
|
|
||||||
propsDestructureRestId = (prop.argument as Identifier).name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function processWithDefaults(node: Node, declId?: LVal): boolean {
|
|
||||||
if (!isCallOf(node, WITH_DEFAULTS)) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if (processDefineProps(node.arguments[0], declId)) {
|
|
||||||
if (propsRuntimeDecl) {
|
|
||||||
error(
|
|
||||||
`${WITH_DEFAULTS} can only be used with type-based ` +
|
|
||||||
`${DEFINE_PROPS} declaration.`,
|
|
||||||
node
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (propsDestructureDecl) {
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
propsRuntimeDefaults = node.arguments[1]
|
|
||||||
if (!propsRuntimeDefaults) {
|
|
||||||
error(`The 2nd argument of ${WITH_DEFAULTS} is required.`, node)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
error(
|
|
||||||
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
|
||||||
node.arguments[0] || node
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
function processDefineEmits(node: Node, declId?: LVal): boolean {
|
function processDefineEmits(node: Node, declId?: LVal): boolean {
|
||||||
if (!isCallOf(node, DEFINE_EMITS)) {
|
if (!isCallOf(node, DEFINE_EMITS)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (hasDefineEmitCall) {
|
if (ctx.hasDefineEmitCall) {
|
||||||
error(`duplicate ${DEFINE_EMITS}() call`, node)
|
error(`duplicate ${DEFINE_EMITS}() call`, node)
|
||||||
}
|
}
|
||||||
hasDefineEmitCall = true
|
ctx.hasDefineEmitCall = true
|
||||||
emitsRuntimeDecl = node.arguments[0]
|
emitsRuntimeDecl = node.arguments[0]
|
||||||
if (node.typeParameters) {
|
if (node.typeParameters) {
|
||||||
if (emitsRuntimeDecl) {
|
if (emitsRuntimeDecl) {
|
||||||
|
@ -608,10 +423,10 @@ export function compileScript(
|
||||||
if (!isCallOf(node, DEFINE_SLOTS)) {
|
if (!isCallOf(node, DEFINE_SLOTS)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (hasDefineSlotsCall) {
|
if (ctx.hasDefineSlotsCall) {
|
||||||
error(`duplicate ${DEFINE_SLOTS}() call`, node)
|
error(`duplicate ${DEFINE_SLOTS}() call`, node)
|
||||||
}
|
}
|
||||||
hasDefineSlotsCall = true
|
ctx.hasDefineSlotsCall = true
|
||||||
|
|
||||||
if (node.arguments.length > 0) {
|
if (node.arguments.length > 0) {
|
||||||
error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
|
error(`${DEFINE_SLOTS}() cannot accept arguments`, node)
|
||||||
|
@ -632,7 +447,7 @@ export function compileScript(
|
||||||
if (!enableDefineModel || !isCallOf(node, DEFINE_MODEL)) {
|
if (!enableDefineModel || !isCallOf(node, DEFINE_MODEL)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
hasDefineModelCall = true
|
ctx.hasDefineModelCall = true
|
||||||
|
|
||||||
const type =
|
const type =
|
||||||
(node.typeParameters && node.typeParameters.params[0]) || undefined
|
(node.typeParameters && node.typeParameters.params[0]) || undefined
|
||||||
|
@ -781,7 +596,7 @@ export function compileScript(
|
||||||
if (!isCallOf(node, DEFINE_OPTIONS)) {
|
if (!isCallOf(node, DEFINE_OPTIONS)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if (hasDefineOptionsCall) {
|
if (ctx.hasDefineOptionsCall) {
|
||||||
error(`duplicate ${DEFINE_OPTIONS}() call`, node)
|
error(`duplicate ${DEFINE_OPTIONS}() call`, node)
|
||||||
}
|
}
|
||||||
if (node.typeParameters) {
|
if (node.typeParameters) {
|
||||||
|
@ -789,7 +604,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
if (!node.arguments[0]) return true
|
if (!node.arguments[0]) return true
|
||||||
|
|
||||||
hasDefineOptionsCall = true
|
ctx.hasDefineOptionsCall = true
|
||||||
optionsRuntimeDecl = unwrapTSNode(node.arguments[0])
|
optionsRuntimeDecl = unwrapTSNode(node.arguments[0])
|
||||||
|
|
||||||
let propsOption = undefined
|
let propsOption = undefined
|
||||||
|
@ -875,10 +690,10 @@ export function compileScript(
|
||||||
|
|
||||||
function processDefineExpose(node: Node): boolean {
|
function processDefineExpose(node: Node): boolean {
|
||||||
if (isCallOf(node, DEFINE_EXPOSE)) {
|
if (isCallOf(node, DEFINE_EXPOSE)) {
|
||||||
if (hasDefineExposeCall) {
|
if (ctx.hasDefineExposeCall) {
|
||||||
error(`duplicate ${DEFINE_EXPOSE}() call`, node)
|
error(`duplicate ${DEFINE_EXPOSE}() call`, node)
|
||||||
}
|
}
|
||||||
hasDefineExposeCall = true
|
ctx.hasDefineExposeCall = true
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -958,9 +773,9 @@ export function compileScript(
|
||||||
*/
|
*/
|
||||||
function hasStaticWithDefaults() {
|
function hasStaticWithDefaults() {
|
||||||
return (
|
return (
|
||||||
propsRuntimeDefaults &&
|
ctx.propsRuntimeDefaults &&
|
||||||
propsRuntimeDefaults.type === 'ObjectExpression' &&
|
ctx.propsRuntimeDefaults.type === 'ObjectExpression' &&
|
||||||
propsRuntimeDefaults.properties.every(
|
ctx.propsRuntimeDefaults.properties.every(
|
||||||
node =>
|
node =>
|
||||||
node.type !== 'SpreadElement' &&
|
node.type !== 'SpreadElement' &&
|
||||||
(!node.computed || node.key.type.endsWith('Literal'))
|
(!node.computed || node.key.type.endsWith('Literal'))
|
||||||
|
@ -993,7 +808,7 @@ export function compileScript(
|
||||||
}`
|
}`
|
||||||
} else if (hasStaticDefaults) {
|
} else if (hasStaticDefaults) {
|
||||||
const prop = (
|
const prop = (
|
||||||
propsRuntimeDefaults as ObjectExpression
|
ctx.propsRuntimeDefaults as ObjectExpression
|
||||||
).properties.find(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
|
||||||
|
@ -1045,10 +860,10 @@ export function compileScript(
|
||||||
})
|
})
|
||||||
.join(',\n ')}\n }`
|
.join(',\n ')}\n }`
|
||||||
|
|
||||||
if (propsRuntimeDefaults && !hasStaticDefaults) {
|
if (ctx.propsRuntimeDefaults && !hasStaticDefaults) {
|
||||||
propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(
|
propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(
|
||||||
propsRuntimeDefaults.start! + startOffset,
|
ctx.propsRuntimeDefaults.start! + startOffset,
|
||||||
propsRuntimeDefaults.end! + startOffset
|
ctx.propsRuntimeDefaults.end! + startOffset
|
||||||
)})`
|
)})`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1056,7 +871,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
function genModels() {
|
function genModels() {
|
||||||
if (!hasDefineModelCall) return
|
if (!ctx.hasDefineModelCall) return
|
||||||
|
|
||||||
let modelPropsDecl = ''
|
let modelPropsDecl = ''
|
||||||
for (const [name, { type, options }] of Object.entries(modelDecls)) {
|
for (const [name, { type, options }] of Object.entries(modelDecls)) {
|
||||||
|
@ -1088,7 +903,7 @@ export function compileScript(
|
||||||
|
|
||||||
let decl: string
|
let decl: string
|
||||||
if (runtimeType && options) {
|
if (runtimeType && options) {
|
||||||
decl = isTS
|
decl = ctx.isTS
|
||||||
? `{ ${codegenOptions}, ...${options} }`
|
? `{ ${codegenOptions}, ...${options} }`
|
||||||
: `Object.assign({ ${codegenOptions} }, ${options})`
|
: `Object.assign({ ${codegenOptions} }, ${options})`
|
||||||
} else {
|
} else {
|
||||||
|
@ -1100,13 +915,13 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
let propsDecls: undefined | string
|
let propsDecls: undefined | string
|
||||||
if (propsRuntimeDecl) {
|
if (ctx.propsRuntimeDecl) {
|
||||||
propsDecls = scriptSetup!.content
|
propsDecls = scriptSetup!.content
|
||||||
.slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)
|
.slice(ctx.propsRuntimeDecl.start!, ctx.propsRuntimeDecl.end!)
|
||||||
.trim()
|
.trim()
|
||||||
if (propsDestructureDecl) {
|
if (ctx.propsDestructureDecl) {
|
||||||
const defaults: string[] = []
|
const defaults: string[] = []
|
||||||
for (const key in propsDestructuredBindings) {
|
for (const key in ctx.propsDestructuredBindings) {
|
||||||
const d = genDestructuredDefaultValue(key)
|
const d = genDestructuredDefaultValue(key)
|
||||||
if (d)
|
if (d)
|
||||||
defaults.push(
|
defaults.push(
|
||||||
|
@ -1121,7 +936,7 @@ export function compileScript(
|
||||||
)}(${propsDecls}, {\n ${defaults.join(',\n ')}\n})`
|
)}(${propsDecls}, {\n ${defaults.join(',\n ')}\n})`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (propsTypeDecl) {
|
} else if (ctx.propsTypeDecl) {
|
||||||
propsDecls = genPropsFromTS()
|
propsDecls = genPropsFromTS()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1143,7 +958,7 @@ export function compileScript(
|
||||||
needSkipFactory: boolean
|
needSkipFactory: boolean
|
||||||
}
|
}
|
||||||
| undefined {
|
| undefined {
|
||||||
const destructured = propsDestructuredBindings[key]
|
const destructured = ctx.propsDestructuredBindings[key]
|
||||||
const defaultVal = destructured && destructured.default
|
const defaultVal = destructured && destructured.default
|
||||||
if (defaultVal) {
|
if (defaultVal) {
|
||||||
const value = scriptSetup!.content.slice(
|
const value = scriptSetup!.content.slice(
|
||||||
|
@ -1204,13 +1019,15 @@ export function compileScript(
|
||||||
m.key.type === 'Identifier'
|
m.key.type === 'Identifier'
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
(propsRuntimeDefaults as ObjectExpression).properties.some(p => {
|
(ctx.propsRuntimeDefaults as ObjectExpression).properties.some(
|
||||||
|
p => {
|
||||||
if (p.type === 'SpreadElement') return false
|
if (p.type === 'SpreadElement') return false
|
||||||
return (
|
return (
|
||||||
resolveObjectKey(p.key, p.computed) ===
|
resolveObjectKey(p.key, p.computed) ===
|
||||||
(m.key as Identifier).name
|
(m.key as Identifier).name
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
res +=
|
res +=
|
||||||
m.key.name +
|
m.key.name +
|
||||||
|
@ -1248,7 +1065,7 @@ export function compileScript(
|
||||||
} else if (emitsTypeDecl) {
|
} else if (emitsTypeDecl) {
|
||||||
emitsDecl = genEmitsFromTS()
|
emitsDecl = genEmitsFromTS()
|
||||||
}
|
}
|
||||||
if (hasDefineModelCall) {
|
if (ctx.hasDefineModelCall) {
|
||||||
let modelEmitsDecl = `[${Object.keys(modelDecls)
|
let modelEmitsDecl = `[${Object.keys(modelDecls)
|
||||||
.map(n => JSON.stringify(`update:${n}`))
|
.map(n => JSON.stringify(`update:${n}`))
|
||||||
.join(', ')}]`
|
.join(', ')}]`
|
||||||
|
@ -1260,29 +1077,32 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0. parse both <script> and <script setup> blocks
|
// 0. parse both <script> and <script setup> blocks
|
||||||
const scriptAst =
|
// const scriptAst =
|
||||||
script &&
|
// script &&
|
||||||
parse(
|
// parse(
|
||||||
script.content,
|
// script.content,
|
||||||
{
|
// {
|
||||||
plugins,
|
// plugins,
|
||||||
sourceType: 'module'
|
// sourceType: 'module'
|
||||||
},
|
// },
|
||||||
scriptStartOffset!
|
// scriptStartOffset!
|
||||||
)
|
// )
|
||||||
|
|
||||||
const scriptSetupAst = parse(
|
// const scriptSetupAst = parse(
|
||||||
scriptSetup.content,
|
// scriptSetup.content,
|
||||||
{
|
// {
|
||||||
plugins: [
|
// plugins: [
|
||||||
...plugins,
|
// ...plugins,
|
||||||
// allow top level await but only inside <script setup>
|
// // allow top level await but only inside <script setup>
|
||||||
'topLevelAwait'
|
// 'topLevelAwait'
|
||||||
],
|
// ],
|
||||||
sourceType: 'module'
|
// sourceType: 'module'
|
||||||
},
|
// },
|
||||||
startOffset
|
// startOffset
|
||||||
)
|
// )
|
||||||
|
|
||||||
|
const scriptAst = ctx.scriptAst
|
||||||
|
const scriptSetupAst = ctx.scriptSetupAst!
|
||||||
|
|
||||||
// 1.1 walk import delcarations of <script>
|
// 1.1 walk import delcarations of <script>
|
||||||
if (scriptAst) {
|
if (scriptAst) {
|
||||||
|
@ -1407,7 +1227,7 @@ export function compileScript(
|
||||||
s.key.type === 'Identifier' &&
|
s.key.type === 'Identifier' &&
|
||||||
s.key.name === 'name'
|
s.key.name === 'name'
|
||||||
) {
|
) {
|
||||||
hasDefaultExportName = true
|
ctx.hasDefaultExportName = true
|
||||||
}
|
}
|
||||||
if (
|
if (
|
||||||
(s.type === 'ObjectMethod' || s.type === 'ObjectProperty') &&
|
(s.type === 'ObjectMethod' || s.type === 'ObjectProperty') &&
|
||||||
|
@ -1415,7 +1235,7 @@ export function compileScript(
|
||||||
s.key.name === 'render'
|
s.key.name === 'render'
|
||||||
) {
|
) {
|
||||||
// TODO warn when we provide a better way to do it?
|
// TODO warn when we provide a better way to do it?
|
||||||
hasDefaultExportRender = true
|
ctx.hasDefaultExportRender = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1531,10 +1351,9 @@ export function compileScript(
|
||||||
const expr = unwrapTSNode(node.expression)
|
const expr = unwrapTSNode(node.expression)
|
||||||
// process `defineProps` and `defineEmit(s)` calls
|
// process `defineProps` and `defineEmit(s)` calls
|
||||||
if (
|
if (
|
||||||
processDefineProps(expr) ||
|
processDefineProps(ctx, expr) ||
|
||||||
processDefineEmits(expr) ||
|
processDefineEmits(expr) ||
|
||||||
processDefineOptions(expr) ||
|
processDefineOptions(expr) ||
|
||||||
processWithDefaults(expr) ||
|
|
||||||
processDefineSlots(expr)
|
processDefineSlots(expr)
|
||||||
) {
|
) {
|
||||||
s.remove(node.start! + startOffset, node.end! + startOffset)
|
s.remove(node.start! + startOffset, node.end! + startOffset)
|
||||||
|
@ -1568,9 +1387,7 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
// defineProps / defineEmits
|
// defineProps / defineEmits
|
||||||
const isDefineProps =
|
const isDefineProps = processDefineProps(ctx, init, decl.id)
|
||||||
processDefineProps(init, decl.id) ||
|
|
||||||
processWithDefaults(init, decl.id)
|
|
||||||
const isDefineEmits =
|
const isDefineEmits =
|
||||||
!isDefineProps && processDefineEmits(init, decl.id)
|
!isDefineProps && processDefineEmits(init, decl.id)
|
||||||
!isDefineEmits &&
|
!isDefineEmits &&
|
||||||
|
@ -1697,12 +1514,12 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3.1 props destructure transform
|
// 3.1 props destructure transform
|
||||||
if (propsDestructureDecl) {
|
if (ctx.propsDestructureDecl) {
|
||||||
transformDestructuredProps(
|
transformDestructuredProps(
|
||||||
scriptSetupAst,
|
scriptSetupAst,
|
||||||
s,
|
s,
|
||||||
startOffset,
|
startOffset,
|
||||||
propsDestructuredBindings,
|
ctx.propsDestructuredBindings,
|
||||||
error,
|
error,
|
||||||
vueImportAliases
|
vueImportAliases
|
||||||
)
|
)
|
||||||
|
@ -1728,8 +1545,8 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. extract runtime props/emits code from setup context type
|
// 4. extract runtime props/emits code from setup context type
|
||||||
if (propsTypeDecl) {
|
if (ctx.propsTypeDecl) {
|
||||||
extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes)
|
extractRuntimeProps(ctx.propsTypeDecl, typeDeclaredProps, declaredTypes)
|
||||||
}
|
}
|
||||||
if (emitsTypeDecl) {
|
if (emitsTypeDecl) {
|
||||||
extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error)
|
extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits, error)
|
||||||
|
@ -1737,9 +1554,9 @@ export function compileScript(
|
||||||
|
|
||||||
// 5. check macro args to make sure it doesn't reference setup scope
|
// 5. check macro args to make sure it doesn't reference setup scope
|
||||||
// variables
|
// variables
|
||||||
checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
|
checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
|
||||||
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
|
checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
|
||||||
checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
|
checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
|
||||||
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
|
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
|
||||||
checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
|
checkInvalidScopeReference(optionsRuntimeDecl, DEFINE_OPTIONS)
|
||||||
|
|
||||||
|
@ -1766,8 +1583,8 @@ export function compileScript(
|
||||||
if (scriptAst) {
|
if (scriptAst) {
|
||||||
Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))
|
||||||
}
|
}
|
||||||
if (propsRuntimeDecl) {
|
if (ctx.propsRuntimeDecl) {
|
||||||
for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {
|
for (const key of getObjectOrArrayExpressionKeys(ctx.propsRuntimeDecl)) {
|
||||||
bindingMetadata[key] = BindingTypes.PROPS
|
bindingMetadata[key] = BindingTypes.PROPS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1778,13 +1595,13 @@ export function compileScript(
|
||||||
bindingMetadata[key] = BindingTypes.PROPS
|
bindingMetadata[key] = BindingTypes.PROPS
|
||||||
}
|
}
|
||||||
// props aliases
|
// props aliases
|
||||||
if (propsDestructureDecl) {
|
if (ctx.propsDestructureDecl) {
|
||||||
if (propsDestructureRestId) {
|
if (ctx.propsDestructureRestId) {
|
||||||
bindingMetadata[propsDestructureRestId] =
|
bindingMetadata[ctx.propsDestructureRestId] =
|
||||||
BindingTypes.SETUP_REACTIVE_CONST
|
BindingTypes.SETUP_REACTIVE_CONST
|
||||||
}
|
}
|
||||||
for (const key in propsDestructuredBindings) {
|
for (const key in ctx.propsDestructuredBindings) {
|
||||||
const { local } = propsDestructuredBindings[key]
|
const { local } = ctx.propsDestructuredBindings[key]
|
||||||
if (local !== key) {
|
if (local !== key) {
|
||||||
bindingMetadata[local] = BindingTypes.PROPS_ALIASED
|
bindingMetadata[local] = BindingTypes.PROPS_ALIASED
|
||||||
;(bindingMetadata.__propsAliases ||
|
;(bindingMetadata.__propsAliases ||
|
||||||
|
@ -1832,7 +1649,7 @@ export function compileScript(
|
||||||
|
|
||||||
// 9. finalize setup() argument signature
|
// 9. finalize setup() argument signature
|
||||||
let args = `__props`
|
let args = `__props`
|
||||||
if (propsTypeDecl) {
|
if (ctx.propsTypeDecl) {
|
||||||
// mark as any and only cast on assignment
|
// mark as any and only cast on assignment
|
||||||
// since the user defined complex types may be incompatible with the
|
// since the user defined complex types may be incompatible with the
|
||||||
// inferred type from generated runtime declarations
|
// inferred type from generated runtime declarations
|
||||||
|
@ -1841,20 +1658,22 @@ export function compileScript(
|
||||||
// inject user assignment of props
|
// inject user assignment of props
|
||||||
// we use a default __props so that template expressions referencing props
|
// we use a default __props so that template expressions referencing props
|
||||||
// can use it directly
|
// can use it directly
|
||||||
if (propsIdentifier) {
|
if (ctx.propsIdentifier) {
|
||||||
s.prependLeft(
|
s.prependLeft(
|
||||||
startOffset,
|
startOffset,
|
||||||
`\nconst ${propsIdentifier} = __props${
|
`\nconst ${ctx.propsIdentifier} = __props${
|
||||||
propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``
|
ctx.propsTypeDecl ? ` as ${genSetupPropsType(ctx.propsTypeDecl)}` : ``
|
||||||
};\n`
|
};\n`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (propsDestructureRestId) {
|
if (ctx.propsDestructureRestId) {
|
||||||
s.prependLeft(
|
s.prependLeft(
|
||||||
startOffset,
|
startOffset,
|
||||||
`\nconst ${propsDestructureRestId} = ${helper(
|
`\nconst ${ctx.propsDestructureRestId} = ${helper(
|
||||||
`createPropsRestProxy`
|
`createPropsRestProxy`
|
||||||
)}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))});\n`
|
)}(__props, ${JSON.stringify(
|
||||||
|
Object.keys(ctx.propsDestructuredBindings)
|
||||||
|
)});\n`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// inject temp variables for async context preservation
|
// inject temp variables for async context preservation
|
||||||
|
@ -1864,7 +1683,9 @@ export function compileScript(
|
||||||
}
|
}
|
||||||
|
|
||||||
const destructureElements =
|
const destructureElements =
|
||||||
hasDefineExposeCall || !options.inlineTemplate ? [`expose: __expose`] : []
|
ctx.hasDefineExposeCall || !options.inlineTemplate
|
||||||
|
? [`expose: __expose`]
|
||||||
|
: []
|
||||||
if (emitIdentifier) {
|
if (emitIdentifier) {
|
||||||
destructureElements.push(
|
destructureElements.push(
|
||||||
emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`
|
emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`
|
||||||
|
@ -1876,7 +1697,10 @@ export function compileScript(
|
||||||
|
|
||||||
// 10. generate return statement
|
// 10. generate return statement
|
||||||
let returned
|
let returned
|
||||||
if (!options.inlineTemplate || (!sfc.template && hasDefaultExportRender)) {
|
if (
|
||||||
|
!options.inlineTemplate ||
|
||||||
|
(!sfc.template && ctx.hasDefaultExportRender)
|
||||||
|
) {
|
||||||
// non-inline mode, or has manual render in normal <script>
|
// non-inline mode, or has manual render in normal <script>
|
||||||
// return bindings from script and script setup
|
// return bindings from script and script setup
|
||||||
const allBindings: Record<string, any> = {
|
const allBindings: Record<string, any> = {
|
||||||
|
@ -1986,7 +1810,7 @@ export function compileScript(
|
||||||
|
|
||||||
// 11. finalize default export
|
// 11. finalize default export
|
||||||
let runtimeOptions = ``
|
let runtimeOptions = ``
|
||||||
if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
|
if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
|
||||||
const match = filename.match(/([^/\\]+)\.\w+$/)
|
const match = filename.match(/([^/\\]+)\.\w+$/)
|
||||||
if (match) {
|
if (match) {
|
||||||
runtimeOptions += `\n __name: '${match[1]}',`
|
runtimeOptions += `\n __name: '${match[1]}',`
|
||||||
|
@ -2012,7 +1836,7 @@ export function compileScript(
|
||||||
// <script setup> components are closed by default. If the user did not
|
// <script setup> components are closed by default. If the user did not
|
||||||
// explicitly call `defineExpose`, call expose() with no args.
|
// explicitly call `defineExpose`, call expose() with no args.
|
||||||
const exposeCall =
|
const exposeCall =
|
||||||
hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
|
ctx.hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
|
||||||
// wrap setup code with function.
|
// wrap setup code with function.
|
||||||
if (isTS) {
|
if (isTS) {
|
||||||
// for TS, make sure the exported type is still valid type with
|
// for TS, make sure the exported type is still valid type with
|
||||||
|
@ -2893,14 +2717,3 @@ export function hmrShouldReload(
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveObjectKey(node: Node, computed: boolean) {
|
|
||||||
switch (node.type) {
|
|
||||||
case 'StringLiteral':
|
|
||||||
case 'NumericLiteral':
|
|
||||||
return node.value
|
|
||||||
case 'Identifier':
|
|
||||||
if (!computed) return node.name
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,153 @@
|
||||||
|
import { Expression, Node, ObjectPattern, Program } from '@babel/types'
|
||||||
|
import { SFCDescriptor } from '../parse'
|
||||||
|
import { generateCodeFrame } from '@vue/shared'
|
||||||
|
import { PropsDeclType } from './defineProps'
|
||||||
|
import { parse as babelParse, ParserOptions, ParserPlugin } from '@babel/parser'
|
||||||
|
import { SFCScriptCompileOptions } from '../compileScript'
|
||||||
|
import MagicString from 'magic-string'
|
||||||
|
|
||||||
|
export type PropsDestructureBindings = Record<
|
||||||
|
string, // public prop key
|
||||||
|
{
|
||||||
|
local: string // local identifier, may be different
|
||||||
|
default?: Expression
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
export class ScriptCompileContext {
|
||||||
|
isJS: boolean
|
||||||
|
isTS: boolean
|
||||||
|
|
||||||
|
scriptAst: Program | null
|
||||||
|
scriptSetupAst: Program | null
|
||||||
|
|
||||||
|
s = new MagicString(this.descriptor.source)
|
||||||
|
|
||||||
|
startOffset = this.descriptor.scriptSetup?.loc.start.offset
|
||||||
|
endOffset = this.descriptor.scriptSetup?.loc.end.offset
|
||||||
|
|
||||||
|
scriptStartOffset = this.descriptor.script?.loc.start.offset
|
||||||
|
scriptEndOffset = this.descriptor.script?.loc.end.offset
|
||||||
|
|
||||||
|
// macros presence check
|
||||||
|
hasDefinePropsCall = false
|
||||||
|
hasDefineEmitCall = false
|
||||||
|
hasDefineExposeCall = false
|
||||||
|
hasDefaultExportName = false
|
||||||
|
hasDefaultExportRender = false
|
||||||
|
hasDefineOptionsCall = false
|
||||||
|
hasDefineSlotsCall = false
|
||||||
|
hasDefineModelCall = false
|
||||||
|
|
||||||
|
// defineProps
|
||||||
|
propsIdentifier: string | undefined
|
||||||
|
propsRuntimeDecl: Node | undefined
|
||||||
|
propsTypeDecl: PropsDeclType | undefined
|
||||||
|
propsDestructureDecl: ObjectPattern | undefined
|
||||||
|
propsDestructuredBindings: PropsDestructureBindings = Object.create(null)
|
||||||
|
propsDestructureRestId: string | undefined
|
||||||
|
propsRuntimeDefaults: Node | undefined
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public descriptor: SFCDescriptor,
|
||||||
|
public options: SFCScriptCompileOptions
|
||||||
|
) {
|
||||||
|
const { script, scriptSetup } = descriptor
|
||||||
|
const scriptLang = script && script.lang
|
||||||
|
const scriptSetupLang = scriptSetup && scriptSetup.lang
|
||||||
|
|
||||||
|
this.isJS =
|
||||||
|
scriptLang === 'js' ||
|
||||||
|
scriptLang === 'jsx' ||
|
||||||
|
scriptSetupLang === 'js' ||
|
||||||
|
scriptSetupLang === 'jsx'
|
||||||
|
this.isTS =
|
||||||
|
scriptLang === 'ts' ||
|
||||||
|
scriptLang === 'tsx' ||
|
||||||
|
scriptSetupLang === 'ts' ||
|
||||||
|
scriptSetupLang === 'tsx'
|
||||||
|
|
||||||
|
// resolve parser plugins
|
||||||
|
const plugins: ParserPlugin[] = []
|
||||||
|
if (!this.isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {
|
||||||
|
plugins.push('jsx')
|
||||||
|
} else {
|
||||||
|
// If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins
|
||||||
|
if (options.babelParserPlugins)
|
||||||
|
options.babelParserPlugins = options.babelParserPlugins.filter(
|
||||||
|
n => n !== 'jsx'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)
|
||||||
|
if (this.isTS) {
|
||||||
|
plugins.push('typescript')
|
||||||
|
if (!plugins.includes('decorators')) {
|
||||||
|
plugins.push('decorators-legacy')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parse(
|
||||||
|
input: string,
|
||||||
|
options: ParserOptions,
|
||||||
|
offset: number
|
||||||
|
): Program {
|
||||||
|
try {
|
||||||
|
return babelParse(input, options).program
|
||||||
|
} catch (e: any) {
|
||||||
|
e.message = `[@vue/compiler-sfc] ${e.message}\n\n${
|
||||||
|
descriptor.filename
|
||||||
|
}\n${generateCodeFrame(
|
||||||
|
descriptor.source,
|
||||||
|
e.pos + offset,
|
||||||
|
e.pos + offset + 1
|
||||||
|
)}`
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.scriptAst =
|
||||||
|
this.descriptor.script &&
|
||||||
|
parse(
|
||||||
|
this.descriptor.script.content,
|
||||||
|
{
|
||||||
|
plugins,
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
this.scriptStartOffset!
|
||||||
|
)
|
||||||
|
|
||||||
|
this.scriptSetupAst =
|
||||||
|
this.descriptor.scriptSetup &&
|
||||||
|
parse(
|
||||||
|
this.descriptor.scriptSetup!.content,
|
||||||
|
{
|
||||||
|
plugins: [...plugins, 'topLevelAwait'],
|
||||||
|
sourceType: 'module'
|
||||||
|
},
|
||||||
|
this.startOffset!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
getString(node: Node, scriptSetup = true): string {
|
||||||
|
const block = scriptSetup
|
||||||
|
? this.descriptor.scriptSetup!
|
||||||
|
: this.descriptor.script!
|
||||||
|
return block.content.slice(node.start!, node.end!)
|
||||||
|
}
|
||||||
|
|
||||||
|
error(
|
||||||
|
msg: string,
|
||||||
|
node: Node,
|
||||||
|
end: number = node.end! + this.startOffset!
|
||||||
|
): never {
|
||||||
|
throw new Error(
|
||||||
|
`[@vue/compiler-sfc] ${msg}\n\n${
|
||||||
|
this.descriptor.filename
|
||||||
|
}\n${generateCodeFrame(
|
||||||
|
this.descriptor.source,
|
||||||
|
node.start! + this.startOffset!,
|
||||||
|
end
|
||||||
|
)}`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,148 @@
|
||||||
|
import {
|
||||||
|
Node,
|
||||||
|
LVal,
|
||||||
|
Identifier,
|
||||||
|
TSTypeLiteral,
|
||||||
|
TSInterfaceBody
|
||||||
|
} from '@babel/types'
|
||||||
|
import { isCallOf } from '@vue/compiler-dom'
|
||||||
|
import { ScriptCompileContext } from './context'
|
||||||
|
import { resolveObjectKey } from './utils'
|
||||||
|
import { resolveQualifiedType } from './resolveType'
|
||||||
|
|
||||||
|
export const DEFINE_PROPS = 'defineProps'
|
||||||
|
export const WITH_DEFAULTS = 'withDefaults'
|
||||||
|
|
||||||
|
export type PropsDeclType = (TSTypeLiteral | TSInterfaceBody) & {
|
||||||
|
__fromNormalScript?: boolean | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export function processDefineProps(
|
||||||
|
ctx: ScriptCompileContext,
|
||||||
|
node: Node,
|
||||||
|
declId?: LVal
|
||||||
|
) {
|
||||||
|
if (!isCallOf(node, DEFINE_PROPS)) {
|
||||||
|
return processWithDefaults(ctx, node, declId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.hasDefinePropsCall) {
|
||||||
|
ctx.error(`duplicate ${DEFINE_PROPS}() call`, node)
|
||||||
|
}
|
||||||
|
ctx.hasDefinePropsCall = true
|
||||||
|
|
||||||
|
ctx.propsRuntimeDecl = node.arguments[0]
|
||||||
|
|
||||||
|
// call has type parameters - infer runtime types from it
|
||||||
|
if (node.typeParameters) {
|
||||||
|
if (ctx.propsRuntimeDecl) {
|
||||||
|
ctx.error(
|
||||||
|
`${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +
|
||||||
|
`at the same time. Use one or the other.`,
|
||||||
|
node
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const rawDecl = node.typeParameters.params[0]
|
||||||
|
ctx.propsTypeDecl = resolveQualifiedType(
|
||||||
|
ctx,
|
||||||
|
rawDecl,
|
||||||
|
node => node.type === 'TSTypeLiteral'
|
||||||
|
) as PropsDeclType | undefined
|
||||||
|
if (!ctx.propsTypeDecl) {
|
||||||
|
ctx.error(
|
||||||
|
`type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +
|
||||||
|
`or a reference to an interface or literal type.`,
|
||||||
|
rawDecl
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (declId) {
|
||||||
|
// handle props destructure
|
||||||
|
if (declId.type === 'ObjectPattern') {
|
||||||
|
ctx.propsDestructureDecl = declId
|
||||||
|
for (const prop of declId.properties) {
|
||||||
|
if (prop.type === 'ObjectProperty') {
|
||||||
|
const propKey = resolveObjectKey(prop.key, prop.computed)
|
||||||
|
|
||||||
|
if (!propKey) {
|
||||||
|
ctx.error(
|
||||||
|
`${DEFINE_PROPS}() destructure cannot use computed key.`,
|
||||||
|
prop.key
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prop.value.type === 'AssignmentPattern') {
|
||||||
|
// default value { foo = 123 }
|
||||||
|
const { left, right } = prop.value
|
||||||
|
if (left.type !== 'Identifier') {
|
||||||
|
ctx.error(
|
||||||
|
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
||||||
|
left
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// store default value
|
||||||
|
ctx.propsDestructuredBindings[propKey] = {
|
||||||
|
local: left.name,
|
||||||
|
default: right
|
||||||
|
}
|
||||||
|
} else if (prop.value.type === 'Identifier') {
|
||||||
|
// simple destructure
|
||||||
|
ctx.propsDestructuredBindings[propKey] = {
|
||||||
|
local: prop.value.name
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.error(
|
||||||
|
`${DEFINE_PROPS}() destructure does not support nested patterns.`,
|
||||||
|
prop.value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// rest spread
|
||||||
|
ctx.propsDestructureRestId = (prop.argument as Identifier).name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ctx.propsIdentifier = ctx.getString(declId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
function processWithDefaults(
|
||||||
|
ctx: ScriptCompileContext,
|
||||||
|
node: Node,
|
||||||
|
declId?: LVal
|
||||||
|
): boolean {
|
||||||
|
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 {
|
||||||
|
ctx.error(
|
||||||
|
`${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,
|
||||||
|
node.arguments[0] || node
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
|
@ -0,0 +1,114 @@
|
||||||
|
import { Node, Statement, TSInterfaceBody, TSTypeElement } from '@babel/types'
|
||||||
|
import { FromNormalScript } from './utils'
|
||||||
|
import { ScriptCompileContext } from './context'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a type Node into
|
||||||
|
*/
|
||||||
|
export function resolveType() {}
|
||||||
|
|
||||||
|
export function resolveQualifiedType(
|
||||||
|
ctx: ScriptCompileContext,
|
||||||
|
node: Node,
|
||||||
|
qualifier: (node: Node) => boolean
|
||||||
|
): Node | undefined {
|
||||||
|
if (qualifier(node)) {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
if (node.type === 'TSTypeReference' && node.typeName.type === 'Identifier') {
|
||||||
|
const refName = node.typeName.name
|
||||||
|
const { scriptAst, scriptSetupAst } = ctx
|
||||||
|
const body = scriptAst
|
||||||
|
? [...scriptSetupAst!.body, ...scriptAst.body]
|
||||||
|
: scriptSetupAst!.body
|
||||||
|
for (let i = 0; i < body.length; i++) {
|
||||||
|
const node = body[i]
|
||||||
|
let qualified = isQualifiedType(
|
||||||
|
node,
|
||||||
|
qualifier,
|
||||||
|
refName
|
||||||
|
) as TSInterfaceBody
|
||||||
|
if (qualified) {
|
||||||
|
const extendsTypes = resolveExtendsType(body, node, qualifier)
|
||||||
|
if (extendsTypes.length) {
|
||||||
|
const bodies: TSTypeElement[] = [...qualified.body]
|
||||||
|
filterExtendsType(extendsTypes, bodies)
|
||||||
|
qualified.body = bodies
|
||||||
|
}
|
||||||
|
;(qualified as FromNormalScript<Node>).__fromNormalScript =
|
||||||
|
scriptAst && i >= scriptSetupAst!.body.length
|
||||||
|
return qualified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isQualifiedType(
|
||||||
|
node: Node,
|
||||||
|
qualifier: (node: Node) => boolean,
|
||||||
|
refName: String
|
||||||
|
): Node | undefined {
|
||||||
|
if (node.type === 'TSInterfaceDeclaration' && node.id.name === refName) {
|
||||||
|
return node.body
|
||||||
|
} else if (
|
||||||
|
node.type === 'TSTypeAliasDeclaration' &&
|
||||||
|
node.id.name === refName &&
|
||||||
|
qualifier(node.typeAnnotation)
|
||||||
|
) {
|
||||||
|
return node.typeAnnotation
|
||||||
|
} else if (node.type === 'ExportNamedDeclaration' && node.declaration) {
|
||||||
|
return isQualifiedType(node.declaration, qualifier, refName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveExtendsType(
|
||||||
|
body: Statement[],
|
||||||
|
node: Node,
|
||||||
|
qualifier: (node: Node) => boolean,
|
||||||
|
cache: Array<Node> = []
|
||||||
|
): Array<Node> {
|
||||||
|
if (node.type === 'TSInterfaceDeclaration' && node.extends) {
|
||||||
|
node.extends.forEach(extend => {
|
||||||
|
if (
|
||||||
|
extend.type === 'TSExpressionWithTypeArguments' &&
|
||||||
|
extend.expression.type === 'Identifier'
|
||||||
|
) {
|
||||||
|
for (const node of body) {
|
||||||
|
const qualified = isQualifiedType(
|
||||||
|
node,
|
||||||
|
qualifier,
|
||||||
|
extend.expression.name
|
||||||
|
)
|
||||||
|
if (qualified) {
|
||||||
|
cache.push(qualified)
|
||||||
|
resolveExtendsType(body, node, qualifier, cache)
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return cache
|
||||||
|
}
|
||||||
|
|
||||||
|
// filter all extends types to keep the override declaration
|
||||||
|
function filterExtendsType(extendsTypes: Node[], bodies: TSTypeElement[]) {
|
||||||
|
extendsTypes.forEach(extend => {
|
||||||
|
const body = (extend as TSInterfaceBody).body
|
||||||
|
body.forEach(newBody => {
|
||||||
|
if (
|
||||||
|
newBody.type === 'TSPropertySignature' &&
|
||||||
|
newBody.key.type === 'Identifier'
|
||||||
|
) {
|
||||||
|
const name = newBody.key.name
|
||||||
|
const hasOverride = bodies.some(
|
||||||
|
seenBody =>
|
||||||
|
seenBody.type === 'TSPropertySignature' &&
|
||||||
|
seenBody.key.type === 'Identifier' &&
|
||||||
|
seenBody.key.name === name
|
||||||
|
)
|
||||||
|
if (!hasOverride) bodies.push(newBody)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { Node } from '@babel/types'
|
||||||
|
|
||||||
|
export type FromNormalScript<T> = T & { __fromNormalScript?: boolean | null }
|
||||||
|
|
||||||
|
export function resolveObjectKey(node: Node, computed: boolean) {
|
||||||
|
switch (node.type) {
|
||||||
|
case 'StringLiteral':
|
||||||
|
case 'NumericLiteral':
|
||||||
|
return node.value
|
||||||
|
case 'Identifier':
|
||||||
|
if (!computed) return node.name
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
Loading…
Reference in New Issue