compileScript.ts 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357
  1. import {
  2. BindingTypes,
  3. UNREF,
  4. isFunctionType,
  5. unwrapTSNode,
  6. walkIdentifiers,
  7. } from '@vue/compiler-dom'
  8. import {
  9. DEFAULT_FILENAME,
  10. type SFCDescriptor,
  11. type SFCScriptBlock,
  12. } from './parse'
  13. import type { ParserPlugin } from '@babel/parser'
  14. import { generateCodeFrame } from '@vue/shared'
  15. import type {
  16. ArrayPattern,
  17. CallExpression,
  18. Declaration,
  19. ExportSpecifier,
  20. Identifier,
  21. LVal,
  22. Node,
  23. ObjectPattern,
  24. Statement,
  25. } from '@babel/types'
  26. import { walk } from 'estree-walker'
  27. import {
  28. type RawSourceMap,
  29. SourceMapConsumer,
  30. SourceMapGenerator,
  31. } from 'source-map-js'
  32. import {
  33. normalScriptDefaultVar,
  34. processNormalScript,
  35. } from './script/normalScript'
  36. import { CSS_VARS_HELPER, genCssVarsCode } from './style/cssVars'
  37. import {
  38. type SFCTemplateCompileOptions,
  39. compileTemplate,
  40. } from './compileTemplate'
  41. import { warnOnce } from './warn'
  42. import { transformDestructuredProps } from './script/definePropsDestructure'
  43. import { ScriptCompileContext } from './script/context'
  44. import {
  45. DEFINE_PROPS,
  46. WITH_DEFAULTS,
  47. genRuntimeProps,
  48. processDefineProps,
  49. } from './script/defineProps'
  50. import {
  51. DEFINE_EMITS,
  52. genRuntimeEmits,
  53. processDefineEmits,
  54. } from './script/defineEmits'
  55. import { DEFINE_EXPOSE, processDefineExpose } from './script/defineExpose'
  56. import { DEFINE_OPTIONS, processDefineOptions } from './script/defineOptions'
  57. import { DEFINE_SLOTS, processDefineSlots } from './script/defineSlots'
  58. import { DEFINE_MODEL, processDefineModel } from './script/defineModel'
  59. import {
  60. getImportedName,
  61. isCallOf,
  62. isJS,
  63. isLiteralNode,
  64. isTS,
  65. } from './script/utils'
  66. import { analyzeScriptBindings } from './script/analyzeScriptBindings'
  67. import { isImportUsed } from './script/importUsageCheck'
  68. import { processAwait } from './script/topLevelAwait'
  69. export interface SFCScriptCompileOptions {
  70. /**
  71. * Scope ID for prefixing injected CSS variables.
  72. * This must be consistent with the `id` passed to `compileStyle`.
  73. */
  74. id: string
  75. /**
  76. * Production mode. Used to determine whether to generate hashed CSS variables
  77. */
  78. isProd?: boolean
  79. /**
  80. * Enable/disable source map. Defaults to true.
  81. */
  82. sourceMap?: boolean
  83. /**
  84. * https://babeljs.io/docs/en/babel-parser#plugins
  85. */
  86. babelParserPlugins?: ParserPlugin[]
  87. /**
  88. * A list of files to parse for global types to be made available for type
  89. * resolving in SFC macros. The list must be fully resolved file system paths.
  90. */
  91. globalTypeFiles?: string[]
  92. /**
  93. * Compile the template and inline the resulting render function
  94. * directly inside setup().
  95. * - Only affects `<script setup>`
  96. * - This should only be used in production because it prevents the template
  97. * from being hot-reloaded separately from component state.
  98. */
  99. inlineTemplate?: boolean
  100. /**
  101. * Generate the final component as a variable instead of default export.
  102. * This is useful in e.g. @vitejs/plugin-vue where the script needs to be
  103. * placed inside the main module.
  104. */
  105. genDefaultAs?: string
  106. /**
  107. * Options for template compilation when inlining. Note these are options that
  108. * would normally be passed to `compiler-sfc`'s own `compileTemplate()`, not
  109. * options passed to `compiler-dom`.
  110. */
  111. templateOptions?: Partial<SFCTemplateCompileOptions>
  112. /**
  113. * Hoist <script setup> static constants.
  114. * - Only enables when one `<script setup>` exists.
  115. * @default true
  116. */
  117. hoistStatic?: boolean
  118. /**
  119. * Set to `false` to disable reactive destructure for `defineProps` (pre-3.5
  120. * behavior), or set to `'error'` to throw hard error on props destructures.
  121. * @default true
  122. */
  123. propsDestructure?: boolean | 'error'
  124. /**
  125. * File system access methods to be used when resolving types
  126. * imported in SFC macros. Defaults to ts.sys in Node.js, can be overwritten
  127. * to use a virtual file system for use in browsers (e.g. in REPLs)
  128. */
  129. fs?: {
  130. fileExists(file: string): boolean
  131. readFile(file: string): string | undefined
  132. realpath?(file: string): string
  133. }
  134. /**
  135. * Transform Vue SFCs into custom elements.
  136. */
  137. customElement?: boolean | ((filename: string) => boolean)
  138. }
  139. export interface ImportBinding {
  140. isType: boolean
  141. imported: string
  142. local: string
  143. source: string
  144. isFromSetup: boolean
  145. isUsedInTemplate: boolean
  146. }
  147. const MACROS = [
  148. DEFINE_PROPS,
  149. DEFINE_EMITS,
  150. DEFINE_EXPOSE,
  151. DEFINE_OPTIONS,
  152. DEFINE_SLOTS,
  153. DEFINE_MODEL,
  154. WITH_DEFAULTS,
  155. ]
  156. /**
  157. * Compile `<script setup>`
  158. * It requires the whole SFC descriptor because we need to handle and merge
  159. * normal `<script>` + `<script setup>` if both are present.
  160. */
  161. export function compileScript(
  162. sfc: SFCDescriptor,
  163. options: SFCScriptCompileOptions,
  164. ): SFCScriptBlock {
  165. if (!options.id) {
  166. warnOnce(
  167. `compileScript now requires passing the \`id\` option.\n` +
  168. `Upgrade your vite or vue-loader version for compatibility with ` +
  169. `the latest experimental proposals.`,
  170. )
  171. }
  172. const { script, scriptSetup, source, filename } = sfc
  173. const hoistStatic = options.hoistStatic !== false && !script
  174. const scopeId = options.id ? options.id.replace(/^data-v-/, '') : ''
  175. const scriptLang = script && script.lang
  176. const scriptSetupLang = scriptSetup && scriptSetup.lang
  177. const isJSOrTS =
  178. isJS(scriptLang, scriptSetupLang) || isTS(scriptLang, scriptSetupLang)
  179. if (script && scriptSetup && scriptLang !== scriptSetupLang) {
  180. throw new Error(
  181. `[@vue/compiler-sfc] <script> and <script setup> must have the same ` +
  182. `language type.`,
  183. )
  184. }
  185. if (!scriptSetup) {
  186. if (!script) {
  187. throw new Error(`[@vue/compiler-sfc] SFC contains no <script> tags.`)
  188. }
  189. // normal <script> only
  190. if (script.lang && !isJSOrTS) {
  191. // do not process non js/ts script blocks
  192. return script
  193. }
  194. const ctx = new ScriptCompileContext(sfc, options)
  195. return processNormalScript(ctx, scopeId)
  196. }
  197. if (scriptSetupLang && !isJSOrTS) {
  198. // do not process non js/ts script blocks
  199. return scriptSetup
  200. }
  201. const ctx = new ScriptCompileContext(sfc, options)
  202. // metadata that needs to be returned
  203. // const ctx.bindingMetadata: BindingMetadata = {}
  204. const scriptBindings: Record<string, BindingTypes> = Object.create(null)
  205. const setupBindings: Record<string, BindingTypes> = Object.create(null)
  206. let defaultExport: Node | undefined
  207. let hasAwait = false
  208. let hasInlinedSsrRenderFn = false
  209. // string offsets
  210. const startOffset = ctx.startOffset!
  211. const endOffset = ctx.endOffset!
  212. const scriptStartOffset = script && script.loc.start.offset
  213. const scriptEndOffset = script && script.loc.end.offset
  214. function hoistNode(node: Statement) {
  215. const start = node.start! + startOffset
  216. let end = node.end! + startOffset
  217. // locate comment
  218. if (node.trailingComments && node.trailingComments.length > 0) {
  219. const lastCommentNode =
  220. node.trailingComments[node.trailingComments.length - 1]
  221. end = lastCommentNode.end! + startOffset
  222. }
  223. // locate the end of whitespace between this statement and the next
  224. while (end <= source.length) {
  225. if (!/\s/.test(source.charAt(end))) {
  226. break
  227. }
  228. end++
  229. }
  230. ctx.s.move(start, end, 0)
  231. }
  232. function registerUserImport(
  233. source: string,
  234. local: string,
  235. imported: string,
  236. isType: boolean,
  237. isFromSetup: boolean,
  238. needTemplateUsageCheck: boolean,
  239. ) {
  240. // template usage check is only needed in non-inline mode, so we can skip
  241. // the work if inlineTemplate is true.
  242. let isUsedInTemplate = needTemplateUsageCheck
  243. if (
  244. needTemplateUsageCheck &&
  245. ctx.isTS &&
  246. sfc.template &&
  247. !sfc.template.src &&
  248. !sfc.template.lang
  249. ) {
  250. isUsedInTemplate = isImportUsed(local, sfc)
  251. }
  252. ctx.userImports[local] = {
  253. isType,
  254. imported,
  255. local,
  256. source,
  257. isFromSetup,
  258. isUsedInTemplate,
  259. }
  260. }
  261. function checkInvalidScopeReference(node: Node | undefined, method: string) {
  262. if (!node) return
  263. walkIdentifiers(node, id => {
  264. const binding = setupBindings[id.name]
  265. if (binding && binding !== BindingTypes.LITERAL_CONST) {
  266. ctx.error(
  267. `\`${method}()\` in <script setup> cannot reference locally ` +
  268. `declared variables because it will be hoisted outside of the ` +
  269. `setup() function. If your component options require initialization ` +
  270. `in the module scope, use a separate normal <script> to export ` +
  271. `the options instead.`,
  272. id,
  273. )
  274. }
  275. })
  276. }
  277. const scriptAst = ctx.scriptAst
  278. const scriptSetupAst = ctx.scriptSetupAst!
  279. // 1.1 walk import declarations of <script>
  280. if (scriptAst) {
  281. for (const node of scriptAst.body) {
  282. if (node.type === 'ImportDeclaration') {
  283. // record imports for dedupe
  284. for (const specifier of node.specifiers) {
  285. const imported = getImportedName(specifier)
  286. registerUserImport(
  287. node.source.value,
  288. specifier.local.name,
  289. imported,
  290. node.importKind === 'type' ||
  291. (specifier.type === 'ImportSpecifier' &&
  292. specifier.importKind === 'type'),
  293. false,
  294. !options.inlineTemplate,
  295. )
  296. }
  297. }
  298. }
  299. }
  300. // 1.2 walk import declarations of <script setup>
  301. for (const node of scriptSetupAst.body) {
  302. if (node.type === 'ImportDeclaration') {
  303. // import declarations are moved to top
  304. hoistNode(node)
  305. // dedupe imports
  306. let removed = 0
  307. const removeSpecifier = (i: number) => {
  308. const removeLeft = i > removed
  309. removed++
  310. const current = node.specifiers[i]
  311. const next = node.specifiers[i + 1]
  312. ctx.s.remove(
  313. removeLeft
  314. ? node.specifiers[i - 1].end! + startOffset
  315. : current.start! + startOffset,
  316. next && !removeLeft
  317. ? next.start! + startOffset
  318. : current.end! + startOffset,
  319. )
  320. }
  321. for (let i = 0; i < node.specifiers.length; i++) {
  322. const specifier = node.specifiers[i]
  323. const local = specifier.local.name
  324. const imported = getImportedName(specifier)
  325. const source = node.source.value
  326. const existing = ctx.userImports[local]
  327. if (source === 'vue' && MACROS.includes(imported)) {
  328. if (local === imported) {
  329. warnOnce(
  330. `\`${imported}\` is a compiler macro and no longer needs to be imported.`,
  331. )
  332. } else {
  333. ctx.error(
  334. `\`${imported}\` is a compiler macro and cannot be aliased to ` +
  335. `a different name.`,
  336. specifier,
  337. )
  338. }
  339. removeSpecifier(i)
  340. } else if (existing) {
  341. if (existing.source === source && existing.imported === imported) {
  342. // already imported in <script setup>, dedupe
  343. removeSpecifier(i)
  344. } else {
  345. ctx.error(
  346. `different imports aliased to same local name.`,
  347. specifier,
  348. )
  349. }
  350. } else {
  351. registerUserImport(
  352. source,
  353. local,
  354. imported,
  355. node.importKind === 'type' ||
  356. (specifier.type === 'ImportSpecifier' &&
  357. specifier.importKind === 'type'),
  358. true,
  359. !options.inlineTemplate,
  360. )
  361. }
  362. }
  363. if (node.specifiers.length && removed === node.specifiers.length) {
  364. ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
  365. }
  366. }
  367. }
  368. // 1.3 resolve possible user import alias of `ref` and `reactive`
  369. const vueImportAliases: Record<string, string> = {}
  370. for (const key in ctx.userImports) {
  371. const { source, imported, local } = ctx.userImports[key]
  372. if (source === 'vue') vueImportAliases[imported] = local
  373. }
  374. // 2.1 process normal <script> body
  375. if (script && scriptAst) {
  376. for (const node of scriptAst.body) {
  377. if (node.type === 'ExportDefaultDeclaration') {
  378. // export default
  379. defaultExport = node
  380. // check if user has manually specified `name` or 'render` option in
  381. // export default
  382. // if has name, skip name inference
  383. // if has render and no template, generate return object instead of
  384. // empty render function (#4980)
  385. let optionProperties
  386. if (defaultExport.declaration.type === 'ObjectExpression') {
  387. optionProperties = defaultExport.declaration.properties
  388. } else if (
  389. defaultExport.declaration.type === 'CallExpression' &&
  390. defaultExport.declaration.arguments[0] &&
  391. defaultExport.declaration.arguments[0].type === 'ObjectExpression'
  392. ) {
  393. optionProperties = defaultExport.declaration.arguments[0].properties
  394. }
  395. if (optionProperties) {
  396. for (const p of optionProperties) {
  397. if (
  398. p.type === 'ObjectProperty' &&
  399. p.key.type === 'Identifier' &&
  400. p.key.name === 'name'
  401. ) {
  402. ctx.hasDefaultExportName = true
  403. }
  404. if (
  405. (p.type === 'ObjectMethod' || p.type === 'ObjectProperty') &&
  406. p.key.type === 'Identifier' &&
  407. p.key.name === 'render'
  408. ) {
  409. // TODO warn when we provide a better way to do it?
  410. ctx.hasDefaultExportRender = true
  411. }
  412. }
  413. }
  414. // export default { ... } --> const __default__ = { ... }
  415. const start = node.start! + scriptStartOffset!
  416. const end = node.declaration.start! + scriptStartOffset!
  417. ctx.s.overwrite(start, end, `const ${normalScriptDefaultVar} = `)
  418. } else if (node.type === 'ExportNamedDeclaration') {
  419. const defaultSpecifier = node.specifiers.find(
  420. s =>
  421. s.exported.type === 'Identifier' && s.exported.name === 'default',
  422. ) as ExportSpecifier
  423. if (defaultSpecifier) {
  424. defaultExport = node
  425. // 1. remove specifier
  426. if (node.specifiers.length > 1) {
  427. ctx.s.remove(
  428. defaultSpecifier.start! + scriptStartOffset!,
  429. defaultSpecifier.end! + scriptStartOffset!,
  430. )
  431. } else {
  432. ctx.s.remove(
  433. node.start! + scriptStartOffset!,
  434. node.end! + scriptStartOffset!,
  435. )
  436. }
  437. if (node.source) {
  438. // export { x as default } from './x'
  439. // rewrite to `import { x as __default__ } from './x'` and
  440. // add to top
  441. ctx.s.prepend(
  442. `import { ${defaultSpecifier.local.name} as ${normalScriptDefaultVar} } from '${node.source.value}'\n`,
  443. )
  444. } else {
  445. // export { x as default }
  446. // rewrite to `const __default__ = x` and move to end
  447. ctx.s.appendLeft(
  448. scriptEndOffset!,
  449. `\nconst ${normalScriptDefaultVar} = ${defaultSpecifier.local.name}\n`,
  450. )
  451. }
  452. }
  453. if (node.declaration) {
  454. walkDeclaration(
  455. 'script',
  456. node.declaration,
  457. scriptBindings,
  458. vueImportAliases,
  459. hoistStatic,
  460. )
  461. }
  462. } else if (
  463. (node.type === 'VariableDeclaration' ||
  464. node.type === 'FunctionDeclaration' ||
  465. node.type === 'ClassDeclaration' ||
  466. node.type === 'TSEnumDeclaration') &&
  467. !node.declare
  468. ) {
  469. walkDeclaration(
  470. 'script',
  471. node,
  472. scriptBindings,
  473. vueImportAliases,
  474. hoistStatic,
  475. )
  476. }
  477. }
  478. // <script> after <script setup>
  479. // we need to move the block up so that `const __default__` is
  480. // declared before being used in the actual component definition
  481. if (scriptStartOffset! > startOffset) {
  482. // if content doesn't end with newline, add one
  483. if (!/\n$/.test(script.content.trim())) {
  484. ctx.s.appendLeft(scriptEndOffset!, `\n`)
  485. }
  486. ctx.s.move(scriptStartOffset!, scriptEndOffset!, 0)
  487. }
  488. }
  489. // 2.2 process <script setup> body
  490. for (const node of scriptSetupAst.body) {
  491. if (node.type === 'ExpressionStatement') {
  492. const expr = unwrapTSNode(node.expression)
  493. // process `defineProps` and `defineEmit(s)` calls
  494. if (
  495. processDefineProps(ctx, expr) ||
  496. processDefineEmits(ctx, expr) ||
  497. processDefineOptions(ctx, expr) ||
  498. processDefineSlots(ctx, expr)
  499. ) {
  500. ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
  501. } else if (processDefineExpose(ctx, expr)) {
  502. // defineExpose({}) -> expose({})
  503. const callee = (expr as CallExpression).callee
  504. ctx.s.overwrite(
  505. callee.start! + startOffset,
  506. callee.end! + startOffset,
  507. '__expose',
  508. )
  509. } else {
  510. processDefineModel(ctx, expr)
  511. }
  512. }
  513. if (node.type === 'VariableDeclaration' && !node.declare) {
  514. const total = node.declarations.length
  515. let left = total
  516. let lastNonRemoved: number | undefined
  517. for (let i = 0; i < total; i++) {
  518. const decl = node.declarations[i]
  519. const init = decl.init && unwrapTSNode(decl.init)
  520. if (init) {
  521. if (processDefineOptions(ctx, init)) {
  522. ctx.error(
  523. `${DEFINE_OPTIONS}() has no returning value, it cannot be assigned.`,
  524. node,
  525. )
  526. }
  527. // defineProps
  528. const isDefineProps = processDefineProps(ctx, init, decl.id as LVal)
  529. if (ctx.propsDestructureRestId) {
  530. setupBindings[ctx.propsDestructureRestId] =
  531. BindingTypes.SETUP_REACTIVE_CONST
  532. }
  533. // defineEmits
  534. const isDefineEmits =
  535. !isDefineProps && processDefineEmits(ctx, init, decl.id as LVal)
  536. !isDefineEmits &&
  537. (processDefineSlots(ctx, init, decl.id as LVal) ||
  538. processDefineModel(ctx, init, decl.id as LVal))
  539. if (
  540. isDefineProps &&
  541. !ctx.propsDestructureRestId &&
  542. ctx.propsDestructureDecl
  543. ) {
  544. if (left === 1) {
  545. ctx.s.remove(node.start! + startOffset, node.end! + startOffset)
  546. } else {
  547. let start = decl.start! + startOffset
  548. let end = decl.end! + startOffset
  549. if (i === total - 1) {
  550. // last one, locate the end of the last one that is not removed
  551. // if we arrive at this branch, there must have been a
  552. // non-removed decl before us, so lastNonRemoved is non-null.
  553. start = node.declarations[lastNonRemoved!].end! + startOffset
  554. } else {
  555. // not the last one, locate the start of the next
  556. end = node.declarations[i + 1].start! + startOffset
  557. }
  558. ctx.s.remove(start, end)
  559. left--
  560. }
  561. } else if (isDefineEmits) {
  562. ctx.s.overwrite(
  563. startOffset + init.start!,
  564. startOffset + init.end!,
  565. '__emit',
  566. )
  567. } else {
  568. lastNonRemoved = i
  569. }
  570. }
  571. }
  572. }
  573. let isAllLiteral = false
  574. // walk declarations to record declared bindings
  575. if (
  576. (node.type === 'VariableDeclaration' ||
  577. node.type === 'FunctionDeclaration' ||
  578. node.type === 'ClassDeclaration' ||
  579. node.type === 'TSEnumDeclaration') &&
  580. !node.declare
  581. ) {
  582. isAllLiteral = walkDeclaration(
  583. 'scriptSetup',
  584. node,
  585. setupBindings,
  586. vueImportAliases,
  587. hoistStatic,
  588. !!ctx.propsDestructureDecl,
  589. )
  590. }
  591. // hoist literal constants
  592. if (hoistStatic && isAllLiteral) {
  593. hoistNode(node)
  594. }
  595. // walk statements & named exports / variable declarations for top level
  596. // await
  597. if (
  598. (node.type === 'VariableDeclaration' && !node.declare) ||
  599. node.type.endsWith('Statement')
  600. ) {
  601. const scope: Statement[][] = [scriptSetupAst.body]
  602. walk(node, {
  603. enter(child: Node, parent: Node | null) {
  604. if (isFunctionType(child)) {
  605. this.skip()
  606. }
  607. if (child.type === 'BlockStatement') {
  608. scope.push(child.body)
  609. }
  610. if (child.type === 'AwaitExpression') {
  611. hasAwait = true
  612. // if the await expression is an expression statement and
  613. // - is in the root scope
  614. // - or is not the first statement in a nested block scope
  615. // then it needs a semicolon before the generated code.
  616. const currentScope = scope[scope.length - 1]
  617. const needsSemi = currentScope.some((n, i) => {
  618. return (
  619. (scope.length === 1 || i > 0) &&
  620. n.type === 'ExpressionStatement' &&
  621. n.start === child.start
  622. )
  623. })
  624. processAwait(
  625. ctx,
  626. child,
  627. needsSemi,
  628. parent!.type === 'ExpressionStatement',
  629. )
  630. }
  631. },
  632. exit(node: Node) {
  633. if (node.type === 'BlockStatement') scope.pop()
  634. },
  635. })
  636. }
  637. if (
  638. (node.type === 'ExportNamedDeclaration' && node.exportKind !== 'type') ||
  639. node.type === 'ExportAllDeclaration' ||
  640. node.type === 'ExportDefaultDeclaration'
  641. ) {
  642. ctx.error(
  643. `<script setup> cannot contain ES module exports. ` +
  644. `If you are using a previous version of <script setup>, please ` +
  645. `consult the updated RFC at https://github.com/vuejs/rfcs/pull/227.`,
  646. node,
  647. )
  648. }
  649. if (ctx.isTS) {
  650. // move all Type declarations to outer scope
  651. if (
  652. node.type.startsWith('TS') ||
  653. (node.type === 'ExportNamedDeclaration' &&
  654. node.exportKind === 'type') ||
  655. (node.type === 'VariableDeclaration' && node.declare)
  656. ) {
  657. if (node.type !== 'TSEnumDeclaration') {
  658. hoistNode(node)
  659. }
  660. }
  661. }
  662. }
  663. // 3 props destructure transform
  664. if (ctx.propsDestructureDecl) {
  665. transformDestructuredProps(ctx, vueImportAliases)
  666. }
  667. // 4. check macro args to make sure it doesn't reference setup scope
  668. // variables
  669. checkInvalidScopeReference(ctx.propsRuntimeDecl, DEFINE_PROPS)
  670. checkInvalidScopeReference(ctx.propsRuntimeDefaults, DEFINE_PROPS)
  671. checkInvalidScopeReference(ctx.propsDestructureDecl, DEFINE_PROPS)
  672. checkInvalidScopeReference(ctx.emitsRuntimeDecl, DEFINE_EMITS)
  673. checkInvalidScopeReference(ctx.optionsRuntimeDecl, DEFINE_OPTIONS)
  674. for (const { runtimeOptionNodes } of Object.values(ctx.modelDecls)) {
  675. for (const node of runtimeOptionNodes) {
  676. checkInvalidScopeReference(node, DEFINE_MODEL)
  677. }
  678. }
  679. // 5. remove non-script content
  680. if (script) {
  681. if (startOffset < scriptStartOffset!) {
  682. // <script setup> before <script>
  683. ctx.s.remove(0, startOffset)
  684. ctx.s.remove(endOffset, scriptStartOffset!)
  685. ctx.s.remove(scriptEndOffset!, source.length)
  686. } else {
  687. // <script> before <script setup>
  688. ctx.s.remove(0, scriptStartOffset!)
  689. ctx.s.remove(scriptEndOffset!, startOffset)
  690. ctx.s.remove(endOffset, source.length)
  691. }
  692. } else {
  693. // only <script setup>
  694. ctx.s.remove(0, startOffset)
  695. ctx.s.remove(endOffset, source.length)
  696. }
  697. // 6. analyze binding metadata
  698. // `defineProps` & `defineModel` also register props bindings
  699. if (scriptAst) {
  700. Object.assign(ctx.bindingMetadata, analyzeScriptBindings(scriptAst.body))
  701. }
  702. for (const [key, { isType, imported, source }] of Object.entries(
  703. ctx.userImports,
  704. )) {
  705. if (isType) continue
  706. ctx.bindingMetadata[key] =
  707. imported === '*' ||
  708. (imported === 'default' && source.endsWith('.vue')) ||
  709. source === 'vue'
  710. ? BindingTypes.SETUP_CONST
  711. : BindingTypes.SETUP_MAYBE_REF
  712. }
  713. for (const key in scriptBindings) {
  714. ctx.bindingMetadata[key] = scriptBindings[key]
  715. }
  716. for (const key in setupBindings) {
  717. ctx.bindingMetadata[key] = setupBindings[key]
  718. }
  719. // 7. inject `useCssVars` calls
  720. if (
  721. sfc.cssVars.length &&
  722. // no need to do this when targeting SSR
  723. !options.templateOptions?.ssr
  724. ) {
  725. ctx.helperImports.add(CSS_VARS_HELPER)
  726. ctx.helperImports.add('unref')
  727. ctx.s.prependLeft(
  728. startOffset,
  729. `\n${genCssVarsCode(
  730. sfc.cssVars,
  731. ctx.bindingMetadata,
  732. scopeId,
  733. !!options.isProd,
  734. )}\n`,
  735. )
  736. }
  737. // 8. finalize setup() argument signature
  738. let args = `__props`
  739. if (ctx.propsTypeDecl) {
  740. // mark as any and only cast on assignment
  741. // since the user defined complex types may be incompatible with the
  742. // inferred type from generated runtime declarations
  743. args += `: any`
  744. }
  745. // inject user assignment of props
  746. // we use a default __props so that template expressions referencing props
  747. // can use it directly
  748. if (ctx.propsDecl) {
  749. if (ctx.propsDestructureRestId) {
  750. ctx.s.overwrite(
  751. startOffset + ctx.propsCall!.start!,
  752. startOffset + ctx.propsCall!.end!,
  753. `${ctx.helper(`createPropsRestProxy`)}(__props, ${JSON.stringify(
  754. Object.keys(ctx.propsDestructuredBindings),
  755. )})`,
  756. )
  757. ctx.s.overwrite(
  758. startOffset + ctx.propsDestructureDecl!.start!,
  759. startOffset + ctx.propsDestructureDecl!.end!,
  760. ctx.propsDestructureRestId,
  761. )
  762. } else if (!ctx.propsDestructureDecl) {
  763. ctx.s.overwrite(
  764. startOffset + ctx.propsCall!.start!,
  765. startOffset + ctx.propsCall!.end!,
  766. '__props',
  767. )
  768. }
  769. }
  770. // inject temp variables for async context preservation
  771. if (hasAwait) {
  772. const any = ctx.isTS ? `: any` : ``
  773. ctx.s.prependLeft(startOffset, `\nlet __temp${any}, __restore${any}\n`)
  774. }
  775. const destructureElements =
  776. ctx.hasDefineExposeCall || !options.inlineTemplate
  777. ? [`expose: __expose`]
  778. : []
  779. if (ctx.emitDecl) {
  780. destructureElements.push(`emit: __emit`)
  781. }
  782. if (destructureElements.length) {
  783. args += `, { ${destructureElements.join(', ')} }`
  784. }
  785. let templateMap
  786. // 9. generate return statement
  787. let returned
  788. if (
  789. !options.inlineTemplate ||
  790. (!sfc.template && ctx.hasDefaultExportRender)
  791. ) {
  792. // non-inline mode, or has manual render in normal <script>
  793. // return bindings from script and script setup
  794. const allBindings: Record<string, any> = {
  795. ...scriptBindings,
  796. ...setupBindings,
  797. }
  798. for (const key in ctx.userImports) {
  799. if (
  800. !ctx.userImports[key].isType &&
  801. ctx.userImports[key].isUsedInTemplate
  802. ) {
  803. allBindings[key] = true
  804. }
  805. }
  806. returned = `{ `
  807. for (const key in allBindings) {
  808. if (
  809. allBindings[key] === true &&
  810. ctx.userImports[key].source !== 'vue' &&
  811. !ctx.userImports[key].source.endsWith('.vue')
  812. ) {
  813. // generate getter for import bindings
  814. // skip vue imports since we know they will never change
  815. returned += `get ${key}() { return ${key} }, `
  816. } else if (ctx.bindingMetadata[key] === BindingTypes.SETUP_LET) {
  817. // local let binding, also add setter
  818. const setArg = key === 'v' ? `_v` : `v`
  819. returned +=
  820. `get ${key}() { return ${key} }, ` +
  821. `set ${key}(${setArg}) { ${key} = ${setArg} }, `
  822. } else {
  823. returned += `${key}, `
  824. }
  825. }
  826. returned = returned.replace(/, $/, '') + ` }`
  827. } else {
  828. // inline mode
  829. if (sfc.template && !sfc.template.src) {
  830. if (options.templateOptions && options.templateOptions.ssr) {
  831. hasInlinedSsrRenderFn = true
  832. }
  833. // inline render function mode - we are going to compile the template and
  834. // inline it right here
  835. const { code, ast, preamble, tips, errors, map } = compileTemplate({
  836. filename,
  837. ast: sfc.template.ast,
  838. source: sfc.template.content,
  839. inMap: sfc.template.map,
  840. ...options.templateOptions,
  841. id: scopeId,
  842. scoped: sfc.styles.some(s => s.scoped),
  843. isProd: options.isProd,
  844. ssrCssVars: sfc.cssVars,
  845. compilerOptions: {
  846. ...(options.templateOptions &&
  847. options.templateOptions.compilerOptions),
  848. inline: true,
  849. isTS: ctx.isTS,
  850. bindingMetadata: ctx.bindingMetadata,
  851. },
  852. })
  853. templateMap = map
  854. if (tips.length) {
  855. tips.forEach(warnOnce)
  856. }
  857. const err = errors[0]
  858. if (typeof err === 'string') {
  859. throw new Error(err)
  860. } else if (err) {
  861. if (err.loc) {
  862. err.message +=
  863. `\n\n` +
  864. sfc.filename +
  865. '\n' +
  866. generateCodeFrame(
  867. source,
  868. err.loc.start.offset,
  869. err.loc.end.offset,
  870. ) +
  871. `\n`
  872. }
  873. throw err
  874. }
  875. if (preamble) {
  876. ctx.s.prepend(preamble)
  877. }
  878. // avoid duplicated unref import
  879. // as this may get injected by the render function preamble OR the
  880. // css vars codegen
  881. if (ast && ast.helpers.has(UNREF)) {
  882. ctx.helperImports.delete('unref')
  883. }
  884. returned = code
  885. } else {
  886. returned = `() => {}`
  887. }
  888. }
  889. if (!options.inlineTemplate && !__TEST__) {
  890. // in non-inline mode, the `__isScriptSetup: true` flag is used by
  891. // componentPublicInstance proxy to allow properties that start with $ or _
  892. ctx.s.appendRight(
  893. endOffset,
  894. `\nconst __returned__ = ${returned}\n` +
  895. `Object.defineProperty(__returned__, '__isScriptSetup', { enumerable: false, value: true })\n` +
  896. `return __returned__` +
  897. `\n}\n\n`,
  898. )
  899. } else {
  900. ctx.s.appendRight(endOffset, `\nreturn ${returned}\n}\n\n`)
  901. }
  902. // 10. finalize default export
  903. const genDefaultAs = options.genDefaultAs
  904. ? `const ${options.genDefaultAs} =`
  905. : `export default`
  906. let runtimeOptions = ``
  907. if (!ctx.hasDefaultExportName && filename && filename !== DEFAULT_FILENAME) {
  908. const match = filename.match(/([^/\\]+)\.\w+$/)
  909. if (match) {
  910. runtimeOptions += `\n __name: '${match[1]}',`
  911. }
  912. }
  913. if (hasInlinedSsrRenderFn) {
  914. runtimeOptions += `\n __ssrInlineRender: true,`
  915. }
  916. const propsDecl = genRuntimeProps(ctx)
  917. if (propsDecl) runtimeOptions += `\n props: ${propsDecl},`
  918. const emitsDecl = genRuntimeEmits(ctx)
  919. if (emitsDecl) runtimeOptions += `\n emits: ${emitsDecl},`
  920. let definedOptions = ''
  921. if (ctx.optionsRuntimeDecl) {
  922. definedOptions = scriptSetup.content
  923. .slice(ctx.optionsRuntimeDecl.start!, ctx.optionsRuntimeDecl.end!)
  924. .trim()
  925. }
  926. // <script setup> components are closed by default. If the user did not
  927. // explicitly call `defineExpose`, call expose() with no args.
  928. const exposeCall =
  929. ctx.hasDefineExposeCall || options.inlineTemplate ? `` : ` __expose();\n`
  930. // wrap setup code with function.
  931. if (ctx.isTS) {
  932. // for TS, make sure the exported type is still valid type with
  933. // correct props information
  934. // we have to use object spread for types to be merged properly
  935. // user's TS setting should compile it down to proper targets
  936. // export default defineComponent({ ...__default__, ... })
  937. const def =
  938. (defaultExport ? `\n ...${normalScriptDefaultVar},` : ``) +
  939. (definedOptions ? `\n ...${definedOptions},` : '')
  940. ctx.s.prependLeft(
  941. startOffset,
  942. `\n${genDefaultAs} /*@__PURE__*/${ctx.helper(
  943. `defineComponent`,
  944. )}({${def}${runtimeOptions}\n ${
  945. hasAwait ? `async ` : ``
  946. }setup(${args}) {\n${exposeCall}`,
  947. )
  948. ctx.s.appendRight(endOffset, `})`)
  949. } else {
  950. if (defaultExport || definedOptions) {
  951. // without TS, can't rely on rest spread, so we use Object.assign
  952. // export default Object.assign(__default__, { ... })
  953. ctx.s.prependLeft(
  954. startOffset,
  955. `\n${genDefaultAs} /*@__PURE__*/Object.assign(${
  956. defaultExport ? `${normalScriptDefaultVar}, ` : ''
  957. }${definedOptions ? `${definedOptions}, ` : ''}{${runtimeOptions}\n ` +
  958. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
  959. )
  960. ctx.s.appendRight(endOffset, `})`)
  961. } else {
  962. ctx.s.prependLeft(
  963. startOffset,
  964. `\n${genDefaultAs} {${runtimeOptions}\n ` +
  965. `${hasAwait ? `async ` : ``}setup(${args}) {\n${exposeCall}`,
  966. )
  967. ctx.s.appendRight(endOffset, `}`)
  968. }
  969. }
  970. // 11. finalize Vue helper imports
  971. if (ctx.helperImports.size > 0) {
  972. const runtimeModuleName =
  973. options.templateOptions?.compilerOptions?.runtimeModuleName
  974. const importSrc = runtimeModuleName
  975. ? JSON.stringify(runtimeModuleName)
  976. : `'vue'`
  977. ctx.s.prepend(
  978. `import { ${[...ctx.helperImports]
  979. .map(h => `${h} as _${h}`)
  980. .join(', ')} } from ${importSrc}\n`,
  981. )
  982. }
  983. const content = ctx.s.toString()
  984. let map =
  985. options.sourceMap !== false
  986. ? (ctx.s.generateMap({
  987. source: filename,
  988. hires: true,
  989. includeContent: true,
  990. }) as unknown as RawSourceMap)
  991. : undefined
  992. // merge source maps of the script setup and template in inline mode
  993. if (templateMap && map) {
  994. const offset = content.indexOf(returned)
  995. const templateLineOffset =
  996. content.slice(0, offset).split(/\r?\n/).length - 1
  997. map = mergeSourceMaps(map, templateMap, templateLineOffset)
  998. }
  999. return {
  1000. ...scriptSetup,
  1001. bindings: ctx.bindingMetadata,
  1002. imports: ctx.userImports,
  1003. content,
  1004. map,
  1005. scriptAst: scriptAst?.body,
  1006. scriptSetupAst: scriptSetupAst?.body,
  1007. deps: ctx.deps ? [...ctx.deps] : undefined,
  1008. }
  1009. }
  1010. function registerBinding(
  1011. bindings: Record<string, BindingTypes>,
  1012. node: Identifier,
  1013. type: BindingTypes,
  1014. ) {
  1015. bindings[node.name] = type
  1016. }
  1017. function walkDeclaration(
  1018. from: 'script' | 'scriptSetup',
  1019. node: Declaration,
  1020. bindings: Record<string, BindingTypes>,
  1021. userImportAliases: Record<string, string>,
  1022. hoistStatic: boolean,
  1023. isPropsDestructureEnabled = false,
  1024. ): boolean {
  1025. let isAllLiteral = false
  1026. if (node.type === 'VariableDeclaration') {
  1027. const isConst = node.kind === 'const'
  1028. isAllLiteral =
  1029. isConst &&
  1030. node.declarations.every(
  1031. decl => decl.id.type === 'Identifier' && isStaticNode(decl.init!),
  1032. )
  1033. // export const foo = ...
  1034. for (const { id, init: _init } of node.declarations) {
  1035. const init = _init && unwrapTSNode(_init)
  1036. const isConstMacroCall =
  1037. isConst &&
  1038. isCallOf(
  1039. init,
  1040. c =>
  1041. c === DEFINE_PROPS ||
  1042. c === DEFINE_EMITS ||
  1043. c === WITH_DEFAULTS ||
  1044. c === DEFINE_SLOTS,
  1045. )
  1046. if (id.type === 'Identifier') {
  1047. let bindingType
  1048. const userReactiveBinding = userImportAliases['reactive']
  1049. if (
  1050. (hoistStatic || from === 'script') &&
  1051. (isAllLiteral || (isConst && isStaticNode(init!)))
  1052. ) {
  1053. bindingType = BindingTypes.LITERAL_CONST
  1054. } else if (isCallOf(init, userReactiveBinding)) {
  1055. // treat reactive() calls as let since it's meant to be mutable
  1056. bindingType = isConst
  1057. ? BindingTypes.SETUP_REACTIVE_CONST
  1058. : BindingTypes.SETUP_LET
  1059. } else if (
  1060. // if a declaration is a const literal, we can mark it so that
  1061. // the generated render fn code doesn't need to unref() it
  1062. isConstMacroCall ||
  1063. (isConst && canNeverBeRef(init!, userReactiveBinding))
  1064. ) {
  1065. bindingType = isCallOf(init, DEFINE_PROPS)
  1066. ? BindingTypes.SETUP_REACTIVE_CONST
  1067. : BindingTypes.SETUP_CONST
  1068. } else if (isConst) {
  1069. if (
  1070. isCallOf(
  1071. init,
  1072. m =>
  1073. m === userImportAliases['ref'] ||
  1074. m === userImportAliases['computed'] ||
  1075. m === userImportAliases['shallowRef'] ||
  1076. m === userImportAliases['customRef'] ||
  1077. m === userImportAliases['toRef'] ||
  1078. m === userImportAliases['useTemplateRef'] ||
  1079. m === DEFINE_MODEL,
  1080. )
  1081. ) {
  1082. bindingType = BindingTypes.SETUP_REF
  1083. } else {
  1084. bindingType = BindingTypes.SETUP_MAYBE_REF
  1085. }
  1086. } else {
  1087. bindingType = BindingTypes.SETUP_LET
  1088. }
  1089. registerBinding(bindings, id, bindingType)
  1090. } else {
  1091. if (isCallOf(init, DEFINE_PROPS) && isPropsDestructureEnabled) {
  1092. continue
  1093. }
  1094. if (id.type === 'ObjectPattern') {
  1095. walkObjectPattern(id, bindings, isConst, isConstMacroCall)
  1096. } else if (id.type === 'ArrayPattern') {
  1097. walkArrayPattern(id, bindings, isConst, isConstMacroCall)
  1098. }
  1099. }
  1100. }
  1101. } else if (node.type === 'TSEnumDeclaration') {
  1102. isAllLiteral = node.members.every(
  1103. member => !member.initializer || isStaticNode(member.initializer),
  1104. )
  1105. bindings[node.id!.name] = isAllLiteral
  1106. ? BindingTypes.LITERAL_CONST
  1107. : BindingTypes.SETUP_CONST
  1108. } else if (
  1109. node.type === 'FunctionDeclaration' ||
  1110. node.type === 'ClassDeclaration'
  1111. ) {
  1112. // export function foo() {} / export class Foo {}
  1113. // export declarations must be named.
  1114. bindings[node.id!.name] = BindingTypes.SETUP_CONST
  1115. }
  1116. return isAllLiteral
  1117. }
  1118. function walkObjectPattern(
  1119. node: ObjectPattern,
  1120. bindings: Record<string, BindingTypes>,
  1121. isConst: boolean,
  1122. isDefineCall = false,
  1123. ) {
  1124. for (const p of node.properties) {
  1125. if (p.type === 'ObjectProperty') {
  1126. if (p.key.type === 'Identifier' && p.key === p.value) {
  1127. // shorthand: const { x } = ...
  1128. const type = isDefineCall
  1129. ? BindingTypes.SETUP_CONST
  1130. : isConst
  1131. ? BindingTypes.SETUP_MAYBE_REF
  1132. : BindingTypes.SETUP_LET
  1133. registerBinding(bindings, p.key, type)
  1134. } else {
  1135. walkPattern(p.value, bindings, isConst, isDefineCall)
  1136. }
  1137. } else {
  1138. // ...rest
  1139. // argument can only be identifier when destructuring
  1140. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1141. registerBinding(bindings, p.argument as Identifier, type)
  1142. }
  1143. }
  1144. }
  1145. function walkArrayPattern(
  1146. node: ArrayPattern,
  1147. bindings: Record<string, BindingTypes>,
  1148. isConst: boolean,
  1149. isDefineCall = false,
  1150. ) {
  1151. for (const e of node.elements) {
  1152. e && walkPattern(e, bindings, isConst, isDefineCall)
  1153. }
  1154. }
  1155. function walkPattern(
  1156. node: Node,
  1157. bindings: Record<string, BindingTypes>,
  1158. isConst: boolean,
  1159. isDefineCall = false,
  1160. ) {
  1161. if (node.type === 'Identifier') {
  1162. const type = isDefineCall
  1163. ? BindingTypes.SETUP_CONST
  1164. : isConst
  1165. ? BindingTypes.SETUP_MAYBE_REF
  1166. : BindingTypes.SETUP_LET
  1167. registerBinding(bindings, node, type)
  1168. } else if (node.type === 'RestElement') {
  1169. // argument can only be identifier when destructuring
  1170. const type = isConst ? BindingTypes.SETUP_CONST : BindingTypes.SETUP_LET
  1171. registerBinding(bindings, node.argument as Identifier, type)
  1172. } else if (node.type === 'ObjectPattern') {
  1173. walkObjectPattern(node, bindings, isConst)
  1174. } else if (node.type === 'ArrayPattern') {
  1175. walkArrayPattern(node, bindings, isConst)
  1176. } else if (node.type === 'AssignmentPattern') {
  1177. if (node.left.type === 'Identifier') {
  1178. const type = isDefineCall
  1179. ? BindingTypes.SETUP_CONST
  1180. : isConst
  1181. ? BindingTypes.SETUP_MAYBE_REF
  1182. : BindingTypes.SETUP_LET
  1183. registerBinding(bindings, node.left, type)
  1184. } else {
  1185. walkPattern(node.left, bindings, isConst)
  1186. }
  1187. }
  1188. }
  1189. function canNeverBeRef(node: Node, userReactiveImport?: string): boolean {
  1190. if (isCallOf(node, userReactiveImport)) {
  1191. return true
  1192. }
  1193. switch (node.type) {
  1194. case 'UnaryExpression':
  1195. case 'BinaryExpression':
  1196. case 'ArrayExpression':
  1197. case 'ObjectExpression':
  1198. case 'FunctionExpression':
  1199. case 'ArrowFunctionExpression':
  1200. case 'UpdateExpression':
  1201. case 'ClassExpression':
  1202. case 'TaggedTemplateExpression':
  1203. return true
  1204. case 'SequenceExpression':
  1205. return canNeverBeRef(
  1206. node.expressions[node.expressions.length - 1],
  1207. userReactiveImport,
  1208. )
  1209. default:
  1210. if (isLiteralNode(node)) {
  1211. return true
  1212. }
  1213. return false
  1214. }
  1215. }
  1216. function isStaticNode(node: Node): boolean {
  1217. node = unwrapTSNode(node)
  1218. switch (node.type) {
  1219. case 'UnaryExpression': // void 0, !true
  1220. return isStaticNode(node.argument)
  1221. case 'LogicalExpression': // 1 > 2
  1222. case 'BinaryExpression': // 1 + 2
  1223. return isStaticNode(node.left) && isStaticNode(node.right)
  1224. case 'ConditionalExpression': {
  1225. // 1 ? 2 : 3
  1226. return (
  1227. isStaticNode(node.test) &&
  1228. isStaticNode(node.consequent) &&
  1229. isStaticNode(node.alternate)
  1230. )
  1231. }
  1232. case 'SequenceExpression': // (1, 2)
  1233. case 'TemplateLiteral': // `foo${1}`
  1234. return node.expressions.every(expr => isStaticNode(expr))
  1235. case 'ParenthesizedExpression': // (1)
  1236. return isStaticNode(node.expression)
  1237. case 'StringLiteral':
  1238. case 'NumericLiteral':
  1239. case 'BooleanLiteral':
  1240. case 'NullLiteral':
  1241. case 'BigIntLiteral':
  1242. return true
  1243. }
  1244. return false
  1245. }
  1246. export function mergeSourceMaps(
  1247. scriptMap: RawSourceMap,
  1248. templateMap: RawSourceMap,
  1249. templateLineOffset: number,
  1250. ): RawSourceMap {
  1251. const generator = new SourceMapGenerator()
  1252. const addMapping = (map: RawSourceMap, lineOffset = 0) => {
  1253. const consumer = new SourceMapConsumer(map)
  1254. ;(consumer as any).sources.forEach((sourceFile: string) => {
  1255. ;(generator as any)._sources.add(sourceFile)
  1256. const sourceContent = consumer.sourceContentFor(sourceFile)
  1257. if (sourceContent != null) {
  1258. generator.setSourceContent(sourceFile, sourceContent)
  1259. }
  1260. })
  1261. consumer.eachMapping(m => {
  1262. if (m.originalLine == null) return
  1263. generator.addMapping({
  1264. generated: {
  1265. line: m.generatedLine + lineOffset,
  1266. column: m.generatedColumn,
  1267. },
  1268. original: {
  1269. line: m.originalLine,
  1270. column: m.originalColumn!,
  1271. },
  1272. source: m.source,
  1273. name: m.name,
  1274. })
  1275. })
  1276. }
  1277. addMapping(scriptMap)
  1278. addMapping(templateMap, templateLineOffset)
  1279. ;(generator as any)._sourceRoot = scriptMap.sourceRoot
  1280. ;(generator as any)._file = scriptMap.file
  1281. return (generator as any).toJSON()
  1282. }