src/compiler/codegen/index.ts TYPESCRIPT 669 lines View on github.com → Search inside
1import { genHandlers } from './events'2import baseDirectives from '../directives/index'3import { camelize, no, extend, capitalize } from 'shared/util'4import { baseWarn, pluckModuleFunction } from '../helpers'5import { emptySlotScopeToken } from '../parser/index'6import {7  ASTAttr,8  ASTDirective,9  ASTElement,10  ASTExpression,11  ASTIfConditions,12  ASTNode,13  ASTText,14  CompilerOptions15} from 'types/compiler'16import { BindingMetadata, BindingTypes } from 'sfc/types'1718type TransformFunction = (el: ASTElement, code: string) => string19type DataGenFunction = (el: ASTElement) => string20type DirectiveFunction = (21  el: ASTElement,22  dir: ASTDirective,23  warn: Function24) => boolean2526export class CodegenState {27  options: CompilerOptions28  warn: Function29  transforms: Array<TransformFunction>30  dataGenFns: Array<DataGenFunction>31  directives: { [key: string]: DirectiveFunction }32  maybeComponent: (el: ASTElement) => boolean33  onceId: number34  staticRenderFns: Array<string>35  pre: boolean3637  constructor(options: CompilerOptions) {38    this.options = options39    this.warn = options.warn || baseWarn40    this.transforms = pluckModuleFunction(options.modules, 'transformCode')41    this.dataGenFns = pluckModuleFunction(options.modules, 'genData')42    this.directives = extend(extend({}, baseDirectives), options.directives)43    const isReservedTag = options.isReservedTag || no44    this.maybeComponent = (el: ASTElement) =>45      !!el.component || !isReservedTag(el.tag)46    this.onceId = 047    this.staticRenderFns = []48    this.pre = false49  }50}5152export type CodegenResult = {53  render: string54  staticRenderFns: Array<string>55}5657export function generate(58  ast: ASTElement | void,59  options: CompilerOptions60): CodegenResult {61  const state = new CodegenState(options)62  // fix #11483, Root level <script> tags should not be rendered.63  const code = ast64    ? ast.tag === 'script'65      ? 'null'66      : genElement(ast, state)67    : '_c("div")'68  return {69    render: `with(this){return ${code}}`,70    staticRenderFns: state.staticRenderFns71  }72}7374export function genElement(el: ASTElement, state: CodegenState): string {75  if (el.parent) {76    el.pre = el.pre || el.parent.pre77  }7879  if (el.staticRoot && !el.staticProcessed) {80    return genStatic(el, state)81  } else if (el.once && !el.onceProcessed) {82    return genOnce(el, state)83  } else if (el.for && !el.forProcessed) {84    return genFor(el, state)85  } else if (el.if && !el.ifProcessed) {86    return genIf(el, state)87  } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {88    return genChildren(el, state) || 'void 0'89  } else if (el.tag === 'slot') {90    return genSlot(el, state)91  } else {92    // component or element93    let code94    if (el.component) {95      code = genComponent(el.component, el, state)96    } else {97      let data98      const maybeComponent = state.maybeComponent(el)99      if (!el.plain || (el.pre && maybeComponent)) {100        data = genData(el, state)101      }102103      let tag: string | undefined104      // check if this is a component in <script setup>105      const bindings = state.options.bindings106      if (maybeComponent && bindings && bindings.__isScriptSetup !== false) {107        tag = checkBindingType(bindings, el.tag)108      }109      if (!tag) tag = `'${el.tag}'`110111      const children = el.inlineTemplate ? null : genChildren(el, state, true)112      code = `_c(${tag}${113        data ? `,${data}` : '' // data114      }${115        children ? `,${children}` : '' // children116      })`117    }118    // module transforms119    for (let i = 0; i < state.transforms.length; i++) {120      code = state.transforms[i](el, code)121    }122    return code123  }124}125126function checkBindingType(bindings: BindingMetadata, key: string) {127  const camelName = camelize(key)128  const PascalName = capitalize(camelName)129  const checkType = (type) => {130    if (bindings[key] === type) {131      return key132    }133    if (bindings[camelName] === type) {134      return camelName135    }136    if (bindings[PascalName] === type) {137      return PascalName138    }139  }140  const fromConst =141    checkType(BindingTypes.SETUP_CONST) ||142    checkType(BindingTypes.SETUP_REACTIVE_CONST)143  if (fromConst) {144    return fromConst145  }146147  const fromMaybeRef =148    checkType(BindingTypes.SETUP_LET) ||149    checkType(BindingTypes.SETUP_REF) ||150    checkType(BindingTypes.SETUP_MAYBE_REF)151  if (fromMaybeRef) {152    return fromMaybeRef153  }154}155156// hoist static sub-trees out157function genStatic(el: ASTElement, state: CodegenState): string {158  el.staticProcessed = true159  // Some elements (templates) need to behave differently inside of a v-pre160  // node.  All pre nodes are static roots, so we can use this as a location to161  // wrap a state change and reset it upon exiting the pre node.162  const originalPreState = state.pre163  if (el.pre) {164    state.pre = el.pre165  }166  state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)167  state.pre = originalPreState168  return `_m(${state.staticRenderFns.length - 1}${169    el.staticInFor ? ',true' : ''170  })`171}172173// v-once174function genOnce(el: ASTElement, state: CodegenState): string {175  el.onceProcessed = true176  if (el.if && !el.ifProcessed) {177    return genIf(el, state)178  } else if (el.staticInFor) {179    let key = ''180    let parent = el.parent181    while (parent) {182      if (parent.for) {183        key = parent.key!184        break185      }186      parent = parent.parent187    }188    if (!key) {189      __DEV__ &&190        state.warn(191          `v-once can only be used inside v-for that is keyed. `,192          el.rawAttrsMap['v-once']193        )194      return genElement(el, state)195    }196    return `_o(${genElement(el, state)},${state.onceId++},${key})`197  } else {198    return genStatic(el, state)199  }200}201202export function genIf(203  el: any,204  state: CodegenState,205  altGen?: Function,206  altEmpty?: string207): string {208  el.ifProcessed = true // avoid recursion209  return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)210}211212function genIfConditions(213  conditions: ASTIfConditions,214  state: CodegenState,215  altGen?: Function,216  altEmpty?: string217): string {218  if (!conditions.length) {219    return altEmpty || '_e()'220  }221222  const condition = conditions.shift()!223  if (condition.exp) {224    return `(${condition.exp})?${genTernaryExp(225      condition.block226    )}:${genIfConditions(conditions, state, altGen, altEmpty)}`227  } else {228    return `${genTernaryExp(condition.block)}`229  }230231  // v-if with v-once should generate code like (a)?_m(0):_m(1)232  function genTernaryExp(el) {233    return altGen234      ? altGen(el, state)235      : el.once236      ? genOnce(el, state)237      : genElement(el, state)238  }239}240241export function genFor(242  el: any,243  state: CodegenState,244  altGen?: Function,245  altHelper?: string246): string {247  const exp = el.for248  const alias = el.alias249  const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''250  const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''251252  if (253    __DEV__ &&254    state.maybeComponent(el) &&255    el.tag !== 'slot' &&256    el.tag !== 'template' &&257    !el.key258  ) {259    state.warn(260      `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +261        `v-for should have explicit keys. ` +262        `See https://v2.vuejs.org/v2/guide/list.html#key for more info.`,263      el.rawAttrsMap['v-for'],264      true /* tip */265    )266  }267268  el.forProcessed = true // avoid recursion269  return (270    `${altHelper || '_l'}((${exp}),` +271    `function(${alias}${iterator1}${iterator2}){` +272    `return ${(altGen || genElement)(el, state)}` +273    '})'274  )275}276277export function genData(el: ASTElement, state: CodegenState): string {278  let data = '{'279280  // directives first.281  // directives may mutate the el's other properties before they are generated.282  const dirs = genDirectives(el, state)283  if (dirs) data += dirs + ','284285  // key286  if (el.key) {287    data += `key:${el.key},`288  }289  // ref290  if (el.ref) {291    data += `ref:${el.ref},`292  }293  if (el.refInFor) {294    data += `refInFor:true,`295  }296  // pre297  if (el.pre) {298    data += `pre:true,`299  }300  // record original tag name for components using "is" attribute301  if (el.component) {302    data += `tag:"${el.tag}",`303  }304  // module data generation functions305  for (let i = 0; i < state.dataGenFns.length; i++) {306    data += state.dataGenFns[i](el)307  }308  // attributes309  if (el.attrs) {310    data += `attrs:${genProps(el.attrs)},`311  }312  // DOM props313  if (el.props) {314    data += `domProps:${genProps(el.props)},`315  }316  // event handlers317  if (el.events) {318    data += `${genHandlers(el.events, false)},`319  }320  if (el.nativeEvents) {321    data += `${genHandlers(el.nativeEvents, true)},`322  }323  // slot target324  // only for non-scoped slots325  if (el.slotTarget && !el.slotScope) {326    data += `slot:${el.slotTarget},`327  }328  // scoped slots329  if (el.scopedSlots) {330    data += `${genScopedSlots(el, el.scopedSlots, state)},`331  }332  // component v-model333  if (el.model) {334    data += `model:{value:${el.model.value},callback:${el.model.callback},expression:${el.model.expression}},`335  }336  // inline-template337  if (el.inlineTemplate) {338    const inlineTemplate = genInlineTemplate(el, state)339    if (inlineTemplate) {340      data += `${inlineTemplate},`341    }342  }343  data = data.replace(/,$/, '') + '}'344  // v-bind dynamic argument wrap345  // v-bind with dynamic arguments must be applied using the same v-bind object346  // merge helper so that class/style/mustUseProp attrs are handled correctly.347  if (el.dynamicAttrs) {348    data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`349  }350  // v-bind data wrap351  if (el.wrapData) {352    data = el.wrapData(data)353  }354  // v-on data wrap355  if (el.wrapListeners) {356    data = el.wrapListeners(data)357  }358  return data359}360361function genDirectives(el: ASTElement, state: CodegenState): string | void {362  const dirs = el.directives363  if (!dirs) return364  let res = 'directives:['365  let hasRuntime = false366  let i, l, dir, needRuntime367  for (i = 0, l = dirs.length; i < l; i++) {368    dir = dirs[i]369    needRuntime = true370    const gen: DirectiveFunction = state.directives[dir.name]371    if (gen) {372      // compile-time directive that manipulates AST.373      // returns true if it also needs a runtime counterpart.374      needRuntime = !!gen(el, dir, state.warn)375    }376    if (needRuntime) {377      hasRuntime = true378      res += `{name:"${dir.name}",rawName:"${dir.rawName}"${379        dir.value380          ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}`381          : ''382      }${dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''}${383        dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''384      }},`385    }386  }387  if (hasRuntime) {388    return res.slice(0, -1) + ']'389  }390}391392function genInlineTemplate(393  el: ASTElement,394  state: CodegenState395): string | undefined {396  const ast = el.children[0]397  if (__DEV__ && (el.children.length !== 1 || ast.type !== 1)) {398    state.warn(399      'Inline-template components must have exactly one child element.',400      { start: el.start }401    )402  }403  if (ast && ast.type === 1) {404    const inlineRenderFns = generate(ast, state.options)405    return `inlineTemplate:{render:function(){${406      inlineRenderFns.render407    }},staticRenderFns:[${inlineRenderFns.staticRenderFns408      .map(code => `function(){${code}}`)409      .join(',')}]}`410  }411}412413function genScopedSlots(414  el: ASTElement,415  slots: { [key: string]: ASTElement },416  state: CodegenState417): string {418  // by default scoped slots are considered "stable", this allows child419  // components with only scoped slots to skip forced updates from parent.420  // but in some cases we have to bail-out of this optimization421  // for example if the slot contains dynamic names, has v-if or v-for on them...422  let needsForceUpdate =423    el.for ||424    Object.keys(slots).some(key => {425      const slot = slots[key]426      return (427        slot.slotTargetDynamic || slot.if || slot.for || containsSlotChild(slot) // is passing down slot from parent which may be dynamic428      )429    })430431  // #9534: if a component with scoped slots is inside a conditional branch,432  // it's possible for the same component to be reused but with different433  // compiled slot content. To avoid that, we generate a unique key based on434  // the generated code of all the slot contents.435  let needsKey = !!el.if436437  // OR when it is inside another scoped slot or v-for (the reactivity may be438  // disconnected due to the intermediate scope variable)439  // #9438, #9506440  // TODO: this can be further optimized by properly analyzing in-scope bindings441  // and skip force updating ones that do not actually use scope variables.442  if (!needsForceUpdate) {443    let parent = el.parent444    while (parent) {445      if (446        (parent.slotScope && parent.slotScope !== emptySlotScopeToken) ||447        parent.for448      ) {449        needsForceUpdate = true450        break451      }452      if (parent.if) {453        needsKey = true454      }455      parent = parent.parent456    }457  }458459  const generatedSlots = Object.keys(slots)460    .map(key => genScopedSlot(slots[key], state))461    .join(',')462463  return `scopedSlots:_u([${generatedSlots}]${464    needsForceUpdate ? `,null,true` : ``465  }${466    !needsForceUpdate && needsKey ? `,null,false,${hash(generatedSlots)}` : ``467  })`468}469470function hash(str) {471  let hash = 5381472  let i = str.length473  while (i) {474    hash = (hash * 33) ^ str.charCodeAt(--i)475  }476  return hash >>> 0477}478479function containsSlotChild(el: ASTNode): boolean {480  if (el.type === 1) {481    if (el.tag === 'slot') {482      return true483    }484    return el.children.some(containsSlotChild)485  }486  return false487}488489function genScopedSlot(el: ASTElement, state: CodegenState): string {490  const isLegacySyntax = el.attrsMap['slot-scope']491  if (el.if && !el.ifProcessed && !isLegacySyntax) {492    return genIf(el, state, genScopedSlot, `null`)493  }494  if (el.for && !el.forProcessed) {495    return genFor(el, state, genScopedSlot)496  }497  const slotScope =498    el.slotScope === emptySlotScopeToken ? `` : String(el.slotScope)499  const fn =500    `function(${slotScope}){` +501    `return ${502      el.tag === 'template'503        ? el.if && isLegacySyntax504          ? `(${el.if})?${genChildren(el, state) || 'undefined'}:undefined`505          : genChildren(el, state) || 'undefined'506        : genElement(el, state)507    }}`508  // reverse proxy v-slot without scope on this.$slots509  const reverseProxy = slotScope ? `` : `,proxy:true`510  return `{key:${el.slotTarget || `"default"`},fn:${fn}${reverseProxy}}`511}512513export function genChildren(514  el: ASTElement,515  state: CodegenState,516  checkSkip?: boolean,517  altGenElement?: Function,518  altGenNode?: Function519): string | void {520  const children = el.children521  if (children.length) {522    const el: any = children[0]523    // optimize single v-for524    if (525      children.length === 1 &&526      el.for &&527      el.tag !== 'template' &&528      el.tag !== 'slot'529    ) {530      const normalizationType = checkSkip531        ? state.maybeComponent(el)532          ? `,1`533          : `,0`534        : ``535      return `${(altGenElement || genElement)(el, state)}${normalizationType}`536    }537    const normalizationType = checkSkip538      ? getNormalizationType(children, state.maybeComponent)539      : 0540    const gen = altGenNode || genNode541    return `[${children.map(c => gen(c, state)).join(',')}]${542      normalizationType ? `,${normalizationType}` : ''543    }`544  }545}546547// determine the normalization needed for the children array.548// 0: no normalization needed549// 1: simple normalization needed (possible 1-level deep nested array)550// 2: full normalization needed551function getNormalizationType(552  children: Array<ASTNode>,553  maybeComponent: (el: ASTElement) => boolean554): number {555  let res = 0556  for (let i = 0; i < children.length; i++) {557    const el: ASTNode = children[i]558    if (el.type !== 1) {559      continue560    }561    if (562      needsNormalization(el) ||563      (el.ifConditions &&564        el.ifConditions.some(c => needsNormalization(c.block)))565    ) {566      res = 2567      break568    }569    if (570      maybeComponent(el) ||571      (el.ifConditions && el.ifConditions.some(c => maybeComponent(c.block)))572    ) {573      res = 1574    }575  }576  return res577}578579function needsNormalization(el: ASTElement): boolean {580  return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'581}582583function genNode(node: ASTNode, state: CodegenState): string {584  if (node.type === 1) {585    return genElement(node, state)586  } else if (node.type === 3 && node.isComment) {587    return genComment(node)588  } else {589    return genText(node)590  }591}592593export function genText(text: ASTText | ASTExpression): string {594  return `_v(${595    text.type === 2596      ? text.expression // no need for () because already wrapped in _s()597      : transformSpecialNewlines(JSON.stringify(text.text))598  })`599}600601export function genComment(comment: ASTText): string {602  return `_e(${JSON.stringify(comment.text)})`603}604605function genSlot(el: ASTElement, state: CodegenState): string {606  const slotName = el.slotName || '"default"'607  const children = genChildren(el, state)608  let res = `_t(${slotName}${children ? `,function(){return ${children}}` : ''}`609  const attrs =610    el.attrs || el.dynamicAttrs611      ? genProps(612          (el.attrs || []).concat(el.dynamicAttrs || []).map(attr => ({613            // slot props are camelized614            name: camelize(attr.name),615            value: attr.value,616            dynamic: attr.dynamic617          }))618        )619      : null620  const bind = el.attrsMap['v-bind']621  if ((attrs || bind) && !children) {622    res += `,null`623  }624  if (attrs) {625    res += `,${attrs}`626  }627  if (bind) {628    res += `${attrs ? '' : ',null'},${bind}`629  }630  return res + ')'631}632633// componentName is el.component, take it as argument to shun flow's pessimistic refinement634function genComponent(635  componentName: string,636  el: ASTElement,637  state: CodegenState638): string {639  const children = el.inlineTemplate ? null : genChildren(el, state, true)640  return `_c(${componentName},${genData(el, state)}${641    children ? `,${children}` : ''642  })`643}644645function genProps(props: Array<ASTAttr>): string {646  let staticProps = ``647  let dynamicProps = ``648  for (let i = 0; i < props.length; i++) {649    const prop = props[i]650    const value = transformSpecialNewlines(prop.value)651    if (prop.dynamic) {652      dynamicProps += `${prop.name},${value},`653    } else {654      staticProps += `"${prop.name}":${value},`655    }656  }657  staticProps = `{${staticProps.slice(0, -1)}}`658  if (dynamicProps) {659    return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`660  } else {661    return staticProps662  }663}664665// #3895, #4268666function transformSpecialNewlines(text: string): string {667  return text.replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029')668}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.