compileScript() — vue Function Reference
Architecture documentation for the compileScript() function in compileScript.ts from the vue codebase.
Entity Profile
Dependency Diagram
graph TD 16b5f9ed_1e09_eff8_5797_7cb90fc86d37["compileScript()"] aeb67520_c222_7e8e_48f7_9535500e1530["analyzeScriptBindings()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| aeb67520_c222_7e8e_48f7_9535500e1530 81fc81a6_5762_0cf2_fa0f_6c5be69a5d84["rewriteDefault()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| 81fc81a6_5762_0cf2_fa0f_6c5be69a5d84 cea5366c_e3c9_28ae_a8fc_2d496c97c902["genNormalScriptCssVarsCode()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| cea5366c_e3c9_28ae_a8fc_2d496c97c902 bbf88914_62b5_839d_3cfb_85a00fd1d67b["parse()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| bbf88914_62b5_839d_3cfb_85a00fd1d67b eb367a79_758d_d887_b761_550177f27249["generateCodeFrame()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| eb367a79_758d_d887_b761_550177f27249 ed5fe71b_2eb2_ab85_8bbb_eb87a7fa5e82["isImportUsed()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| ed5fe71b_2eb2_ab85_8bbb_eb87a7fa5e82 c9c376a2_8768_e10f_0236_eb868edd143f["isCallOf()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| c9c376a2_8768_e10f_0236_eb868edd143f eb301be5_f530_960e_129f_798877ea7f83["walkIdentifiers()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| eb301be5_f530_960e_129f_798877ea7f83 85a1f3da_8d0e_641f_3117_26167fbacbbf["toRuntimeTypeString()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| 85a1f3da_8d0e_641f_3117_26167fbacbbf 47c14f28_c1ca_52d4_cc59_404156ff9d1e["remove()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| 47c14f28_c1ca_52d4_cc59_404156ff9d1e 4ae6f599_c6d3_5f0e_3efb_3633ef6e68c0["walkDeclaration()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| 4ae6f599_c6d3_5f0e_3efb_3633ef6e68c0 ff0dbb26_906d_c579_81e5_ceadaf4a13fe["warnOnce()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| ff0dbb26_906d_c579_81e5_ceadaf4a13fe 5fee095c_1592_ad66_e831_5c09154637d6["isFunctionType()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| 5fee095c_1592_ad66_e831_5c09154637d6 64256cf8_3f72_4dd0_8aab_f5daed1e921e["registerBinding()"] 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 -->|calls| 64256cf8_3f72_4dd0_8aab_f5daed1e921e style 16b5f9ed_1e09_eff8_5797_7cb90fc86d37 fill:#6366f1,stroke:#818cf8,color:#fff
Relationship Graph
Source Code
packages/compiler-sfc/src/compileScript.ts lines 98–1273
export function compileScript(
sfc: SFCDescriptor,
options: SFCScriptCompileOptions = { id: '' }
): SFCScriptBlock {
let { filename, script, scriptSetup, source } = sfc
const isProd = !!options.isProd
const genSourceMap = options.sourceMap !== false
let refBindings: string[] | undefined
const cssVars = sfc.cssVars
const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
const scriptLang = script && script.lang
const scriptSetupLang = scriptSetup && scriptSetup.lang
const isTS =
scriptLang === 'ts' ||
scriptLang === 'tsx' ||
scriptSetupLang === 'ts' ||
scriptSetupLang === 'tsx'
// resolve parser plugins
const plugins: ParserPlugin[] = []
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 (!script) {
throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
}
if (scriptLang && !isTS && scriptLang !== 'jsx') {
// do not process non js/ts script blocks
return script
}
try {
let content = script.content
let map = script.map
const scriptAst = _parse(content, {
plugins,
sourceType: 'module'
}).program
const bindings = analyzeScriptBindings(scriptAst.body)
if (cssVars.length) {
content = rewriteDefault(content, DEFAULT_VAR, plugins)
content += genNormalScriptCssVarsCode(
cssVars,
bindings,
scopeId,
isProd
)
content += `\nexport default ${DEFAULT_VAR}`
}
return {
...script,
content,
map,
bindings,
scriptAst: scriptAst.body
}
} catch (e: any) {
// silently fallback if parse fails since user may be using custom
// babel syntax
return script
}
}
if (script && scriptLang !== scriptSetupLang) {
throw new Error(
`[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
`language type.`
)
}
if (scriptSetupLang && !isTS && scriptSetupLang !== 'jsx') {
// do not process non js/ts script blocks
return scriptSetup
}
// metadata that needs to be returned
const bindingMetadata: BindingMetadata = {}
const helperImports: Set<string> = new Set()
const userImports: Record<string, ImportBinding> = Object.create(null)
const userImportAlias: Record<string, string> = Object.create(null)
const scriptBindings: Record<string, BindingTypes> = Object.create(null)
const setupBindings: Record<string, BindingTypes> = Object.create(null)
let defaultExport: Node | undefined
let hasDefinePropsCall = false
let hasDefineEmitCall = false
let hasDefineExposeCall = false
let hasDefaultExportName = false
let propsRuntimeDecl: Node | undefined
let propsRuntimeDefaults: ObjectExpression | undefined
let propsDestructureDecl: Node | undefined
let propsDestructureRestId: string | undefined
let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined
let propsTypeDeclRaw: Node | undefined
let propsIdentifier: string | undefined
let emitsRuntimeDecl: Node | undefined
let emitsTypeDecl:
| TSFunctionType
| TSTypeLiteral
| TSInterfaceBody
| undefined
let emitsTypeDeclRaw: Node | undefined
let emitIdentifier: string | undefined
let hasInlinedSsrRenderFn = false
// props/emits declared via types
const typeDeclaredProps: Record<string, PropTypeData> = {}
const typeDeclaredEmits: Set<string> = new Set()
// record declared types for runtime props type generation
const declaredTypes: Record<string, string[]> = {}
// props destructure data
const propsDestructuredBindings: Record<
string, // public prop key
{
local: string // local identifier, may be different
default?: Expression
}
> = Object.create(null)
// magic-string state
const s = new MagicString(source)
const startOffset = scriptSetup.start
const endOffset = scriptSetup.end
const scriptStartOffset = script && script.start
const scriptEndOffset = script && script.end
function helper(key: string): string {
helperImports.add(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${filename}\n${generateCodeFrame(
source,
e.pos + offset,
e.pos + offset + 1
)}`
throw e
}
}
function error(
msg: string,
node: Node,
end: number = node.end! + startOffset
): never {
throw new Error(
`[@vue/compiler-sfc] ${msg}\n\n${filename}\n${generateCodeFrame(
source,
node.start! + startOffset,
end
)}`
)
}
function registerUserImport(
source: string,
local: string,
imported: string | false,
isType: boolean,
isFromSetup: boolean
) {
if (source === 'vue' && imported) {
userImportAlias[imported] = local
}
let isUsedInTemplate = true
if (sfc.template && !sfc.template.src && !sfc.template.lang) {
isUsedInTemplate = isImportUsed(local, sfc, isTS)
}
userImports[local] = {
isType,
imported: imported || 'default',
source,
isFromSetup,
isUsedInTemplate
}
}
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 TSTypeLiteral | TSInterfaceBody | 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) {
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] as ObjectExpression
if (
!propsRuntimeDefaults ||
propsRuntimeDefaults.type !== 'ObjectExpression'
) {
error(
`The 2nd argument of ${WITH_DEFAULTS} must be an object literal.`,
propsRuntimeDefaults || 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 {
if (!isCallOf(node, DEFINE_EMITS)) {
return false
}
if (hasDefineEmitCall) {
error(`duplicate ${DEFINE_EMITS}() call`, node)
}
hasDefineEmitCall = true
emitsRuntimeDecl = node.arguments[0]
if (node.typeParameters) {
if (emitsRuntimeDecl) {
error(
`${DEFINE_EMITS}() cannot accept both type and non-type arguments ` +
`at the same time. Use one or the other.`,
node
)
}
emitsTypeDeclRaw = node.typeParameters.params[0]
emitsTypeDecl = resolveQualifiedType(
emitsTypeDeclRaw,
node => node.type === 'TSFunctionType' || node.type === 'TSTypeLiteral'
) as TSFunctionType | TSTypeLiteral | TSInterfaceBody | undefined
if (!emitsTypeDecl) {
error(
`type argument passed to ${DEFINE_EMITS}() must be a function type, ` +
`a literal type with call signatures, or a reference to the above types.`,
emitsTypeDeclRaw
)
}
}
if (declId) {
emitIdentifier =
declId.type === 'Identifier'
? declId.name
: scriptSetup!.content.slice(declId.start!, declId.end!)
}
return true
}
function resolveQualifiedType(
node: Node,
qualifier: (node: Node) => boolean
) {
if (qualifier(node)) {
return node
}
if (
node.type === 'TSTypeReference' &&
node.typeName.type === 'Identifier'
) {
const refName = node.typeName.name
const isQualifiedType = (node: Node): 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)
}
}
const body = scriptAst
? [...scriptSetupAst.body, ...scriptAst.body]
: scriptSetupAst.body
for (const node of body) {
const qualified = isQualifiedType(node)
if (qualified) {
return qualified
}
}
}
}
function processDefineExpose(node: Node): boolean {
if (isCallOf(node, DEFINE_EXPOSE)) {
if (hasDefineExposeCall) {
error(`duplicate ${DEFINE_EXPOSE}() call`, node)
}
hasDefineExposeCall = true
return true
}
return false
}
function checkInvalidScopeReference(node: Node | undefined, method: string) {
if (!node) return
walkIdentifiers(node, id => {
if (setupBindings[id.name]) {
error(
`\`${method}()\` in <script setup> cannot reference locally ` +
`declared variables because it will be hoisted outside of the ` +
`setup() function. If your component options require initialization ` +
`in the module scope, use a separate normal <script> to export ` +
`the options instead.`,
id
)
}
})
}
/**
* 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() {
return (
propsRuntimeDefaults &&
propsRuntimeDefaults.type === 'ObjectExpression' &&
propsRuntimeDefaults.properties.every(
node =>
(node.type === 'ObjectProperty' && !node.computed) ||
node.type === 'ObjectMethod'
)
)
}
function genRuntimeProps(props: Record<string, PropTypeData>) {
const keys = Object.keys(props)
if (!keys.length) {
return ``
}
const hasStaticDefaults = hasStaticWithDefaults()
const scriptSetupSource = scriptSetup!.content
let propsDecls = `{
${keys
.map(key => {
let defaultString: string | undefined
const destructured = genDestructuredDefaultValue(key)
if (destructured) {
defaultString = `default: ${destructured}`
} else if (hasStaticDefaults) {
const prop = propsRuntimeDefaults!.properties.find(
(node: any) => node.key.name === key
) as ObjectProperty | ObjectMethod
if (prop) {
if (prop.type === 'ObjectProperty') {
// prop has corresponding static default value
defaultString = `default: ${scriptSetupSource.slice(
prop.value.start!,
prop.value.end!
)}`
} else {
defaultString = `default() ${scriptSetupSource.slice(
prop.body.start!,
prop.body.end!
)}`
}
}
}
const { type, required } = props[key]
if (!isProd) {
return `${key}: { type: ${toRuntimeTypeString(
type
)}, required: ${required}${
defaultString ? `, ${defaultString}` : ``
} }`
} else if (
type.some(
el => el === 'Boolean' || (defaultString && el === 'Function')
)
) {
// #4783 production: if boolean or defaultString and function exists, should keep the type.
return `${key}: { type: ${toRuntimeTypeString(type)}${
defaultString ? `, ${defaultString}` : ``
} }`
} else {
// production: checks are useless
return `${key}: ${defaultString ? `{ ${defaultString} }` : 'null'}`
}
})
.join(',\n ')}\n }`
if (propsRuntimeDefaults && !hasStaticDefaults) {
propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(
propsRuntimeDefaults.start! + startOffset,
propsRuntimeDefaults.end! + startOffset
)})`
}
return `\n props: ${propsDecls},`
}
function genDestructuredDefaultValue(key: string): string | undefined {
const destructured = propsDestructuredBindings[key]
if (destructured && destructured.default) {
const value = scriptSetup!.content.slice(
destructured.default.start!,
destructured.default.end!
)
const isLiteral = destructured.default.type.endsWith('Literal')
return isLiteral ? value : `() => (${value})`
}
}
function genSetupPropsType(node: TSTypeLiteral | TSInterfaceBody) {
const scriptSetupSource = scriptSetup!.content
if (hasStaticWithDefaults()) {
// if withDefaults() is used, we need to remove the optional flags
// on props that have default values
let res = `{ `
const members = node.type === 'TSTypeLiteral' ? node.members : node.body
for (const m of members) {
if (
(m.type === 'TSPropertySignature' ||
m.type === 'TSMethodSignature') &&
m.typeAnnotation &&
m.key.type === 'Identifier'
) {
if (
propsRuntimeDefaults!.properties.some(
(p: any) => p.key.name === (m.key as Identifier).name
)
) {
res +=
m.key.name +
(m.type === 'TSMethodSignature' ? '()' : '') +
scriptSetupSource.slice(
m.typeAnnotation.start!,
m.typeAnnotation.end!
) +
', '
} else {
res +=
scriptSetupSource.slice(m.start!, m.typeAnnotation.end!) + `, `
}
}
}
return (res.length ? res.slice(0, -2) : res) + ` }`
} else {
return scriptSetupSource.slice(node.start!, node.end!)
}
}
// 1. process normal <script> first if it exists
let scriptAst: Program | undefined
if (script) {
scriptAst = parse(
script.content,
{
plugins,
sourceType: 'module'
},
scriptStartOffset!
)
for (const node of scriptAst.body) {
if (node.type === 'ImportDeclaration') {
// record imports for dedupe
for (const specifier of node.specifiers) {
const imported =
specifier.type === 'ImportSpecifier' &&
specifier.imported.type === 'Identifier' &&
specifier.imported.name
registerUserImport(
node.source.value,
specifier.local.name,
imported,
node.importKind === 'type' ||
(specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'),
false
)
}
} else if (node.type === 'ExportDefaultDeclaration') {
// export default
defaultExport = node
// check if user has manually specified `name` or 'render` option in
// export default
// if has name, skip name inference
// if has render and no template, generate return object instead of
// empty render function (#4980)
let optionProperties
if (defaultExport.declaration.type === 'ObjectExpression') {
optionProperties = defaultExport.declaration.properties
} else if (
defaultExport.declaration.type === 'CallExpression' &&
defaultExport.declaration.arguments[0].type === 'ObjectExpression'
) {
optionProperties = defaultExport.declaration.arguments[0].properties
}
if (optionProperties) {
for (const s of optionProperties) {
if (
s.type === 'ObjectProperty' &&
s.key.type === 'Identifier' &&
s.key.name === 'name'
) {
hasDefaultExportName = true
}
}
}
// export default { ... } --> const __default__ = { ... }
const start = node.start! + scriptStartOffset!
const end = node.declaration.start! + scriptStartOffset!
s.overwrite(start, end, `const ${DEFAULT_VAR} = `)
} else if (node.type === 'ExportNamedDeclaration') {
const defaultSpecifier = node.specifiers.find(
s => s.exported.type === 'Identifier' && s.exported.name === 'default'
) as ExportSpecifier
if (defaultSpecifier) {
defaultExport = node
// 1. remove specifier
if (node.specifiers.length > 1) {
s.remove(
defaultSpecifier.start! + scriptStartOffset!,
defaultSpecifier.end! + scriptStartOffset!
)
} else {
s.remove(
node.start! + scriptStartOffset!,
node.end! + scriptStartOffset!
)
}
if (node.source) {
// export { x as default } from './x'
// rewrite to `import { x as __default__ } from './x'` and
// add to top
s.prepend(
`import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\n`
)
} else {
// export { x as default }
// rewrite to `const __default__ = x` and move to end
s.appendLeft(
scriptEndOffset!,
`\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`
)
}
}
if (node.declaration) {
walkDeclaration(node.declaration, scriptBindings, userImportAlias)
}
} else if (
(node.type === 'VariableDeclaration' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration' ||
node.type === 'TSEnumDeclaration') &&
!node.declare
) {
walkDeclaration(node, scriptBindings, userImportAlias)
}
}
// apply reactivity transform
// if (enableReactivityTransform && shouldTransform(script.content)) {
// const { rootRefs, importedHelpers } = transformAST(
// scriptAst,
// s,
// scriptStartOffset!
// )
// refBindings = rootRefs
// for (const h of importedHelpers) {
// helperImports.add(h)
// }
// }
// <script> after <script setup>
// we need to move the block up so that `const __default__` is
// declared before being used in the actual component definition
if (scriptStartOffset! > startOffset) {
// if content doesn't end with newline, add one
if (!/\n$/.test(script.content.trim())) {
s.appendLeft(scriptEndOffset!, `\n`)
}
s.move(scriptStartOffset!, scriptEndOffset!, 0)
}
}
// 2. parse <script setup> and walk over top level statements
const scriptSetupAst = parse(
scriptSetup.content,
{
plugins: [
...plugins,
// allow top level await but only inside <script setup>
'topLevelAwait'
],
sourceType: 'module'
},
startOffset
)
for (const node of scriptSetupAst.body) {
const start = node.start! + startOffset
let end = node.end! + startOffset
// locate comment
if (node.trailingComments && node.trailingComments.length > 0) {
const lastCommentNode =
node.trailingComments[node.trailingComments.length - 1]
end = lastCommentNode.end! + startOffset
}
// locate the end of whitespace between this statement and the next
while (end <= source.length) {
if (!/\s/.test(source.charAt(end))) {
break
}
end++
}
// (Dropped) `ref: x` bindings
if (
node.type === 'LabeledStatement' &&
node.label.name === 'ref' &&
node.body.type === 'ExpressionStatement'
) {
error(
`ref sugar using the label syntax was an experimental proposal and ` +
`has been dropped based on community feedback. Please check out ` +
`the new proposal at https://github.com/vuejs/rfcs/discussions/369`,
node
)
}
if (node.type === 'ImportDeclaration') {
// import declarations are moved to top
s.move(start, end, 0)
// dedupe imports
let removed = 0
const removeSpecifier = (i: number) => {
const removeLeft = i > removed
removed++
const current = node.specifiers[i]
const next = node.specifiers[i + 1]
s.remove(
removeLeft
? node.specifiers[i - 1].end! + startOffset
: current.start! + startOffset,
next && !removeLeft
? next.start! + startOffset
: current.end! + startOffset
)
}
for (let i = 0; i < node.specifiers.length; i++) {
const specifier = node.specifiers[i]
const local = specifier.local.name
let imported =
specifier.type === 'ImportSpecifier' &&
specifier.imported.type === 'Identifier' &&
specifier.imported.name
if (specifier.type === 'ImportNamespaceSpecifier') {
imported = '*'
}
const source = node.source.value
const existing = userImports[local]
if (
source === 'vue' &&
(imported === DEFINE_PROPS ||
imported === DEFINE_EMITS ||
imported === DEFINE_EXPOSE)
) {
warnOnce(
`\`${imported}\` is a compiler macro and no longer needs to be imported.`
)
removeSpecifier(i)
} else if (existing) {
if (existing.source === source && existing.imported === imported) {
// already imported in <script setup>, dedupe
removeSpecifier(i)
} else {
error(`different imports aliased to same local name.`, specifier)
}
} else {
registerUserImport(
source,
local,
imported,
node.importKind === 'type' ||
(specifier.type === 'ImportSpecifier' &&
specifier.importKind === 'type'),
true
)
}
}
if (node.specifiers.length && removed === node.specifiers.length) {
s.remove(node.start! + startOffset, node.end! + startOffset)
}
}
if (node.type === 'ExpressionStatement') {
// process `defineProps` and `defineEmit(s)` calls
if (
processDefineProps(node.expression) ||
processDefineEmits(node.expression) ||
processWithDefaults(node.expression)
) {
s.remove(node.start! + startOffset, node.end! + startOffset)
} else if (processDefineExpose(node.expression)) {
// defineExpose({}) -> expose({})
const callee = (node.expression as CallExpression).callee
s.overwrite(
callee.start! + startOffset,
callee.end! + startOffset,
'expose'
)
}
}
if (node.type === 'VariableDeclaration' && !node.declare) {
const total = node.declarations.length
let left = total
for (let i = 0; i < total; i++) {
const decl = node.declarations[i]
if (decl.init) {
// defineProps / defineEmits
const isDefineProps =
processDefineProps(decl.init, decl.id) ||
processWithDefaults(decl.init, decl.id)
const isDefineEmits = processDefineEmits(decl.init, decl.id)
if (isDefineProps || isDefineEmits) {
if (left === 1) {
s.remove(node.start! + startOffset, node.end! + startOffset)
} else {
let start = decl.start! + startOffset
let end = decl.end! + startOffset
if (i === 0) {
// first one, locate the start of the next
end = node.declarations[i + 1].start! + startOffset
} else {
// not first one, locate the end of the prev
start = node.declarations[i - 1].end! + startOffset
}
s.remove(start, end)
left--
}
}
}
}
}
// walk declarations to record declared bindings
if (
(node.type === 'VariableDeclaration' ||
node.type === 'FunctionDeclaration' ||
node.type === 'ClassDeclaration') &&
!node.declare
) {
walkDeclaration(node, setupBindings, userImportAlias)
}
// walk statements & named exports / variable declarations for top level
// await
if (
(node.type === 'VariableDeclaration' && !node.declare) ||
node.type.endsWith('Statement')
) {
const scope: Statement[][] = [scriptSetupAst.body]
;(walk as any)(node, {
enter(child: Node, parent: Node) {
if (isFunctionType(child)) {
this.skip()
}
if (child.type === 'BlockStatement') {
scope.push(child.body)
}
if (child.type === 'AwaitExpression') {
error(
`Vue 2 does not support top level await in <script setup>.`,
child
)
}
},
exit(node: Node) {
if (node.type === 'BlockStatement') scope.pop()
}
})
}
if (
(node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
node.type === 'ExportAllDeclaration' ||
node.type === 'ExportDefaultDeclaration'
) {
error(
`<script setup> cannot contain ES module exports. ` +
`If you are using a previous version of <script setup>, please ` +
`consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
node
)
}
if (isTS) {
// runtime enum
if (node.type === 'TSEnumDeclaration') {
registerBinding(setupBindings, node.id, BindingTypes.SETUP_CONST)
}
// move all Type declarations to outer scope
if (
node.type.startsWith('TS') ||
(node.type === 'ExportNamedDeclaration' &&
node.exportKind === 'type') ||
(node.type === 'VariableDeclaration' && node.declare)
) {
recordType(node, declaredTypes)
s.move(start, end, 0)
}
}
}
// 3. Apply reactivity transform
// if (
// (enableReactivityTransform &&
// // normal <script> had ref bindings that maybe used in <script setup>
// (refBindings || shouldTransform(scriptSetup.content))) ||
// propsDestructureDecl
// ) {
// const { rootRefs, importedHelpers } = transformAST(
// scriptSetupAst,
// s,
// startOffset,
// refBindings,
// propsDestructuredBindings
// )
// refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs
// for (const h of importedHelpers) {
// helperImports.add(h)
// }
// }
// 4. extract runtime props/emits code from setup context type
if (propsTypeDecl) {
extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes, isProd)
}
if (emitsTypeDecl) {
extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits)
}
// 5. check useOptions args to make sure it doesn't reference setup scope
// variables
checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)
checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)
checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)
checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)
// 6. remove non-script content
if (script) {
if (startOffset < scriptStartOffset!) {
// <script setup> before <script>
s.remove(0, startOffset)
s.remove(endOffset, scriptStartOffset!)
s.remove(scriptEndOffset!, source.length)
} else {
// <script> before <script setup>
s.remove(0, scriptStartOffset!)
s.remove(scriptEndOffset!, startOffset)
s.remove(endOffset, source.length)
}
} else {
// only <script setup>
s.remove(0, startOffset)
s.remove(endOffset, source.length)
}
// 7. analyze binding metadata
if (scriptAst) {
Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))
}
if (propsRuntimeDecl) {
for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {
bindingMetadata[key] = BindingTypes.PROPS
}
}
for (const key in typeDeclaredProps) {
bindingMetadata[key] = BindingTypes.PROPS
}
// props aliases
// if (propsDestructureDecl) {
// if (propsDestructureRestId) {
// bindingMetadata[propsDestructureRestId] =
// BindingTypes.SETUP_REACTIVE_CONST
// }
// for (const key in propsDestructuredBindings) {
// const { local } = propsDestructuredBindings[key]
// if (local !== key) {
// bindingMetadata[local] = BindingTypes.PROPS_ALIASED
// ;(bindingMetadata.__propsAliases ||
// (bindingMetadata.__propsAliases = {}))[local] = key
// }
// }
// }
for (const [key, { isType, imported, source }] of Object.entries(
userImports
)) {
if (isType) continue
bindingMetadata[key] =
imported === '*' ||
(imported === 'default' && source.endsWith('.vue')) ||
source === 'vue'
? BindingTypes.SETUP_CONST
: BindingTypes.SETUP_MAYBE_REF
}
for (const key in scriptBindings) {
bindingMetadata[key] = scriptBindings[key]
}
for (const key in setupBindings) {
bindingMetadata[key] = setupBindings[key]
}
// known ref bindings
if (refBindings) {
for (const key of refBindings) {
bindingMetadata[key] = BindingTypes.SETUP_REF
}
}
// 8. inject `useCssVars` calls
if (cssVars.length) {
helperImports.add(CSS_VARS_HELPER)
s.prependRight(
startOffset,
`\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`
)
}
// 9. finalize setup() argument signature
let args = `__props`
if (propsTypeDecl) {
// mark as any and only cast on assignment
// since the user defined complex types may be incompatible with the
// inferred type from generated runtime declarations
args += `: any`
}
// inject user assignment of props
// we use a default __props so that template expressions referencing props
// can use it directly
if (propsIdentifier) {
s.prependLeft(
startOffset,
`\nconst ${propsIdentifier} = __props${
propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``
};\n`
)
}
if (propsDestructureRestId) {
s.prependLeft(
startOffset,
`\nconst ${propsDestructureRestId} = ${helper(
`createPropsRestProxy`
)}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))});\n`
)
}
const destructureElements = hasDefineExposeCall ? [`expose`] : []
if (emitIdentifier) {
destructureElements.push(
emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`
)
}
if (destructureElements.length) {
args += `, { ${destructureElements.join(', ')} }`
if (emitsTypeDecl) {
args += `: { emit: (${scriptSetup.content.slice(
emitsTypeDecl.start!,
emitsTypeDecl.end!
)}), expose: any, slots: any, attrs: any }`
}
}
// 10. generate return statement
const allBindings: Record<string, any> = {
...scriptBindings,
...setupBindings
}
for (const key in userImports) {
if (!userImports[key].isType && userImports[key].isUsedInTemplate) {
allBindings[key] = true
}
}
// __sfc marker indicates these bindings are compiled from <script setup>
// and should not be proxied on `this`
const returned = `{ ${__TEST__ ? `` : `__sfc: true,`}${Object.keys(
allBindings
).join(', ')} }`
s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
// 11. finalize default export
let runtimeOptions = ``
if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
const match = filename.match(/([^/\\]+)\.\w+$/)
if (match) {
runtimeOptions += `\n __name: '${match[1]}',`
}
}
if (hasInlinedSsrRenderFn) {
runtimeOptions += `\n __ssrInlineRender: true,`
}
if (propsRuntimeDecl) {
let declCode = scriptSetup.content
.slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)
.trim()
if (propsDestructureDecl) {
const defaults: string[] = []
for (const key in propsDestructuredBindings) {
const d = genDestructuredDefaultValue(key)
if (d) defaults.push(`${key}: ${d}`)
}
if (defaults.length) {
declCode = `${helper(
`mergeDefaults`
)}(${declCode}, {\n ${defaults.join(',\n ')}\n})`
}
}
runtimeOptions += `\n props: ${declCode},`
} else if (propsTypeDecl) {
runtimeOptions += genRuntimeProps(typeDeclaredProps)
}
if (emitsRuntimeDecl) {
runtimeOptions += `\n emits: ${scriptSetup.content
.slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)
.trim()},`
} else if (emitsTypeDecl) {
runtimeOptions += genRuntimeEmits(typeDeclaredEmits)
}
// wrap setup code with function.
if (isTS) {
// for TS, make sure the exported type is still valid type with
// correct props information
// we have to use object spread for types to be merged properly
// user's TS setting should compile it down to proper targets
// export default defineComponent({ ...__default__, ... })
const def = defaultExport ? `\n ...${DEFAULT_VAR},` : ``
s.prependLeft(
startOffset,
`\nexport default /*#__PURE__*/${helper(
`defineComponent`
)}({${def}${runtimeOptions}\n setup(${args}) {\n`
)
s.appendRight(endOffset, `})`)
} else {
if (defaultExport) {
// without TS, can't rely on rest spread, so we use Object.assign
// export default Object.assign(__default__, { ... })
s.prependLeft(
startOffset,
`\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n ` +
`setup(${args}) {\n`
)
s.appendRight(endOffset, `})`)
} else {
s.prependLeft(
startOffset,
`\nexport default {${runtimeOptions}\n setup(${args}) {\n`
)
s.appendRight(endOffset, `}`)
}
}
// 12. finalize Vue helper imports
if (helperImports.size > 0) {
s.prepend(
`import { ${[...helperImports]
.map(h => `${h} as _${h}`)
.join(', ')} } from 'vue'\n`
)
}
s.trim()
return {
...scriptSetup,
bindings: bindingMetadata,
imports: userImports,
content: s.toString(),
map: genSourceMap
? (s.generateMap({
source: filename,
hires: true,
includeContent: true
}) as unknown as RawSourceMap)
: undefined,
scriptAst: scriptAst?.body,
scriptSetupAst: scriptSetupAst?.body
}
}
Domain
Subdomains
Calls
- analyzeScriptBindings()
- extractRuntimeEmits()
- extractRuntimeProps()
- genCssVarsCode()
- genNormalScriptCssVarsCode()
- genRuntimeEmits()
- generateCodeFrame()
- getObjectOrArrayExpressionKeys()
- isCallOf()
- isFunctionType()
- isImportUsed()
- parse()
- recordType()
- registerBinding()
- remove()
- rewriteDefault()
- toRuntimeTypeString()
- toString()
- walkDeclaration()
- walkIdentifiers()
- warnOnce()
Source
Frequently Asked Questions
What does compileScript() do?
compileScript() is a function in the vue codebase.
What does compileScript() call?
compileScript() calls 21 function(s): analyzeScriptBindings, extractRuntimeEmits, extractRuntimeProps, genCssVarsCode, genNormalScriptCssVarsCode, genRuntimeEmits, generateCodeFrame, getObjectOrArrayExpressionKeys, and 13 more.
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free