123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264 |
- /* @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('+')
- }
|