1import MagicString from 'magic-string'2import LRU from 'lru-cache'3import { walkIdentifiers, isFunctionType } from './babelUtils'4import { BindingMetadata, BindingTypes } from './types'5import { SFCDescriptor, SFCScriptBlock } from './parseComponent'6import {7 parse as _parse,8 parseExpression,9 ParserOptions,10 ParserPlugin11} from '@babel/parser'12import { generateCodeFrame } from 'compiler/codeframe'13import { camelize, capitalize, isBuiltInTag, makeMap } from 'shared/util'14import { parseHTML } from 'compiler/parser/html-parser'15import { baseOptions as webCompilerOptions } from 'web/compiler/options'16import {17 Node,18 Declaration,19 ObjectPattern,20 ObjectExpression,21 ArrayPattern,22 Identifier,23 ExportSpecifier,24 TSType,25 TSTypeLiteral,26 TSFunctionType,27 ObjectProperty,28 ArrayExpression,29 Statement,30 CallExpression,31 RestElement,32 TSInterfaceBody,33 Program,34 ObjectMethod,35 LVal,36 Expression37} from '@babel/types'38import { walk } from 'estree-walker'39import { RawSourceMap } from 'source-map'40import { warnOnce } from './warn'41import { isReservedTag } from 'web/util'42import { bindRE, dirRE, onRE, slotRE } from 'compiler/parser'43import { parseText } from 'compiler/parser/text-parser'44import { DEFAULT_FILENAME } from './parseComponent'45import {46 CSS_VARS_HELPER,47 genCssVarsCode,48 genNormalScriptCssVarsCode49} from './cssVars'50import { rewriteDefault } from './rewriteDefault'5152// Special compiler macros53const DEFINE_PROPS = 'defineProps'54const DEFINE_EMITS = 'defineEmits'55const DEFINE_EXPOSE = 'defineExpose'56const WITH_DEFAULTS = 'withDefaults'5758// constants59const DEFAULT_VAR = `__default__`6061const isBuiltInDir = makeMap(62 `once,memo,if,for,else,else-if,slot,text,html,on,bind,model,show,cloak,is`63)6465export interface SFCScriptCompileOptions {66 /**67 * Scope ID for prefixing injected CSS variables.68 * This must be consistent with the `id` passed to `compileStyle`.69 */70 id: string71 /**72 * Production mode. Used to determine whether to generate hashed CSS variables73 */74 isProd?: boolean75 /**76 * Enable/disable source map. Defaults to true.77 */78 sourceMap?: boolean79 /**80 * https://babeljs.io/docs/en/babel-parser#plugins81 */82 babelParserPlugins?: ParserPlugin[]83}8485export interface ImportBinding {86 isType: boolean87 imported: string88 source: string89 isFromSetup: boolean90 isUsedInTemplate: boolean91}9293/**94 * Compile `<script setup>`95 * It requires the whole SFC descriptor because we need to handle and merge96 * normal `<script>` + `<script setup>` if both are present.97 */98export function compileScript(99 sfc: SFCDescriptor,100 options: SFCScriptCompileOptions = { id: '' }101): SFCScriptBlock {102 let { filename, script, scriptSetup, source } = sfc103 const isProd = !!options.isProd104 const genSourceMap = options.sourceMap !== false105 let refBindings: string[] | undefined106107 const cssVars = sfc.cssVars108 const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''109 const scriptLang = script && script.lang110 const scriptSetupLang = scriptSetup && scriptSetup.lang111 const isTS =112 scriptLang === 'ts' ||113 scriptLang === 'tsx' ||114 scriptSetupLang === 'ts' ||115 scriptSetupLang === 'tsx'116117 // resolve parser plugins118 const plugins: ParserPlugin[] = []119 if (!isTS || scriptLang === 'tsx' || scriptSetupLang === 'tsx') {120 plugins.push('jsx')121 } else {122 // If don't match the case of adding jsx, should remove the jsx from the babelParserPlugins123 if (options.babelParserPlugins)124 options.babelParserPlugins = options.babelParserPlugins.filter(125 n => n !== 'jsx'126 )127 }128 if (options.babelParserPlugins) plugins.push(...options.babelParserPlugins)129 if (isTS) {130 plugins.push('typescript')131 if (!plugins.includes('decorators')) {132 plugins.push('decorators-legacy')133 }134 }135136 if (!scriptSetup) {137 if (!script) {138 throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)139 }140 if (scriptLang && !isTS && scriptLang !== 'jsx') {141 // do not process non js/ts script blocks142 return script143 }144 try {145 let content = script.content146 let map = script.map147 const scriptAst = _parse(content, {148 plugins,149 sourceType: 'module'150 }).program151 const bindings = analyzeScriptBindings(scriptAst.body)152 if (cssVars.length) {153 content = rewriteDefault(content, DEFAULT_VAR, plugins)154 content += genNormalScriptCssVarsCode(155 cssVars,156 bindings,157 scopeId,158 isProd159 )160 content += `\nexport default ${DEFAULT_VAR}`161 }162 return {163 ...script,164 content,165 map,166 bindings,167 scriptAst: scriptAst.body168 }169 } catch (e: any) {170 // silently fallback if parse fails since user may be using custom171 // babel syntax172 return script173 }174 }175176 if (script && scriptLang !== scriptSetupLang) {177 throw new Error(178 `[@vue/compiler-sfc] <script> and <script setup> must have the same ` +179 `language type.`180 )181 }182183 if (scriptSetupLang && !isTS && scriptSetupLang !== 'jsx') {184 // do not process non js/ts script blocks185 return scriptSetup186 }187188 // metadata that needs to be returned189 const bindingMetadata: BindingMetadata = {}190 const helperImports: Set<string> = new Set()191 const userImports: Record<string, ImportBinding> = Object.create(null)192 const userImportAlias: Record<string, string> = Object.create(null)193 const scriptBindings: Record<string, BindingTypes> = Object.create(null)194 const setupBindings: Record<string, BindingTypes> = Object.create(null)195196 let defaultExport: Node | undefined197 let hasDefinePropsCall = false198 let hasDefineEmitCall = false199 let hasDefineExposeCall = false200 let hasDefaultExportName = false201 let propsRuntimeDecl: Node | undefined202 let propsRuntimeDefaults: ObjectExpression | undefined203 let propsDestructureDecl: Node | undefined204 let propsDestructureRestId: string | undefined205 let propsTypeDecl: TSTypeLiteral | TSInterfaceBody | undefined206 let propsTypeDeclRaw: Node | undefined207 let propsIdentifier: string | undefined208 let emitsRuntimeDecl: Node | undefined209 let emitsTypeDecl:210 | TSFunctionType211 | TSTypeLiteral212 | TSInterfaceBody213 | undefined214 let emitsTypeDeclRaw: Node | undefined215 let emitIdentifier: string | undefined216 let hasInlinedSsrRenderFn = false217 // props/emits declared via types218 const typeDeclaredProps: Record<string, PropTypeData> = {}219 const typeDeclaredEmits: Set<string> = new Set()220 // record declared types for runtime props type generation221 const declaredTypes: Record<string, string[]> = {}222 // props destructure data223 const propsDestructuredBindings: Record<224 string, // public prop key225 {226 local: string // local identifier, may be different227 default?: Expression228 }229 > = Object.create(null)230231 // magic-string state232 const s = new MagicString(source)233 const startOffset = scriptSetup.start234 const endOffset = scriptSetup.end235 const scriptStartOffset = script && script.start236 const scriptEndOffset = script && script.end237238 function helper(key: string): string {239 helperImports.add(key)240 return `_${key}`241 }242243 function parse(244 input: string,245 options: ParserOptions,246 offset: number247 ): Program {248 try {249 return _parse(input, options).program250 } catch (e: any) {251 e.message = `[@vue/compiler-sfc] ${252 e.message253 }\n\n${filename}\n${generateCodeFrame(254 source,255 e.pos + offset,256 e.pos + offset + 1257 )}`258 throw e259 }260 }261262 function error(263 msg: string,264 node: Node,265 end: number = node.end! + startOffset266 ): never {267 throw new Error(268 `[@vue/compiler-sfc] ${msg}\n\n${filename}\n${generateCodeFrame(269 source,270 node.start! + startOffset,271 end272 )}`273 )274 }275276 function registerUserImport(277 source: string,278 local: string,279 imported: string | false,280 isType: boolean,281 isFromSetup: boolean282 ) {283 if (source === 'vue' && imported) {284 userImportAlias[imported] = local285 }286287 let isUsedInTemplate = true288 if (sfc.template && !sfc.template.src && !sfc.template.lang) {289 isUsedInTemplate = isImportUsed(local, sfc, isTS)290 }291292 userImports[local] = {293 isType,294 imported: imported || 'default',295 source,296 isFromSetup,297 isUsedInTemplate298 }299 }300301 function processDefineProps(node: Node, declId?: LVal): boolean {302 if (!isCallOf(node, DEFINE_PROPS)) {303 return false304 }305306 if (hasDefinePropsCall) {307 error(`duplicate ${DEFINE_PROPS}() call`, node)308 }309 hasDefinePropsCall = true310311 propsRuntimeDecl = node.arguments[0]312313 // call has type parameters - infer runtime types from it314 if (node.typeParameters) {315 if (propsRuntimeDecl) {316 error(317 `${DEFINE_PROPS}() cannot accept both type and non-type arguments ` +318 `at the same time. Use one or the other.`,319 node320 )321 }322323 propsTypeDeclRaw = node.typeParameters.params[0]324 propsTypeDecl = resolveQualifiedType(325 propsTypeDeclRaw,326 node => node.type === 'TSTypeLiteral'327 ) as TSTypeLiteral | TSInterfaceBody | undefined328329 if (!propsTypeDecl) {330 error(331 `type argument passed to ${DEFINE_PROPS}() must be a literal type, ` +332 `or a reference to an interface or literal type.`,333 propsTypeDeclRaw334 )335 }336 }337338 if (declId) {339 propsIdentifier = scriptSetup!.content.slice(declId.start!, declId.end!)340 }341342 return true343 }344345 function processWithDefaults(node: Node, declId?: LVal): boolean {346 if (!isCallOf(node, WITH_DEFAULTS)) {347 return false348 }349 if (processDefineProps(node.arguments[0], declId)) {350 if (propsRuntimeDecl) {351 error(352 `${WITH_DEFAULTS} can only be used with type-based ` +353 `${DEFINE_PROPS} declaration.`,354 node355 )356 }357 if (propsDestructureDecl) {358 error(359 `${WITH_DEFAULTS}() is unnecessary when using destructure with ${DEFINE_PROPS}().\n` +360 `Prefer using destructure default values, e.g. const { foo = 1 } = defineProps(...).`,361 node.callee362 )363 }364 propsRuntimeDefaults = node.arguments[1] as ObjectExpression365 if (366 !propsRuntimeDefaults ||367 propsRuntimeDefaults.type !== 'ObjectExpression'368 ) {369 error(370 `The 2nd argument of ${WITH_DEFAULTS} must be an object literal.`,371 propsRuntimeDefaults || node372 )373 }374 } else {375 error(376 `${WITH_DEFAULTS}' first argument must be a ${DEFINE_PROPS} call.`,377 node.arguments[0] || node378 )379 }380 return true381 }382383 function processDefineEmits(node: Node, declId?: LVal): boolean {384 if (!isCallOf(node, DEFINE_EMITS)) {385 return false386 }387 if (hasDefineEmitCall) {388 error(`duplicate ${DEFINE_EMITS}() call`, node)389 }390 hasDefineEmitCall = true391 emitsRuntimeDecl = node.arguments[0]392 if (node.typeParameters) {393 if (emitsRuntimeDecl) {394 error(395 `${DEFINE_EMITS}() cannot accept both type and non-type arguments ` +396 `at the same time. Use one or the other.`,397 node398 )399 }400401 emitsTypeDeclRaw = node.typeParameters.params[0]402 emitsTypeDecl = resolveQualifiedType(403 emitsTypeDeclRaw,404 node => node.type === 'TSFunctionType' || node.type === 'TSTypeLiteral'405 ) as TSFunctionType | TSTypeLiteral | TSInterfaceBody | undefined406407 if (!emitsTypeDecl) {408 error(409 `type argument passed to ${DEFINE_EMITS}() must be a function type, ` +410 `a literal type with call signatures, or a reference to the above types.`,411 emitsTypeDeclRaw412 )413 }414 }415416 if (declId) {417 emitIdentifier =418 declId.type === 'Identifier'419 ? declId.name420 : scriptSetup!.content.slice(declId.start!, declId.end!)421 }422423 return true424 }425426 function resolveQualifiedType(427 node: Node,428 qualifier: (node: Node) => boolean429 ) {430 if (qualifier(node)) {431 return node432 }433 if (434 node.type === 'TSTypeReference' &&435 node.typeName.type === 'Identifier'436 ) {437 const refName = node.typeName.name438 const isQualifiedType = (node: Node): Node | undefined => {439 if (440 node.type === 'TSInterfaceDeclaration' &&441 node.id.name === refName442 ) {443 return node.body444 } else if (445 node.type === 'TSTypeAliasDeclaration' &&446 node.id.name === refName &&447 qualifier(node.typeAnnotation)448 ) {449 return node.typeAnnotation450 } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {451 return isQualifiedType(node.declaration)452 }453 }454 const body = scriptAst455 ? [...scriptSetupAst.body, ...scriptAst.body]456 : scriptSetupAst.body457 for (const node of body) {458 const qualified = isQualifiedType(node)459 if (qualified) {460 return qualified461 }462 }463 }464 }465466 function processDefineExpose(node: Node): boolean {467 if (isCallOf(node, DEFINE_EXPOSE)) {468 if (hasDefineExposeCall) {469 error(`duplicate ${DEFINE_EXPOSE}() call`, node)470 }471 hasDefineExposeCall = true472 return true473 }474 return false475 }476477 function checkInvalidScopeReference(node: Node | undefined, method: string) {478 if (!node) return479 walkIdentifiers(node, id => {480 if (setupBindings[id.name]) {481 error(482 `\`${method}()\` in <script setup> cannot reference locally ` +483 `declared variables because it will be hoisted outside of the ` +484 `setup() function. If your component options require initialization ` +485 `in the module scope, use a separate normal <script> to export ` +486 `the options instead.`,487 id488 )489 }490 })491 }492493 /**494 * check defaults. If the default object is an object literal with only495 * static properties, we can directly generate more optimized default496 * declarations. Otherwise we will have to fallback to runtime merging.497 */498 function hasStaticWithDefaults() {499 return (500 propsRuntimeDefaults &&501 propsRuntimeDefaults.type === 'ObjectExpression' &&502 propsRuntimeDefaults.properties.every(503 node =>504 (node.type === 'ObjectProperty' && !node.computed) ||505 node.type === 'ObjectMethod'506 )507 )508 }509510 function genRuntimeProps(props: Record<string, PropTypeData>) {511 const keys = Object.keys(props)512 if (!keys.length) {513 return ``514 }515 const hasStaticDefaults = hasStaticWithDefaults()516 const scriptSetupSource = scriptSetup!.content517 let propsDecls = `{518 ${keys519 .map(key => {520 let defaultString: string | undefined521 const destructured = genDestructuredDefaultValue(key)522 if (destructured) {523 defaultString = `default: ${destructured}`524 } else if (hasStaticDefaults) {525 const prop = propsRuntimeDefaults!.properties.find(526 (node: any) => node.key.name === key527 ) as ObjectProperty | ObjectMethod528 if (prop) {529 if (prop.type === 'ObjectProperty') {530 // prop has corresponding static default value531 defaultString = `default: ${scriptSetupSource.slice(532 prop.value.start!,533 prop.value.end!534 )}`535 } else {536 defaultString = `default() ${scriptSetupSource.slice(537 prop.body.start!,538 prop.body.end!539 )}`540 }541 }542 }543544 const { type, required } = props[key]545 if (!isProd) {546 return `${key}: { type: ${toRuntimeTypeString(547 type548 )}, required: ${required}${549 defaultString ? `, ${defaultString}` : ``550 } }`551 } else if (552 type.some(553 el => el === 'Boolean' || (defaultString && el === 'Function')554 )555 ) {556 // #4783 production: if boolean or defaultString and function exists, should keep the type.557 return `${key}: { type: ${toRuntimeTypeString(type)}${558 defaultString ? `, ${defaultString}` : ``559 } }`560 } else {561 // production: checks are useless562 return `${key}: ${defaultString ? `{ ${defaultString} }` : 'null'}`563 }564 })565 .join(',\n ')}\n }`566567 if (propsRuntimeDefaults && !hasStaticDefaults) {568 propsDecls = `${helper('mergeDefaults')}(${propsDecls}, ${source.slice(569 propsRuntimeDefaults.start! + startOffset,570 propsRuntimeDefaults.end! + startOffset571 )})`572 }573574 return `\n props: ${propsDecls},`575 }576577 function genDestructuredDefaultValue(key: string): string | undefined {578 const destructured = propsDestructuredBindings[key]579 if (destructured && destructured.default) {580 const value = scriptSetup!.content.slice(581 destructured.default.start!,582 destructured.default.end!583 )584 const isLiteral = destructured.default.type.endsWith('Literal')585 return isLiteral ? value : `() => (${value})`586 }587 }588589 function genSetupPropsType(node: TSTypeLiteral | TSInterfaceBody) {590 const scriptSetupSource = scriptSetup!.content591 if (hasStaticWithDefaults()) {592 // if withDefaults() is used, we need to remove the optional flags593 // on props that have default values594 let res = `{ `595 const members = node.type === 'TSTypeLiteral' ? node.members : node.body596 for (const m of members) {597 if (598 (m.type === 'TSPropertySignature' ||599 m.type === 'TSMethodSignature') &&600 m.typeAnnotation &&601 m.key.type === 'Identifier'602 ) {603 if (604 propsRuntimeDefaults!.properties.some(605 (p: any) => p.key.name === (m.key as Identifier).name606 )607 ) {608 res +=609 m.key.name +610 (m.type === 'TSMethodSignature' ? '()' : '') +611 scriptSetupSource.slice(612 m.typeAnnotation.start!,613 m.typeAnnotation.end!614 ) +615 ', '616 } else {617 res +=618 scriptSetupSource.slice(m.start!, m.typeAnnotation.end!) + `, `619 }620 }621 }622 return (res.length ? res.slice(0, -2) : res) + ` }`623 } else {624 return scriptSetupSource.slice(node.start!, node.end!)625 }626 }627628 // 1. process normal <script> first if it exists629 let scriptAst: Program | undefined630 if (script) {631 scriptAst = parse(632 script.content,633 {634 plugins,635 sourceType: 'module'636 },637 scriptStartOffset!638 )639640 for (const node of scriptAst.body) {641 if (node.type === 'ImportDeclaration') {642 // record imports for dedupe643 for (const specifier of node.specifiers) {644 const imported =645 specifier.type === 'ImportSpecifier' &&646 specifier.imported.type === 'Identifier' &&647 specifier.imported.name648 registerUserImport(649 node.source.value,650 specifier.local.name,651 imported,652 node.importKind === 'type' ||653 (specifier.type === 'ImportSpecifier' &&654 specifier.importKind === 'type'),655 false656 )657 }658 } else if (node.type === 'ExportDefaultDeclaration') {659 // export default660 defaultExport = node661662 // check if user has manually specified `name` or 'render` option in663 // export default664 // if has name, skip name inference665 // if has render and no template, generate return object instead of666 // empty render function (#4980)667 let optionProperties668 if (defaultExport.declaration.type === 'ObjectExpression') {669 optionProperties = defaultExport.declaration.properties670 } else if (671 defaultExport.declaration.type === 'CallExpression' &&672 defaultExport.declaration.arguments[0].type === 'ObjectExpression'673 ) {674 optionProperties = defaultExport.declaration.arguments[0].properties675 }676 if (optionProperties) {677 for (const s of optionProperties) {678 if (679 s.type === 'ObjectProperty' &&680 s.key.type === 'Identifier' &&681 s.key.name === 'name'682 ) {683 hasDefaultExportName = true684 }685 }686 }687688 // export default { ... } --> const __default__ = { ... }689 const start = node.start! + scriptStartOffset!690 const end = node.declaration.start! + scriptStartOffset!691 s.overwrite(start, end, `const ${DEFAULT_VAR} = `)692 } else if (node.type === 'ExportNamedDeclaration') {693 const defaultSpecifier = node.specifiers.find(694 s => s.exported.type === 'Identifier' && s.exported.name === 'default'695 ) as ExportSpecifier696 if (defaultSpecifier) {697 defaultExport = node698 // 1. remove specifier699 if (node.specifiers.length > 1) {700 s.remove(701 defaultSpecifier.start! + scriptStartOffset!,702 defaultSpecifier.end! + scriptStartOffset!703 )704 } else {705 s.remove(706 node.start! + scriptStartOffset!,707 node.end! + scriptStartOffset!708 )709 }710 if (node.source) {711 // export { x as default } from './x'712 // rewrite to `import { x as __default__ } from './x'` and713 // add to top714 s.prepend(715 `import { ${defaultSpecifier.local.name} as ${DEFAULT_VAR} } from '${node.source.value}'\n`716 )717 } else {718 // export { x as default }719 // rewrite to `const __default__ = x` and move to end720 s.appendLeft(721 scriptEndOffset!,722 `\nconst ${DEFAULT_VAR} = ${defaultSpecifier.local.name}\n`723 )724 }725 }726 if (node.declaration) {727 walkDeclaration(node.declaration, scriptBindings, userImportAlias)728 }729 } else if (730 (node.type === 'VariableDeclaration' ||731 node.type === 'FunctionDeclaration' ||732 node.type === 'ClassDeclaration' ||733 node.type === 'TSEnumDeclaration') &&734 !node.declare735 ) {736 walkDeclaration(node, scriptBindings, userImportAlias)737 }738 }739740 // apply reactivity transform741 // if (enableReactivityTransform && shouldTransform(script.content)) {742 // const { rootRefs, importedHelpers } = transformAST(743 // scriptAst,744 // s,745 // scriptStartOffset!746 // )747 // refBindings = rootRefs748 // for (const h of importedHelpers) {749 // helperImports.add(h)750 // }751 // }752753 // <script> after <script setup>754 // we need to move the block up so that `const __default__` is755 // declared before being used in the actual component definition756 if (scriptStartOffset! > startOffset) {757 // if content doesn't end with newline, add one758 if (!/\n$/.test(script.content.trim())) {759 s.appendLeft(scriptEndOffset!, `\n`)760 }761 s.move(scriptStartOffset!, scriptEndOffset!, 0)762 }763 }764765 // 2. parse <script setup> and walk over top level statements766 const scriptSetupAst = parse(767 scriptSetup.content,768 {769 plugins: [770 ...plugins,771 // allow top level await but only inside <script setup>772 'topLevelAwait'773 ],774 sourceType: 'module'775 },776 startOffset777 )778779 for (const node of scriptSetupAst.body) {780 const start = node.start! + startOffset781 let end = node.end! + startOffset782 // locate comment783 if (node.trailingComments && node.trailingComments.length > 0) {784 const lastCommentNode =785 node.trailingComments[node.trailingComments.length - 1]786 end = lastCommentNode.end! + startOffset787 }788 // locate the end of whitespace between this statement and the next789 while (end <= source.length) {790 if (!/\s/.test(source.charAt(end))) {791 break792 }793 end++794 }795796 // (Dropped) `ref: x` bindings797 if (798 node.type === 'LabeledStatement' &&799 node.label.name === 'ref' &&800 node.body.type === 'ExpressionStatement'801 ) {802 error(803 `ref sugar using the label syntax was an experimental proposal and ` +804 `has been dropped based on community feedback. Please check out ` +805 `the new proposal at https://github.com/vuejs/rfcs/discussions/369`,806 node807 )808 }809810 if (node.type === 'ImportDeclaration') {811 // import declarations are moved to top812 s.move(start, end, 0)813814 // dedupe imports815 let removed = 0816 const removeSpecifier = (i: number) => {817 const removeLeft = i > removed818 removed++819 const current = node.specifiers[i]820 const next = node.specifiers[i + 1]821 s.remove(822 removeLeft823 ? node.specifiers[i - 1].end! + startOffset824 : current.start! + startOffset,825 next && !removeLeft826 ? next.start! + startOffset827 : current.end! + startOffset828 )829 }830831 for (let i = 0; i < node.specifiers.length; i++) {832 const specifier = node.specifiers[i]833 const local = specifier.local.name834 let imported =835 specifier.type === 'ImportSpecifier' &&836 specifier.imported.type === 'Identifier' &&837 specifier.imported.name838 if (specifier.type === 'ImportNamespaceSpecifier') {839 imported = '*'840 }841 const source = node.source.value842 const existing = userImports[local]843 if (844 source === 'vue' &&845 (imported === DEFINE_PROPS ||846 imported === DEFINE_EMITS ||847 imported === DEFINE_EXPOSE)848 ) {849 warnOnce(850 `\`${imported}\` is a compiler macro and no longer needs to be imported.`851 )852 removeSpecifier(i)853 } else if (existing) {854 if (existing.source === source && existing.imported === imported) {855 // already imported in <script setup>, dedupe856 removeSpecifier(i)857 } else {858 error(`different imports aliased to same local name.`, specifier)859 }860 } else {861 registerUserImport(862 source,863 local,864 imported,865 node.importKind === 'type' ||866 (specifier.type === 'ImportSpecifier' &&867 specifier.importKind === 'type'),868 true869 )870 }871 }872 if (node.specifiers.length && removed === node.specifiers.length) {873 s.remove(node.start! + startOffset, node.end! + startOffset)874 }875 }876877 if (node.type === 'ExpressionStatement') {878 // process `defineProps` and `defineEmit(s)` calls879 if (880 processDefineProps(node.expression) ||881 processDefineEmits(node.expression) ||882 processWithDefaults(node.expression)883 ) {884 s.remove(node.start! + startOffset, node.end! + startOffset)885 } else if (processDefineExpose(node.expression)) {886 // defineExpose({}) -> expose({})887 const callee = (node.expression as CallExpression).callee888 s.overwrite(889 callee.start! + startOffset,890 callee.end! + startOffset,891 'expose'892 )893 }894 }895896 if (node.type === 'VariableDeclaration' && !node.declare) {897 const total = node.declarations.length898 let left = total899 for (let i = 0; i < total; i++) {900 const decl = node.declarations[i]901 if (decl.init) {902 // defineProps / defineEmits903 const isDefineProps =904 processDefineProps(decl.init, decl.id) ||905 processWithDefaults(decl.init, decl.id)906 const isDefineEmits = processDefineEmits(decl.init, decl.id)907 if (isDefineProps || isDefineEmits) {908 if (left === 1) {909 s.remove(node.start! + startOffset, node.end! + startOffset)910 } else {911 let start = decl.start! + startOffset912 let end = decl.end! + startOffset913 if (i === 0) {914 // first one, locate the start of the next915 end = node.declarations[i + 1].start! + startOffset916 } else {917 // not first one, locate the end of the prev918 start = node.declarations[i - 1].end! + startOffset919 }920 s.remove(start, end)921 left--922 }923 }924 }925 }926 }927928 // walk declarations to record declared bindings929 if (930 (node.type === 'VariableDeclaration' ||931 node.type === 'FunctionDeclaration' ||932 node.type === 'ClassDeclaration') &&933 !node.declare934 ) {935 walkDeclaration(node, setupBindings, userImportAlias)936 }937938 // walk statements & named exports / variable declarations for top level939 // await940 if (941 (node.type === 'VariableDeclaration' && !node.declare) ||942 node.type.endsWith('Statement')943 ) {944 const scope: Statement[][] = [scriptSetupAst.body]945 ;(walk as any)(node, {946 enter(child: Node, parent: Node) {947 if (isFunctionType(child)) {948 this.skip()949 }950 if (child.type === 'BlockStatement') {951 scope.push(child.body)952 }953 if (child.type === 'AwaitExpression') {954 error(955 `Vue 2 does not support top level await in <script setup>.`,956 child957 )958 }959 },960 exit(node: Node) {961 if (node.type === 'BlockStatement') scope.pop()962 }963 })964 }965966 if (967 (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||968 node.type === 'ExportAllDeclaration' ||969 node.type === 'ExportDefaultDeclaration'970 ) {971 error(972 `<script setup> cannot contain ES module exports. ` +973 `If you are using a previous version of <script setup>, please ` +974 `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,975 node976 )977 }978979 if (isTS) {980 // runtime enum981 if (node.type === 'TSEnumDeclaration') {982 registerBinding(setupBindings, node.id, BindingTypes.SETUP_CONST)983 }984985 // move all Type declarations to outer scope986 if (987 node.type.startsWith('TS') ||988 (node.type === 'ExportNamedDeclaration' &&989 node.exportKind === 'type') ||990 (node.type === 'VariableDeclaration' && node.declare)991 ) {992 recordType(node, declaredTypes)993 s.move(start, end, 0)994 }995 }996 }997998 // 3. Apply reactivity transform999 // if (1000 // (enableReactivityTransform &&1001 // // normal <script> had ref bindings that maybe used in <script setup>1002 // (refBindings || shouldTransform(scriptSetup.content))) ||1003 // propsDestructureDecl1004 // ) {1005 // const { rootRefs, importedHelpers } = transformAST(1006 // scriptSetupAst,1007 // s,1008 // startOffset,1009 // refBindings,1010 // propsDestructuredBindings1011 // )1012 // refBindings = refBindings ? [...refBindings, ...rootRefs] : rootRefs1013 // for (const h of importedHelpers) {1014 // helperImports.add(h)1015 // }1016 // }10171018 // 4. extract runtime props/emits code from setup context type1019 if (propsTypeDecl) {1020 extractRuntimeProps(propsTypeDecl, typeDeclaredProps, declaredTypes, isProd)1021 }1022 if (emitsTypeDecl) {1023 extractRuntimeEmits(emitsTypeDecl, typeDeclaredEmits)1024 }10251026 // 5. check useOptions args to make sure it doesn't reference setup scope1027 // variables1028 checkInvalidScopeReference(propsRuntimeDecl, DEFINE_PROPS)1029 checkInvalidScopeReference(propsRuntimeDefaults, DEFINE_PROPS)1030 checkInvalidScopeReference(propsDestructureDecl, DEFINE_PROPS)1031 checkInvalidScopeReference(emitsRuntimeDecl, DEFINE_EMITS)10321033 // 6. remove non-script content1034 if (script) {1035 if (startOffset < scriptStartOffset!) {1036 // <script setup> before <script>1037 s.remove(0, startOffset)1038 s.remove(endOffset, scriptStartOffset!)1039 s.remove(scriptEndOffset!, source.length)1040 } else {1041 // <script> before <script setup>1042 s.remove(0, scriptStartOffset!)1043 s.remove(scriptEndOffset!, startOffset)1044 s.remove(endOffset, source.length)1045 }1046 } else {1047 // only <script setup>1048 s.remove(0, startOffset)1049 s.remove(endOffset, source.length)1050 }10511052 // 7. analyze binding metadata1053 if (scriptAst) {1054 Object.assign(bindingMetadata, analyzeScriptBindings(scriptAst.body))1055 }1056 if (propsRuntimeDecl) {1057 for (const key of getObjectOrArrayExpressionKeys(propsRuntimeDecl)) {1058 bindingMetadata[key] = BindingTypes.PROPS1059 }1060 }1061 for (const key in typeDeclaredProps) {1062 bindingMetadata[key] = BindingTypes.PROPS1063 }1064 // props aliases1065 // if (propsDestructureDecl) {1066 // if (propsDestructureRestId) {1067 // bindingMetadata[propsDestructureRestId] =1068 // BindingTypes.SETUP_REACTIVE_CONST1069 // }1070 // for (const key in propsDestructuredBindings) {1071 // const { local } = propsDestructuredBindings[key]1072 // if (local !== key) {1073 // bindingMetadata[local] = BindingTypes.PROPS_ALIASED1074 // ;(bindingMetadata.__propsAliases ||1075 // (bindingMetadata.__propsAliases = {}))[local] = key1076 // }1077 // }1078 // }1079 for (const [key, { isType, imported, source }] of Object.entries(1080 userImports1081 )) {1082 if (isType) continue1083 bindingMetadata[key] =1084 imported === '*' ||1085 (imported === 'default' && source.endsWith('.vue')) ||1086 source === 'vue'1087 ? BindingTypes.SETUP_CONST1088 : BindingTypes.SETUP_MAYBE_REF1089 }1090 for (const key in scriptBindings) {1091 bindingMetadata[key] = scriptBindings[key]1092 }1093 for (const key in setupBindings) {1094 bindingMetadata[key] = setupBindings[key]1095 }1096 // known ref bindings1097 if (refBindings) {1098 for (const key of refBindings) {1099 bindingMetadata[key] = BindingTypes.SETUP_REF1100 }1101 }11021103 // 8. inject `useCssVars` calls1104 if (cssVars.length) {1105 helperImports.add(CSS_VARS_HELPER)1106 s.prependRight(1107 startOffset,1108 `\n${genCssVarsCode(cssVars, bindingMetadata, scopeId, isProd)}\n`1109 )1110 }11111112 // 9. finalize setup() argument signature1113 let args = `__props`1114 if (propsTypeDecl) {1115 // mark as any and only cast on assignment1116 // since the user defined complex types may be incompatible with the1117 // inferred type from generated runtime declarations1118 args += `: any`1119 }1120 // inject user assignment of props1121 // we use a default __props so that template expressions referencing props1122 // can use it directly1123 if (propsIdentifier) {1124 s.prependLeft(1125 startOffset,1126 `\nconst ${propsIdentifier} = __props${1127 propsTypeDecl ? ` as ${genSetupPropsType(propsTypeDecl)}` : ``1128 };\n`1129 )1130 }1131 if (propsDestructureRestId) {1132 s.prependLeft(1133 startOffset,1134 `\nconst ${propsDestructureRestId} = ${helper(1135 `createPropsRestProxy`1136 )}(__props, ${JSON.stringify(Object.keys(propsDestructuredBindings))});\n`1137 )1138 }11391140 const destructureElements = hasDefineExposeCall ? [`expose`] : []1141 if (emitIdentifier) {1142 destructureElements.push(1143 emitIdentifier === `emit` ? `emit` : `emit: ${emitIdentifier}`1144 )1145 }1146 if (destructureElements.length) {1147 args += `, { ${destructureElements.join(', ')} }`1148 if (emitsTypeDecl) {1149 args += `: { emit: (${scriptSetup.content.slice(1150 emitsTypeDecl.start!,1151 emitsTypeDecl.end!1152 )}), expose: any, slots: any, attrs: any }`1153 }1154 }11551156 // 10. generate return statement1157 const allBindings: Record<string, any> = {1158 ...scriptBindings,1159 ...setupBindings1160 }1161 for (const key in userImports) {1162 if (!userImports[key].isType && userImports[key].isUsedInTemplate) {1163 allBindings[key] = true1164 }1165 }1166 // __sfc marker indicates these bindings are compiled from <script setup>1167 // and should not be proxied on `this`1168 const returned = `{ ${__TEST__ ? `` : `__sfc: true,`}${Object.keys(1169 allBindings1170 ).join(', ')} }`11711172 s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)11731174 // 11. finalize default export1175 let runtimeOptions = ``1176 if (!hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {1177 const match = filename.match(/([^/\\]+)\.\w+$/)1178 if (match) {1179 runtimeOptions += `\n __name: '${match[1]}',`1180 }1181 }1182 if (hasInlinedSsrRenderFn) {1183 runtimeOptions += `\n __ssrInlineRender: true,`1184 }1185 if (propsRuntimeDecl) {1186 let declCode = scriptSetup.content1187 .slice(propsRuntimeDecl.start!, propsRuntimeDecl.end!)1188 .trim()1189 if (propsDestructureDecl) {1190 const defaults: string[] = []1191 for (const key in propsDestructuredBindings) {1192 const d = genDestructuredDefaultValue(key)1193 if (d) defaults.push(`${key}: ${d}`)1194 }1195 if (defaults.length) {1196 declCode = `${helper(1197 `mergeDefaults`1198 )}(${declCode}, {\n ${defaults.join(',\n ')}\n})`1199 }1200 }1201 runtimeOptions += `\n props: ${declCode},`1202 } else if (propsTypeDecl) {1203 runtimeOptions += genRuntimeProps(typeDeclaredProps)1204 }1205 if (emitsRuntimeDecl) {1206 runtimeOptions += `\n emits: ${scriptSetup.content1207 .slice(emitsRuntimeDecl.start!, emitsRuntimeDecl.end!)1208 .trim()},`1209 } else if (emitsTypeDecl) {1210 runtimeOptions += genRuntimeEmits(typeDeclaredEmits)1211 }12121213 // wrap setup code with function.1214 if (isTS) {1215 // for TS, make sure the exported type is still valid type with1216 // correct props information1217 // we have to use object spread for types to be merged properly1218 // user's TS setting should compile it down to proper targets1219 // export default defineComponent({ ...__default__, ... })1220 const def = defaultExport ? `\n ...${DEFAULT_VAR},` : ``1221 s.prependLeft(1222 startOffset,1223 `\nexport default /*#__PURE__*/${helper(1224 `defineComponent`1225 )}({${def}${runtimeOptions}\n setup(${args}) {\n`1226 )1227 s.appendRight(endOffset, `})`)1228 } else {1229 if (defaultExport) {1230 // without TS, can't rely on rest spread, so we use Object.assign1231 // export default Object.assign(__default__, { ... })1232 s.prependLeft(1233 startOffset,1234 `\nexport default /*#__PURE__*/Object.assign(${DEFAULT_VAR}, {${runtimeOptions}\n ` +1235 `setup(${args}) {\n`1236 )1237 s.appendRight(endOffset, `})`)1238 } else {1239 s.prependLeft(1240 startOffset,1241 `\nexport default {${runtimeOptions}\n setup(${args}) {\n`1242 )1243 s.appendRight(endOffset, `}`)1244 }1245 }12461247 // 12. finalize Vue helper imports1248 if (helperImports.size > 0) {1249 s.prepend(1250 `import { ${[...helperImports]1251 .map(h => `${h} as _${h}`)1252 .join(', ')} } from 'vue'\n`1253 )1254 }12551256 s.trim()12571258 return {1259 ...scriptSetup,1260 bindings: bindingMetadata,1261 imports: userImports,1262 content: s.toString(),1263 map: genSourceMap1264 ? (s.generateMap({1265 source: filename,1266 hires: true,1267 includeContent: true1268 }) as unknown as RawSourceMap)1269 : undefined,1270 scriptAst: scriptAst?.body,1271 scriptSetupAst: scriptSetupAst?.body1272 }1273}12741275function registerBinding(1276 bindings: Record<string, BindingTypes>,1277 node: Identifier,1278 type: BindingTypes1279) {1280 bindings[node.name] = type1281}12821283function walkDeclaration(1284 node: Declaration,1285 bindings: Record<string, BindingTypes>,1286 userImportAlias: Record<string, string>1287) {1288 if (node.type === 'VariableDeclaration') {1289 const isConst = node.kind === 'const'1290 // export const foo = ...1291 for (const { id, init } of node.declarations) {1292 const isDefineCall = !!(1293 isConst &&1294 isCallOf(1295 init,1296 c => c === DEFINE_PROPS || c === DEFINE_EMITS || c === WITH_DEFAULTS1297 )1298 )1299 if (id.type === 'Identifier') {1300 let bindingType1301 const userReactiveBinding = userImportAlias['reactive'] || 'reactive'1302 if (isCallOf(init, userReactiveBinding)) {1303 // treat reactive() calls as let since it's meant to be mutable1304 bindingType = isConst1305 ? BindingTypes.SETUP_REACTIVE_CONST1306 : BindingTypes.SETUP_LET1307 } else if (1308 // if a declaration is a const literal, we can mark it so that1309 // the generated render fn code doesn't need to unref() it1310 isDefineCall ||1311 (isConst && canNeverBeRef(init!, userReactiveBinding))1312 ) {1313 bindingType = isCallOf(init, DEFINE_PROPS)1314 ? BindingTypes.SETUP_REACTIVE_CONST1315 : BindingTypes.SETUP_CONST1316 } else if (isConst) {1317 if (isCallOf(init, userImportAlias['ref'] || 'ref')) {1318 bindingType = BindingTypes.SETUP_REF1319 } else {1320 bindingType = BindingTypes.SETUP_MAYBE_REF1321 }1322 } else {1323 bindingType = BindingTypes.SETUP_LET1324 }1325 registerBinding(bindings, id, bindingType)1326 } else {1327 if (isCallOf(init, DEFINE_PROPS)) {1328 // skip walking props destructure1329 return1330 }1331 if (id.type === 'ObjectPattern') {1332 walkObjectPattern(id, bindings, isConst, isDefineCall)1333 } else if (id.type === 'ArrayPattern') {1334 walkArrayPattern(id, bindings, isConst, isDefineCall)1335 }1336 }1337 }1338 } else if (1339 node.type === 'TSEnumDeclaration' ||1340 node.type === 'FunctionDeclaration' ||1341 node.type === 'ClassDeclaration'1342 ) {1343 // export function foo() {} / export class Foo {}1344 // export declarations must be named.1345 bindings[node.id!.name] = BindingTypes.SETUP_CONST1346 }1347}13481349function walkObjectPattern(1350 node: ObjectPattern,1351 bindings: Record<string, BindingTypes>,1352 isConst: boolean,1353 isDefineCall = false1354) {1355 for (const p of node.properties) {1356 if (p.type === 'ObjectProperty') {1357 if (p.key.type === 'Identifier' && p.key === p.value) {1358 // shorthand: const { x } = ...1359 const type = isDefineCall1360 ? BindingTypes.SETUP_CONST1361 : isConst1362 ? BindingTypes.SETUP_MAYBE_REF1363 : BindingTypes.SETUP_LET1364 registerBinding(bindings, p.key, type)1365 } else {1366 walkPattern(p.value, bindings, isConst, isDefineCall)1367 }1368 } else {1369 // ...rest1370 // argument can only be identifier when destructuring1371 const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET1372 registerBinding(bindings, p.argument as Identifier, type)1373 }1374 }1375}13761377function walkArrayPattern(1378 node: ArrayPattern,1379 bindings: Record<string, BindingTypes>,1380 isConst: boolean,1381 isDefineCall = false1382) {1383 for (const e of node.elements) {1384 e && walkPattern(e, bindings, isConst, isDefineCall)1385 }1386}13871388function walkPattern(1389 node: Node,1390 bindings: Record<string, BindingTypes>,1391 isConst: boolean,1392 isDefineCall = false1393) {1394 if (node.type === 'Identifier') {1395 const type = isDefineCall1396 ? BindingTypes.SETUP_CONST1397 : isConst1398 ? BindingTypes.SETUP_MAYBE_REF1399 : BindingTypes.SETUP_LET1400 registerBinding(bindings, node, type)1401 } else if (node.type === 'RestElement') {1402 // argument can only be identifier when destructuring1403 const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET1404 registerBinding(bindings, node.argument as Identifier, type)1405 } else if (node.type === 'ObjectPattern') {1406 walkObjectPattern(node, bindings, isConst)1407 } else if (node.type === 'ArrayPattern') {1408 walkArrayPattern(node, bindings, isConst)1409 } else if (node.type === 'AssignmentPattern') {1410 if (node.left.type === 'Identifier') {1411 const type = isDefineCall1412 ? BindingTypes.SETUP_CONST1413 : isConst1414 ? BindingTypes.SETUP_MAYBE_REF1415 : BindingTypes.SETUP_LET1416 registerBinding(bindings, node.left, type)1417 } else {1418 walkPattern(node.left, bindings, isConst)1419 }1420 }1421}14221423interface PropTypeData {1424 key: string1425 type: string[]1426 required: boolean1427}14281429function recordType(node: Node, declaredTypes: Record<string, string[]>) {1430 if (node.type === 'TSInterfaceDeclaration') {1431 declaredTypes[node.id.name] = [`Object`]1432 } else if (node.type === 'TSTypeAliasDeclaration') {1433 declaredTypes[node.id.name] = inferRuntimeType(1434 node.typeAnnotation,1435 declaredTypes1436 )1437 } else if (node.type === 'ExportNamedDeclaration' && node.declaration) {1438 recordType(node.declaration, declaredTypes)1439 }1440}14411442function extractRuntimeProps(1443 node: TSTypeLiteral | TSInterfaceBody,1444 props: Record<string, PropTypeData>,1445 declaredTypes: Record<string, string[]>,1446 isProd: boolean1447) {1448 const members = node.type === 'TSTypeLiteral' ? node.members : node.body1449 for (const m of members) {1450 if (1451 (m.type === 'TSPropertySignature' || m.type === 'TSMethodSignature') &&1452 m.key.type === 'Identifier'1453 ) {1454 let type1455 if (m.type === 'TSMethodSignature') {1456 type = ['Function']1457 } else if (m.typeAnnotation) {1458 type = inferRuntimeType(m.typeAnnotation.typeAnnotation, declaredTypes)1459 }1460 props[m.key.name] = {1461 key: m.key.name,1462 required: !m.optional,1463 type: type || [`null`]1464 }1465 }1466 }1467}14681469function inferRuntimeType(1470 node: TSType,1471 declaredTypes: Record<string, string[]>1472): string[] {1473 switch (node.type) {1474 case 'TSStringKeyword':1475 return ['String']1476 case 'TSNumberKeyword':1477 return ['Number']1478 case 'TSBooleanKeyword':1479 return ['Boolean']1480 case 'TSObjectKeyword':1481 return ['Object']1482 case 'TSTypeLiteral':1483 // TODO (nice to have) generate runtime property validation1484 return ['Object']1485 case 'TSFunctionType':1486 return ['Function']1487 case 'TSArrayType':1488 case 'TSTupleType':1489 // TODO (nice to have) generate runtime element type/length checks1490 return ['Array']14911492 case 'TSLiteralType':1493 switch (node.literal.type) {1494 case 'StringLiteral':1495 return ['String']1496 case 'BooleanLiteral':1497 return ['Boolean']1498 case 'NumericLiteral':1499 case 'BigIntLiteral':1500 return ['Number']1501 default:1502 return [`null`]1503 }15041505 case 'TSTypeReference':1506 if (node.typeName.type === 'Identifier') {1507 if (declaredTypes[node.typeName.name]) {1508 return declaredTypes[node.typeName.name]1509 }1510 switch (node.typeName.name) {1511 case 'Array':1512 case 'Function':1513 case 'Object':1514 case 'Set':1515 case 'Map':1516 case 'WeakSet':1517 case 'WeakMap':1518 case 'Date':1519 case 'Promise':1520 return [node.typeName.name]1521 case 'Record':1522 case 'Partial':1523 case 'Readonly':1524 case 'Pick':1525 case 'Omit':1526 case 'Exclude':1527 case 'Extract':1528 case 'Required':1529 case 'InstanceType':1530 return ['Object']1531 }1532 }1533 return [`null`]15341535 case 'TSParenthesizedType':1536 return inferRuntimeType(node.typeAnnotation, declaredTypes)1537 case 'TSUnionType':1538 return [1539 ...new Set(1540 [].concat(1541 ...(node.types.map(t => inferRuntimeType(t, declaredTypes)) as any)1542 )1543 )1544 ]1545 case 'TSIntersectionType':1546 return ['Object']15471548 case 'TSSymbolKeyword':1549 return ['Symbol']15501551 default:1552 return [`null`] // no runtime check1553 }1554}15551556function toRuntimeTypeString(types: string[]) {1557 return types.length > 1 ? `[${types.join(', ')}]` : types[0]1558}15591560function extractRuntimeEmits(1561 node: TSFunctionType | TSTypeLiteral | TSInterfaceBody,1562 emits: Set<string>1563) {1564 if (node.type === 'TSTypeLiteral' || node.type === 'TSInterfaceBody') {1565 const members = node.type === 'TSTypeLiteral' ? node.members : node.body1566 for (let t of members) {1567 if (t.type === 'TSCallSignatureDeclaration') {1568 extractEventNames(t.parameters[0], emits)1569 }1570 }1571 return1572 } else {1573 extractEventNames(node.parameters[0], emits)1574 }1575}15761577function extractEventNames(1578 eventName: ArrayPattern | Identifier | ObjectPattern | RestElement,1579 emits: Set<string>1580) {1581 if (1582 eventName.type === 'Identifier' &&1583 eventName.typeAnnotation &&1584 eventName.typeAnnotation.type === 'TSTypeAnnotation'1585 ) {1586 const typeNode = eventName.typeAnnotation.typeAnnotation1587 if (typeNode.type === 'TSLiteralType') {1588 if (1589 typeNode.literal.type !== 'UnaryExpression' &&1590 typeNode.literal.type !== 'TemplateLiteral'1591 ) {1592 emits.add(String(typeNode.literal.value))1593 }1594 } else if (typeNode.type === 'TSUnionType') {1595 for (const t of typeNode.types) {1596 if (1597 t.type === 'TSLiteralType' &&1598 t.literal.type !== 'UnaryExpression' &&1599 t.literal.type !== 'TemplateLiteral'1600 ) {1601 emits.add(String(t.literal.value))1602 }1603 }1604 }1605 }1606}16071608function genRuntimeEmits(emits: Set<string>) {1609 return emits.size1610 ? `\n emits: [${Array.from(emits)1611 .map(p => JSON.stringify(p))1612 .join(', ')}],`1613 : ``1614}16151616function isCallOf(1617 node: Node | null | undefined,1618 test: string | ((id: string) => boolean)1619): node is CallExpression {1620 return !!(1621 node &&1622 node.type === 'CallExpression' &&1623 node.callee.type === 'Identifier' &&1624 (typeof test === 'string'1625 ? node.callee.name === test1626 : test(node.callee.name))1627 )1628}16291630function canNeverBeRef(node: Node, userReactiveImport: string): boolean {1631 if (isCallOf(node, userReactiveImport)) {1632 return true1633 }1634 switch (node.type) {1635 case 'UnaryExpression':1636 case 'BinaryExpression':1637 case 'ArrayExpression':1638 case 'ObjectExpression':1639 case 'FunctionExpression':1640 case 'ArrowFunctionExpression':1641 case 'UpdateExpression':1642 case 'ClassExpression':1643 case 'TaggedTemplateExpression':1644 return true1645 case 'SequenceExpression':1646 return canNeverBeRef(1647 node.expressions[node.expressions.length - 1],1648 userReactiveImport1649 )1650 default:1651 if (node.type.endsWith('Literal')) {1652 return true1653 }1654 return false1655 }1656}16571658/**1659 * Analyze bindings in normal `<script>`1660 * Note that `compileScriptSetup` already analyzes bindings as part of its1661 * compilation process so this should only be used on single `<script>` SFCs.1662 */1663function analyzeScriptBindings(ast: Statement[]): BindingMetadata {1664 for (const node of ast) {1665 if (1666 node.type === 'ExportDefaultDeclaration' &&1667 node.declaration.type === 'ObjectExpression'1668 ) {1669 return analyzeBindingsFromOptions(node.declaration)1670 }1671 }1672 return {}1673}16741675function analyzeBindingsFromOptions(node: ObjectExpression): BindingMetadata {1676 const bindings: BindingMetadata = {}1677 // #3270, #32751678 // mark non-script-setup so we don't resolve components/directives from these1679 Object.defineProperty(bindings, '__isScriptSetup', {1680 enumerable: false,1681 value: false1682 })1683 for (const property of node.properties) {1684 if (1685 property.type === 'ObjectProperty' &&1686 !property.computed &&1687 property.key.type === 'Identifier'1688 ) {1689 // props1690 if (property.key.name === 'props') {1691 // props: ['foo']1692 // props: { foo: ... }1693 for (const key of getObjectOrArrayExpressionKeys(property.value)) {1694 bindings[key] = BindingTypes.PROPS1695 }1696 }16971698 // inject1699 else if (property.key.name === 'inject') {1700 // inject: ['foo']1701 // inject: { foo: {} }1702 for (const key of getObjectOrArrayExpressionKeys(property.value)) {1703 bindings[key] = BindingTypes.OPTIONS1704 }1705 }17061707 // computed & methods1708 else if (1709 property.value.type === 'ObjectExpression' &&1710 (property.key.name === 'computed' || property.key.name === 'methods')1711 ) {1712 // methods: { foo() {} }1713 // computed: { foo() {} }1714 for (const key of getObjectExpressionKeys(property.value)) {1715 bindings[key] = BindingTypes.OPTIONS1716 }1717 }1718 }17191720 // setup & data1721 else if (1722 property.type === 'ObjectMethod' &&1723 property.key.type === 'Identifier' &&1724 (property.key.name === 'setup' || property.key.name === 'data')1725 ) {1726 for (const bodyItem of property.body.body) {1727 // setup() {1728 // return {1729 // foo: null1730 // }1731 // }1732 if (1733 bodyItem.type === 'ReturnStatement' &&1734 bodyItem.argument &&1735 bodyItem.argument.type === 'ObjectExpression'1736 ) {1737 for (const key of getObjectExpressionKeys(bodyItem.argument)) {1738 bindings[key] =1739 property.key.name === 'setup'1740 ? BindingTypes.SETUP_MAYBE_REF1741 : BindingTypes.DATA1742 }1743 }1744 }1745 }1746 }17471748 return bindings1749}17501751function getObjectExpressionKeys(node: ObjectExpression): string[] {1752 const keys: string[] = []1753 for (const prop of node.properties) {1754 if (1755 (prop.type === 'ObjectProperty' || prop.type === 'ObjectMethod') &&1756 !prop.computed1757 ) {1758 if (prop.key.type === 'Identifier') {1759 keys.push(prop.key.name)1760 } else if (prop.key.type === 'StringLiteral') {1761 keys.push(prop.key.value)1762 }1763 }1764 }1765 return keys1766}17671768function getArrayExpressionKeys(node: ArrayExpression): string[] {1769 const keys: string[] = []1770 for (const element of node.elements) {1771 if (element && element.type === 'StringLiteral') {1772 keys.push(element.value)1773 }1774 }1775 return keys1776}17771778function getObjectOrArrayExpressionKeys(value: Node): string[] {1779 if (value.type === 'ArrayExpression') {1780 return getArrayExpressionKeys(value)1781 }1782 if (value.type === 'ObjectExpression') {1783 return getObjectExpressionKeys(value)1784 }1785 return []1786}17871788const templateUsageCheckCache = new LRU<string, string>(512)17891790function resolveTemplateUsageCheckString(sfc: SFCDescriptor, isTS: boolean) {1791 const { content } = sfc.template!1792 const cached = templateUsageCheckCache.get(content)1793 if (cached) {1794 return cached1795 }17961797 let code = ''17981799 parseHTML(content, {1800 ...webCompilerOptions,1801 start(tag, attrs) {1802 if (!isBuiltInTag(tag) && !isReservedTag(tag)) {1803 code += `,${camelize(tag)},${capitalize(camelize(tag))}`1804 }1805 for (let i = 0; i < attrs.length; i++) {1806 const { name, value } = attrs[i]1807 if (dirRE.test(name)) {1808 const baseName = onRE.test(name)1809 ? 'on'1810 : slotRE.test(name)1811 ? 'slot'1812 : bindRE.test(name)1813 ? 'bind'1814 : name.replace(dirRE, '')1815 if (!isBuiltInDir(baseName)) {1816 code += `,v${capitalize(camelize(baseName))}`1817 }1818 if (value) {1819 code += `,${processExp(value, isTS, baseName)}`1820 }1821 } else if (name === 'ref') {1822 code += `,${value}`1823 }1824 }1825 },1826 chars(text) {1827 const res = parseText(text)1828 if (res) {1829 code += `,${processExp(res.expression, isTS)}`1830 }1831 }1832 })18331834 code += ';'1835 templateUsageCheckCache.set(content, code)1836 return code1837}18381839const forAliasRE = /([\s\S]*?)\s+(?:in|of)\s+([\s\S]*)/18401841function processExp(exp: string, isTS: boolean, dir?: string): string {1842 if (isTS && / as\s+\w|<.*>|:/.test(exp)) {1843 if (dir === 'slot') {1844 exp = `(${exp})=>{}`1845 } else if (dir === 'on') {1846 exp = `()=>{return ${exp}}`1847 } else if (dir === 'for') {1848 const inMatch = exp.match(forAliasRE)1849 if (inMatch) {1850 const [, LHS, RHS] = inMatch1851 return processExp(`(${LHS})=>{}`, true) + processExp(RHS, true)1852 }1853 }1854 let ret = ''1855 // has potential type cast or generic arguments that uses types1856 const ast = parseExpression(exp, { plugins: ['typescript'] })1857 walkIdentifiers(ast, node => {1858 ret += `,` + node.name1859 })1860 return ret1861 }1862 return stripStrings(exp)1863}18641865function stripStrings(exp: string) {1866 return exp1867 .replace(/'[^']*'|"[^"]*"/g, '')1868 .replace(/`[^`]+`/g, stripTemplateString)1869}18701871function stripTemplateString(str: string): string {1872 const interpMatch = str.match(/\${[^}]+}/g)1873 if (interpMatch) {1874 return interpMatch.map(m => m.slice(2, -1)).join(',')1875 }1876 return ''1877}18781879function isImportUsed(1880 local: string,1881 sfc: SFCDescriptor,1882 isTS: boolean1883): boolean {1884 return new RegExp(1885 // #4274 escape $ since it's a special char in regex1886 // (and is the only regex special char that is valid in identifiers)1887 `[^\\w$_]${local.replace(/\$/g, '\\$')}[^\\w$_]`1888 ).test(resolveTemplateUsageCheckString(sfc, isTS))1889}18901891/**1892 * Note: this comparison assumes the prev/next script are already identical,1893 * and only checks the special case where <script setup> unused import1894 * pruning result changes due to template changes.1895 */1896export function hmrShouldReload(1897 prevImports: Record<string, ImportBinding>,1898 next: SFCDescriptor1899): boolean {1900 if (!next.scriptSetup) {1901 return false1902 }19031904 const isTS = next.scriptSetup.lang === 'ts' || next.scriptSetup.lang === 'tsx'1905 // for each previous import, check if its used status remain the same based on1906 // the next descriptor's template1907 for (const key in prevImports) {1908 // if an import was previous unused, but now is used, we need to force1909 // reload so that the script now includes that import.1910 if (!prevImports[key].isUsedInTemplate && isImportUsed(key, next, isTS)) {1911 return true1912 }1913 }19141915 return false1916}
Findings
✓ No findings reported for this file.