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.