vue
Version:
Reactive, component-oriented view layer for modern web interfaces.
265 lines (239 loc) • 7.21 kB
JavaScript
/* @flow */
// The SSR codegen is essentially extending the default codegen to handle
// SSR-optimizable nodes and turn them into string render fns. In cases where
// a node is not optimizable it simply falls back to the default codegen.
import {
genIf,
genFor,
genData,
genText,
genElement,
genChildren,
CodegenState
} from 'compiler/codegen/index'
import {
genAttrSegments,
genDOMPropSegments,
genClassSegments,
genStyleSegments,
applyModelTransform
} from './modules'
import { escape } from 'web/server/util'
import { optimizability } from './optimizer'
import type { CodegenResult } from 'compiler/codegen/index'
export type StringSegment = {
type: number;
value: string;
};
// segment types
export const RAW = 0
export const INTERPOLATION = 1
export const EXPRESSION = 2
export function generate (
ast: ASTElement | void,
options: CompilerOptions
): CodegenResult {
const state = new CodegenState(options)
const code = ast ? genSSRElement(ast, state) : '_c("div")'
return {
render: `with(this){return ${code}}`,
staticRenderFns: state.staticRenderFns
}
}
function genSSRElement (el: ASTElement, state: CodegenState): string {
if (el.for && !el.forProcessed) {
return genFor(el, state, genSSRElement)
} else if (el.if && !el.ifProcessed) {
return genIf(el, state, genSSRElement)
} else if (el.tag === 'template' && !el.slotTarget) {
return el.ssrOptimizability === optimizability.FULL
? genChildrenAsStringNode(el, state)
: genSSRChildren(el, state) || 'void 0'
}
switch (el.ssrOptimizability) {
case optimizability.FULL:
// stringify whole tree
return genStringElement(el, state)
case optimizability.SELF:
// stringify self and check children
return genStringElementWithChildren(el, state)
case optimizability.CHILDREN:
// generate self as VNode and stringify children
return genNormalElement(el, state, true)
case optimizability.PARTIAL:
// generate self as VNode and check children
return genNormalElement(el, state, false)
default:
// bail whole tree
return genElement(el, state)
}
}
function genNormalElement (el, state, stringifyChildren) {
const data = el.plain ? undefined : genData(el, state)
const children = stringifyChildren
? `[${genChildrenAsStringNode(el, state)}]`
: genSSRChildren(el, state, true)
return `_c('${el.tag}'${
data ? `,${data}` : ''
}${
children ? `,${children}` : ''
})`
}
function genSSRChildren (el, state, checkSkip) {
return genChildren(el, state, checkSkip, genSSRElement, genSSRNode)
}
function genSSRNode (el, state) {
return el.type === 1
? genSSRElement(el, state)
: genText(el)
}
function genChildrenAsStringNode (el, state) {
return el.children.length
? `_ssrNode(${flattenSegments(childrenToSegments(el, state))})`
: ''
}
function genStringElement (el, state) {
return `_ssrNode(${elementToString(el, state)})`
}
function genStringElementWithChildren (el, state) {
const children = genSSRChildren(el, state, true)
return `_ssrNode(${
flattenSegments(elementToOpenTagSegments(el, state))
},"</${el.tag}>"${
children ? `,${children}` : ''
})`
}
function elementToString (el, state) {
return `(${flattenSegments(elementToSegments(el, state))})`
}
function elementToSegments (el, state): Array<StringSegment> {
// v-for / v-if
if (el.for && !el.forProcessed) {
el.forProcessed = true
return [{
type: EXPRESSION,
value: genFor(el, state, elementToString, '_ssrList')
}]
} else if (el.if && !el.ifProcessed) {
el.ifProcessed = true
return [{
type: EXPRESSION,
value: genIf(el, state, elementToString, '"<!---->"')
}]
} else if (el.tag === 'template') {
return childrenToSegments(el, state)
}
const openSegments = elementToOpenTagSegments(el, state)
const childrenSegments = childrenToSegments(el, state)
const { isUnaryTag } = state.options
const close = (isUnaryTag && isUnaryTag(el.tag))
? []
: [{ type: RAW, value: `</${el.tag}>` }]
return openSegments.concat(childrenSegments, close)
}
function elementToOpenTagSegments (el, state): Array<StringSegment> {
applyModelTransform(el, state)
let binding
const segments = [{ type: RAW, value: `<${el.tag}` }]
// attrs
if (el.attrs) {
segments.push.apply(segments, genAttrSegments(el.attrs))
}
// domProps
if (el.props) {
segments.push.apply(segments, genDOMPropSegments(el.props, el.attrs))
}
// v-bind="object"
if ((binding = el.attrsMap['v-bind'])) {
segments.push({ type: EXPRESSION, value: `_ssrAttrs(${binding})` })
}
// v-bind.prop="object"
if ((binding = el.attrsMap['v-bind.prop'])) {
segments.push({ type: EXPRESSION, value: `_ssrDOMProps(${binding})` })
}
// class
if (el.staticClass || el.classBinding) {
segments.push.apply(
segments,
genClassSegments(el.staticClass, el.classBinding)
)
}
// style & v-show
if (el.staticStyle || el.styleBinding || el.attrsMap['v-show']) {
segments.push.apply(
segments,
genStyleSegments(
el.attrsMap.style,
el.staticStyle,
el.styleBinding,
el.attrsMap['v-show']
)
)
}
// _scopedId
if (state.options.scopeId) {
segments.push({ type: RAW, value: ` ${state.options.scopeId}` })
}
segments.push({ type: RAW, value: `>` })
return segments
}
function childrenToSegments (el, state): Array<StringSegment> {
let binding
if ((binding = el.attrsMap['v-html'])) {
return [{ type: EXPRESSION, value: `_s(${binding})` }]
}
if ((binding = el.attrsMap['v-text'])) {
return [{ type: INTERPOLATION, value: `_s(${binding})` }]
}
if (el.tag === 'textarea' && (binding = el.attrsMap['v-model'])) {
return [{ type: INTERPOLATION, value: `_s(${binding})` }]
}
return el.children
? nodesToSegments(el.children, state)
: []
}
function nodesToSegments (
children: Array<ASTNode>,
state: CodegenState
): Array<StringSegment> {
const segments = []
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (c.type === 1) {
segments.push.apply(segments, elementToSegments(c, state))
} else if (c.type === 2) {
segments.push({ type: INTERPOLATION, value: c.expression })
} else if (c.type === 3) {
let text = escape(c.text)
if (c.isComment) {
text = '<!--' + text + '-->'
}
segments.push({ type: RAW, value: text })
}
}
return segments
}
function flattenSegments (segments: Array<StringSegment>): string {
const mergedSegments = []
let textBuffer = ''
const pushBuffer = () => {
if (textBuffer) {
mergedSegments.push(JSON.stringify(textBuffer))
textBuffer = ''
}
}
for (let i = 0; i < segments.length; i++) {
const s = segments[i]
if (s.type === RAW) {
textBuffer += s.value
} else if (s.type === INTERPOLATION) {
pushBuffer()
mergedSegments.push(`_ssrEscape(${s.value})`)
} else if (s.type === EXPRESSION) {
pushBuffer()
mergedSegments.push(`(${s.value})`)
}
}
pushBuffer()
return mergedSegments.join('+')
}