| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618 |
- /* @flow */
- import { genHandlers } from './events'
- import baseDirectives from '../directives/index'
- import { camelize, no, extend } from 'shared/util'
- import { baseWarn, pluckModuleFunction } from '../helpers'
- import { emptySlotScopeToken } from '../parser/index'
- type TransformFunction = (el: ASTElement, code: string) => string;
- type DataGenFunction = (el: ASTElement) => string;
- type DirectiveFunction = (el: ASTElement, dir: ASTDirective, warn: Function) => boolean;
- export class CodegenState {
- options: CompilerOptions;
- warn: Function;
- transforms: Array<TransformFunction>;
- dataGenFns: Array<DataGenFunction>;
- directives: { [key: string]: DirectiveFunction };
- maybeComponent: (el: ASTElement) => boolean;
- onceId: number;
- staticRenderFns: Array<string>;
- pre: boolean;
- constructor (options: CompilerOptions) {
- this.options = options
- this.warn = options.warn || baseWarn
- this.transforms = pluckModuleFunction(options.modules, 'transformCode')
- this.dataGenFns = pluckModuleFunction(options.modules, 'genData')
- this.directives = extend(extend({}, baseDirectives), options.directives)
- const isReservedTag = options.isReservedTag || no
- this.maybeComponent = (el: ASTElement) => !!el.component || !isReservedTag(el.tag)
- this.onceId = 0
- this.staticRenderFns = []
- this.pre = false
- }
- }
- export type CodegenResult = {
- render: string,
- staticRenderFns: Array<string>
- };
- export function generate (
- ast: ASTElement | void,
- options: CompilerOptions
- ): CodegenResult {
- const state = new CodegenState(options)
- const code = ast ? genElement(ast, state) : '_c("div")'
- return {
- render: `with(this){return ${code}}`,
- staticRenderFns: state.staticRenderFns
- }
- }
- export function genElement (el: ASTElement, state: CodegenState): string {
- if (el.parent) {
- el.pre = el.pre || el.parent.pre
- }
- if (el.staticRoot && !el.staticProcessed) {
- return genStatic(el, state)
- } else if (el.once && !el.onceProcessed) {
- return genOnce(el, state)
- } else if (el.for && !el.forProcessed) {
- return genFor(el, state)
- } else if (el.if && !el.ifProcessed) {
- return genIf(el, state)
- } else if (el.tag === 'template' && !el.slotTarget && !state.pre) {
- return genChildren(el, state) || 'void 0'
- } else if (el.tag === 'slot') {
- return genSlot(el, state)
- } else {
- // component or element
- let code
- if (el.component) {
- code = genComponent(el.component, el, state)
- } else {
- let data
- if (!el.plain || (el.pre && state.maybeComponent(el))) {
- data = genData(el, state)
- }
- const children = el.inlineTemplate ? null : genChildren(el, state, true)
- code = `_c('${el.tag}'${
- data ? `,${data}` : '' // data
- }${
- children ? `,${children}` : '' // children
- })`
- }
- // module transforms
- for (let i = 0; i < state.transforms.length; i++) {
- code = state.transforms[i](el, code)
- }
- return code
- }
- }
- // hoist static sub-trees out
- function genStatic (el: ASTElement, state: CodegenState): string {
- el.staticProcessed = true
- // Some elements (templates) need to behave differently inside of a v-pre
- // node. All pre nodes are static roots, so we can use this as a location to
- // wrap a state change and reset it upon exiting the pre node.
- const originalPreState = state.pre
- if (el.pre) {
- state.pre = el.pre
- }
- state.staticRenderFns.push(`with(this){return ${genElement(el, state)}}`)
- state.pre = originalPreState
- return `_m(${
- state.staticRenderFns.length - 1
- }${
- el.staticInFor ? ',true' : ''
- })`
- }
- // v-once
- function genOnce (el: ASTElement, state: CodegenState): string {
- el.onceProcessed = true
- if (el.if && !el.ifProcessed) {
- return genIf(el, state)
- } else if (el.staticInFor) {
- let key = ''
- let parent = el.parent
- while (parent) {
- if (parent.for) {
- key = parent.key
- break
- }
- parent = parent.parent
- }
- if (!key) {
- process.env.NODE_ENV !== 'production' && state.warn(
- `v-once can only be used inside v-for that is keyed. `,
- el.rawAttrsMap['v-once']
- )
- return genElement(el, state)
- }
- return `_o(${genElement(el, state)},${state.onceId++},${key})`
- } else {
- return genStatic(el, state)
- }
- }
- export function genIf (
- el: any,
- state: CodegenState,
- altGen?: Function,
- altEmpty?: string
- ): string {
- el.ifProcessed = true // avoid recursion
- return genIfConditions(el.ifConditions.slice(), state, altGen, altEmpty)
- }
- function genIfConditions (
- conditions: ASTIfConditions,
- state: CodegenState,
- altGen?: Function,
- altEmpty?: string
- ): string {
- if (!conditions.length) {
- return altEmpty || '_e()'
- }
- const condition = conditions.shift()
- if (condition.exp) {
- return `(${condition.exp})?${
- genTernaryExp(condition.block)
- }:${
- genIfConditions(conditions, state, altGen, altEmpty)
- }`
- } else {
- return `${genTernaryExp(condition.block)}`
- }
- // v-if with v-once should generate code like (a)?_m(0):_m(1)
- function genTernaryExp (el) {
- return altGen
- ? altGen(el, state)
- : el.once
- ? genOnce(el, state)
- : genElement(el, state)
- }
- }
- export function genFor (
- el: any,
- state: CodegenState,
- altGen?: Function,
- altHelper?: string
- ): string {
- const exp = el.for
- const alias = el.alias
- const iterator1 = el.iterator1 ? `,${el.iterator1}` : ''
- const iterator2 = el.iterator2 ? `,${el.iterator2}` : ''
- if (process.env.NODE_ENV !== 'production' &&
- state.maybeComponent(el) &&
- el.tag !== 'slot' &&
- el.tag !== 'template' &&
- !el.key
- ) {
- state.warn(
- `<${el.tag} v-for="${alias} in ${exp}">: component lists rendered with ` +
- `v-for should have explicit keys. ` +
- `See https://vuejs.org/guide/list.html#key for more info.`,
- el.rawAttrsMap['v-for'],
- true /* tip */
- )
- }
- el.forProcessed = true // avoid recursion
- return `${altHelper || '_l'}((${exp}),` +
- `function(${alias}${iterator1}${iterator2}){` +
- `return ${(altGen || genElement)(el, state)}` +
- '})'
- }
- export function genData (el: ASTElement, state: CodegenState): string {
- let data = '{'
- // directives first.
- // directives may mutate the el's other properties before they are generated.
- const dirs = genDirectives(el, state)
- if (dirs) data += dirs + ','
- // key
- if (el.key) {
- data += `key:${el.key},`
- }
- // ref
- if (el.ref) {
- data += `ref:${el.ref},`
- }
- if (el.refInFor) {
- data += `refInFor:true,`
- }
- // pre
- if (el.pre) {
- data += `pre:true,`
- }
- // record original tag name for components using "is" attribute
- if (el.component) {
- data += `tag:"${el.tag}",`
- }
- // module data generation functions
- for (let i = 0; i < state.dataGenFns.length; i++) {
- data += state.dataGenFns[i](el)
- }
- // attributes
- if (el.attrs) {
- data += `attrs:${genProps(el.attrs)},`
- }
- // DOM props
- if (el.props) {
- data += `domProps:${genProps(el.props)},`
- }
- // event handlers
- if (el.events) {
- data += `${genHandlers(el.events, false)},`
- }
- if (el.nativeEvents) {
- data += `${genHandlers(el.nativeEvents, true)},`
- }
- // slot target
- // only for non-scoped slots
- if (el.slotTarget && !el.slotScope) {
- data += `slot:${el.slotTarget},`
- }
- // scoped slots
- if (el.scopedSlots) {
- data += `${genScopedSlots(el, el.scopedSlots, state)},`
- }
- // component v-model
- if (el.model) {
- data += `model:{value:${
- el.model.value
- },callback:${
- el.model.callback
- },expression:${
- el.model.expression
- }},`
- }
- // inline-template
- if (el.inlineTemplate) {
- const inlineTemplate = genInlineTemplate(el, state)
- if (inlineTemplate) {
- data += `${inlineTemplate},`
- }
- }
- data = data.replace(/,$/, '') + '}'
- // v-bind dynamic argument wrap
- // v-bind with dynamic arguments must be applied using the same v-bind object
- // merge helper so that class/style/mustUseProp attrs are handled correctly.
- if (el.dynamicAttrs) {
- data = `_b(${data},"${el.tag}",${genProps(el.dynamicAttrs)})`
- }
- // v-bind data wrap
- if (el.wrapData) {
- data = el.wrapData(data)
- }
- // v-on data wrap
- if (el.wrapListeners) {
- data = el.wrapListeners(data)
- }
- return data
- }
- function genDirectives (el: ASTElement, state: CodegenState): string | void {
- const dirs = el.directives
- if (!dirs) return
- let res = 'directives:['
- let hasRuntime = false
- let i, l, dir, needRuntime
- for (i = 0, l = dirs.length; i < l; i++) {
- dir = dirs[i]
- needRuntime = true
- const gen: DirectiveFunction = state.directives[dir.name]
- if (gen) {
- // compile-time directive that manipulates AST.
- // returns true if it also needs a runtime counterpart.
- needRuntime = !!gen(el, dir, state.warn)
- }
- if (needRuntime) {
- hasRuntime = true
- res += `{name:"${dir.name}",rawName:"${dir.rawName}"${
- dir.value ? `,value:(${dir.value}),expression:${JSON.stringify(dir.value)}` : ''
- }${
- dir.arg ? `,arg:${dir.isDynamicArg ? dir.arg : `"${dir.arg}"`}` : ''
- }${
- dir.modifiers ? `,modifiers:${JSON.stringify(dir.modifiers)}` : ''
- }},`
- }
- }
- if (hasRuntime) {
- return res.slice(0, -1) + ']'
- }
- }
- function genInlineTemplate (el: ASTElement, state: CodegenState): ?string {
- const ast = el.children[0]
- if (process.env.NODE_ENV !== 'production' && (
- el.children.length !== 1 || ast.type !== 1
- )) {
- state.warn(
- 'Inline-template components must have exactly one child element.',
- { start: el.start }
- )
- }
- if (ast && ast.type === 1) {
- const inlineRenderFns = generate(ast, state.options)
- return `inlineTemplate:{render:function(){${
- inlineRenderFns.render
- }},staticRenderFns:[${
- inlineRenderFns.staticRenderFns.map(code => `function(){${code}}`).join(',')
- }]}`
- }
- }
- function genScopedSlots (
- el: ASTElement,
- slots: { [key: string]: ASTElement },
- state: CodegenState
- ): string {
- // by default scoped slots are considered "stable", this allows child
- // components with only scoped slots to skip forced updates from parent.
- // but in some cases we have to bail-out of this optimization
- // for example if the slot contains dynamic names, has v-if or v-for on them...
- let needsForceUpdate = el.for || Object.keys(slots).some(key => {
- const slot = slots[key]
- return (
- slot.slotTargetDynamic ||
- slot.if ||
- slot.for ||
- containsSlotChild(slot) // is passing down slot from parent which may be dynamic
- )
- })
- // #9534: if a component with scoped slots is inside a conditional branch,
- // it's possible for the same component to be reused but with different
- // compiled slot content. To avoid that, we generate a unique key based on
- // the generated code of all the slot contents.
- let needsKey = !!el.if
- // OR when it is inside another scoped slot or v-for (the reactivity may be
- // disconnected due to the intermediate scope variable)
- // #9438, #9506
- // TODO: this can be further optimized by properly analyzing in-scope bindings
- // and skip force updating ones that do not actually use scope variables.
- if (!needsForceUpdate) {
- let parent = el.parent
- while (parent) {
- if (
- (parent.slotScope && parent.slotScope !== emptySlotScopeToken) ||
- parent.for
- ) {
- needsForceUpdate = true
- break
- }
- if (parent.if) {
- needsKey = true
- }
- parent = parent.parent
- }
- }
- const generatedSlots = Object.keys(slots)
- .map(key => genScopedSlot(slots[key], state))
- .join(',')
- return `scopedSlots:_u([${generatedSlots}]${
- needsForceUpdate ? `,null,true` : ``
- }${
- !needsForceUpdate && needsKey ? `,null,false,${hash(generatedSlots)}` : ``
- })`
- }
- function hash(str) {
- let hash = 5381
- let i = str.length
- while(i) {
- hash = (hash * 33) ^ str.charCodeAt(--i)
- }
- return hash >>> 0
- }
- function containsSlotChild (el: ASTNode): boolean {
- if (el.type === 1) {
- if (el.tag === 'slot') {
- return true
- }
- return el.children.some(containsSlotChild)
- }
- return false
- }
- function genScopedSlot (
- el: ASTElement,
- state: CodegenState
- ): string {
- const isLegacySyntax = el.attrsMap['slot-scope']
- if (el.if && !el.ifProcessed && !isLegacySyntax) {
- return genIf(el, state, genScopedSlot, `null`)
- }
- if (el.for && !el.forProcessed) {
- return genFor(el, state, genScopedSlot)
- }
- const slotScope = el.slotScope === emptySlotScopeToken
- ? ``
- : String(el.slotScope)
- const fn = `function(${slotScope}){` +
- `return ${el.tag === 'template'
- ? el.if && isLegacySyntax
- ? `(${el.if})?${genChildren(el, state) || 'undefined'}:undefined`
- : genChildren(el, state) || 'undefined'
- : genElement(el, state)
- }}`
- // reverse proxy v-slot without scope on this.$slots
- const reverseProxy = slotScope ? `` : `,proxy:true`
- return `{key:${el.slotTarget || `"default"`},fn:${fn}${reverseProxy}}`
- }
- export function genChildren (
- el: ASTElement,
- state: CodegenState,
- checkSkip?: boolean,
- altGenElement?: Function,
- altGenNode?: Function
- ): string | void {
- const children = el.children
- if (children.length) {
- const el: any = children[0]
- // optimize single v-for
- if (children.length === 1 &&
- el.for &&
- el.tag !== 'template' &&
- el.tag !== 'slot'
- ) {
- const normalizationType = checkSkip
- ? state.maybeComponent(el) ? `,1` : `,0`
- : ``
- return `${(altGenElement || genElement)(el, state)}${normalizationType}`
- }
- const normalizationType = checkSkip
- ? getNormalizationType(children, state.maybeComponent)
- : 0
- const gen = altGenNode || genNode
- return `[${children.map(c => gen(c, state)).join(',')}]${
- normalizationType ? `,${normalizationType}` : ''
- }`
- }
- }
- // determine the normalization needed for the children array.
- // 0: no normalization needed
- // 1: simple normalization needed (possible 1-level deep nested array)
- // 2: full normalization needed
- function getNormalizationType (
- children: Array<ASTNode>,
- maybeComponent: (el: ASTElement) => boolean
- ): number {
- let res = 0
- for (let i = 0; i < children.length; i++) {
- const el: ASTNode = children[i]
- if (el.type !== 1) {
- continue
- }
- if (needsNormalization(el) ||
- (el.ifConditions && el.ifConditions.some(c => needsNormalization(c.block)))) {
- res = 2
- break
- }
- if (maybeComponent(el) ||
- (el.ifConditions && el.ifConditions.some(c => maybeComponent(c.block)))) {
- res = 1
- }
- }
- return res
- }
- function needsNormalization (el: ASTElement): boolean {
- return el.for !== undefined || el.tag === 'template' || el.tag === 'slot'
- }
- function genNode (node: ASTNode, state: CodegenState): string {
- if (node.type === 1) {
- return genElement(node, state)
- } else if (node.type === 3 && node.isComment) {
- return genComment(node)
- } else {
- return genText(node)
- }
- }
- export function genText (text: ASTText | ASTExpression): string {
- return `_v(${text.type === 2
- ? text.expression // no need for () because already wrapped in _s()
- : transformSpecialNewlines(JSON.stringify(text.text))
- })`
- }
- export function genComment (comment: ASTText): string {
- return `_e(${JSON.stringify(comment.text)})`
- }
- function genSlot (el: ASTElement, state: CodegenState): string {
- const slotName = el.slotName || '"default"'
- const children = genChildren(el, state)
- let res = `_t(${slotName}${children ? `,${children}` : ''}`
- const attrs = el.attrs || el.dynamicAttrs
- ? genProps((el.attrs || []).concat(el.dynamicAttrs || []).map(attr => ({
- // slot props are camelized
- name: camelize(attr.name),
- value: attr.value,
- dynamic: attr.dynamic
- })))
- : null
- const bind = el.attrsMap['v-bind']
- if ((attrs || bind) && !children) {
- res += `,null`
- }
- if (attrs) {
- res += `,${attrs}`
- }
- if (bind) {
- res += `${attrs ? '' : ',null'},${bind}`
- }
- return res + ')'
- }
- // componentName is el.component, take it as argument to shun flow's pessimistic refinement
- function genComponent (
- componentName: string,
- el: ASTElement,
- state: CodegenState
- ): string {
- const children = el.inlineTemplate ? null : genChildren(el, state, true)
- return `_c(${componentName},${genData(el, state)}${
- children ? `,${children}` : ''
- })`
- }
- function genProps (props: Array<ASTAttr>): string {
- let staticProps = ``
- let dynamicProps = ``
- for (let i = 0; i < props.length; i++) {
- const prop = props[i]
- const value = __WEEX__
- ? generateValue(prop.value)
- : transformSpecialNewlines(prop.value)
- if (prop.dynamic) {
- dynamicProps += `${prop.name},${value},`
- } else {
- staticProps += `"${prop.name}":${value},`
- }
- }
- staticProps = `{${staticProps.slice(0, -1)}}`
- if (dynamicProps) {
- return `_d(${staticProps},[${dynamicProps.slice(0, -1)}])`
- } else {
- return staticProps
- }
- }
- /* istanbul ignore next */
- function generateValue (value) {
- if (typeof value === 'string') {
- return transformSpecialNewlines(value)
- }
- return JSON.stringify(value)
- }
- // #3895, #4268
- function transformSpecialNewlines (text: string): string {
- return text
- .replace(/\u2028/g, '\\u2028')
- .replace(/\u2029/g, '\\u2029')
- }
|