diff --git a/flow/compiler.js b/flow/compiler.js index 64dd243e..9bfff578 100644 --- a/flow/compiler.js +++ b/flow/compiler.js @@ -129,8 +129,7 @@ declare type ASTElement = { wrapData?: (code: string) => string; // 2.4 ssr optimization - ssrOptimizable?: boolean; - ssrOptimizableRoot?: boolean; + ssrOptimizability?: number; // weex specific appendAsTree?: boolean; @@ -142,8 +141,7 @@ declare type ASTExpression = { text: string; static?: boolean; // 2.4 ssr optimization - ssrOptimizable?: boolean; - ssrOptimizableRoot?: boolean; + ssrOptimizability?: number; }; declare type ASTText = { @@ -151,8 +149,7 @@ declare type ASTText = { text: string; static?: boolean; // 2.4 ssr optimization - ssrOptimizable?: boolean; - ssrOptimizableRoot?: boolean; + ssrOptimizability?: number; }; // SFC-parser related declarations diff --git a/src/compiler/codegen/index.js b/src/compiler/codegen/index.js index 7511005b..9bb871e9 100644 --- a/src/compiler/codegen/index.js +++ b/src/compiler/codegen/index.js @@ -119,14 +119,15 @@ function genOnce (el: ASTElement, state: CodegenState): string { } } -function genIf (el: any, state: CodegenState): string { +export function genIf (el: any, state: CodegenState, altGen?: Function): string { el.ifProcessed = true // avoid recursion - return genIfConditions(el.ifConditions.slice(), state) + return genIfConditions(el.ifConditions.slice(), state, altGen) } function genIfConditions ( conditions: ASTIfConditions, - state: CodegenState + state: CodegenState, + altGen?: Function ): string { if (!conditions.length) { return '_e()' @@ -145,11 +146,15 @@ function genIfConditions ( // v-if with v-once should generate code like (a)?_m(0):_m(1) function genTernaryExp (el) { - return el.once ? genOnce(el, state) : genElement(el, state) + return altGen + ? altGen(el, state) + : el.once + ? genOnce(el, state) + : genElement(el, state) } } -function genFor (el: any, state: CodegenState): string { +export function genFor (el: any, state: CodegenState, altGen?: Function): string { const exp = el.for const alias = el.alias const iterator1 = el.iterator1 ? `,${el.iterator1}` : '' @@ -172,11 +177,11 @@ function genFor (el: any, state: CodegenState): string { el.forProcessed = true // avoid recursion return `_l((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + - `return ${genElement(el, state)}` + + `return ${(altGen || genElement)(el, state)}` + '})' } -function genData (el: ASTElement, state: CodegenState): string { +export function genData (el: ASTElement, state: CodegenState): string { let data = '{' // directives first. @@ -345,10 +350,12 @@ function genForScopedSlot ( '})' } -function genChildren ( +export function genChildren ( el: ASTElement, state: CodegenState, - checkSkip?: boolean + checkSkip?: boolean, + altGenElement?: Function, + altGenNode?: Function ): string | void { const children = el.children if (children.length) { @@ -359,12 +366,13 @@ function genChildren ( el.tag !== 'template' && el.tag !== 'slot' ) { - return genElement(el, state) + return (altGenElement || genElement)(el, state) } const normalizationType = checkSkip ? getNormalizationType(children, state.maybeComponent) : 0 - return `[${children.map(c => genNode(c, state)).join(',')}]${ + const gen = altGenNode || genNode + return `[${children.map(c => gen(c, state)).join(',')}]${ normalizationType ? `,${normalizationType}` : '' }` } diff --git a/src/server/optimizing-compiler/codegen.js b/src/server/optimizing-compiler/codegen.js index 72144f73..15c64101 100644 --- a/src/server/optimizing-compiler/codegen.js +++ b/src/server/optimizing-compiler/codegen.js @@ -5,7 +5,16 @@ // a node is not optimizable it simply falls back to the default codegen. // import * as directives from './directives' -import { CodegenState, genElement } from 'compiler/codegen/index' +import { FULL, PARTIAL, CHILDREN } from './optimizer' + +import { + genIf, + genFor, + genData, + genElement, + genChildren, + CodegenState +} from 'compiler/codegen/index' type SSRCompileResult = { render: string; @@ -27,7 +36,7 @@ export function generate ( options: CompilerOptions ): SSRCompileResult { const state = new SSRCodegenState(options) - const code = ast ? genSSRElement(ast, state, true) : '_c("div")' + const code = ast ? genSSRElement(ast, state) : '_c("div")' return { render: `with(this){return ${code}}`, staticRenderFns: state.staticRenderFns, @@ -35,18 +44,55 @@ export function generate ( } } -function genSSRElement ( - el: ASTElement, - state: SSRCodegenState, - isComponentRoot?: boolean -): string { - if (el.ssrOptimizableRoot && !isComponentRoot) { - return genStringRenderFn(el, state) - } else { - return genElement(el, state) +function genSSRElement (el: ASTElement, state: SSRCodegenState): string { + if (el.for && !el.forProcessed) { + return genFor(el, state, genSSRElement) + } else if (el.if && !el.ifProcessed) { + return genIf(el, state, genSSRElement) + } + + switch (el.ssrOptimizability) { + case FULL: + // stringify whole tree + return genStringNode(el, state, true) + case PARTIAL: + // stringify self and check children + return genStringNode(el, state, false) + case CHILDREN: + // generate self as VNode and check children + return genVNode(el, state) + default: + // bail whole tree + return genElement(el, state) + } +} + +function genSSRNode (el, state) { + return el.type === 1 + ? genSSRElement(el, state) + : genStringNode(el, state) +} + +function genSSRChildren (el, state, checkSkip) { + return genChildren(el, state, checkSkip, genSSRElement, genSSRNode) +} + +function genVNode (el, state) { + let code + const data = el.plain ? undefined : genData(el, state) + const children = el.inlineTemplate ? null : genSSRChildren(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 } -function genStringRenderFn (el, state) { - return '' +function genStringNode (el, state, includeChildren) { + return '!!!' } diff --git a/src/server/optimizing-compiler/directives.js b/src/server/optimizing-compiler/directives.js index e69de29b..769a089e 100644 --- a/src/server/optimizing-compiler/directives.js +++ b/src/server/optimizing-compiler/directives.js @@ -0,0 +1,7 @@ +/* @flow */ + +export default { + show () { + + } +} diff --git a/src/server/optimizing-compiler/optimizer.js b/src/server/optimizing-compiler/optimizer.js index 878ab98f..3a8f498b 100644 --- a/src/server/optimizing-compiler/optimizer.js +++ b/src/server/optimizing-compiler/optimizer.js @@ -1,6 +1,12 @@ /* @flow */ -import { no, isBuiltInTag } from 'shared/util' +import { no, makeMap, isBuiltInTag } from 'shared/util' + +// optimizability constants +export const FALSE = 0 // whole sub tree un-optimizable +export const FULL = 1 // whole sub tree optimizable +export const PARTIAL = 2 // self optimizable but has un-optimizable children +export const CHILDREN = 3 // self un-optimizable but may have optimizable children let isPlatformReservedTag @@ -16,70 +22,60 @@ let isPlatformReservedTag export function optimize (root: ?ASTElement, options: CompilerOptions) { if (!root) return isPlatformReservedTag = options.isReservedTag || no - // first pass: mark all non-optimizable nodes. - markNonOptimizable(root) - // second pass: mark optimizable trees. - markOptimizableTrees(root, false) + walk(root, true) } -function markNonOptimizable (node: ASTNode) { - node.ssrOptimizable = isOptimizable(node) +function walk (node: ASTNode, isRoot?: boolean) { + if (isUnOptimizableTree(node)) { + node.ssrOptimizability = FALSE + return + } + // root node or nodes with custom directives should always be a VNode + if (isRoot || hasCustomDirective(node)) { + node.ssrOptimizability = CHILDREN + } if (node.type === 1) { - // do not make component slot content optimizable so that render fns can - // still manipulate the nodes. - if ( - !isPlatformReservedTag(node.tag) && - node.tag !== 'slot' && - node.attrsMap['inline-template'] == null - ) { - return - } for (let i = 0, l = node.children.length; i < l; i++) { const child = node.children[i] - markNonOptimizable(child) - if (!child.ssrOptimizable) { - node.ssrOptimizable = false + walk(child) + if (child.ssrOptimizability !== FULL && node.ssrOptimizability == null) { + node.ssrOptimizability = PARTIAL } } if (node.ifConditions) { for (let i = 1, l = node.ifConditions.length; i < l; i++) { const block = node.ifConditions[i].block - markNonOptimizable(block) - if (!block.ssrOptimizable) { - node.ssrOptimizable = false + walk(block) + if (block.ssrOptimizability !== FULL && node.ssrOptimizability == null) { + node.ssrOptimizability = PARTIAL } } } + if (node.ssrOptimizability == null) { + node.ssrOptimizability = FULL + } + } else { + node.ssrOptimizability = FULL } } -function isOptimizable (node: ASTNode): boolean { +function isUnOptimizableTree (node: ASTNode): boolean { if (node.type === 2 || node.type === 3) { // text or expression - return true + return false } return ( - !isBuiltInTag(node.tag) && // not a built-in (slot, component) - !!isPlatformReservedTag(node.tag) // not a component + isBuiltInTag(node.tag) || // built-in (slot, component) + !isPlatformReservedTag(node.tag) // custom component ) } -function markOptimizableTrees (node: ASTNode) { - if (node.type === 1) { - if (node.ssrOptimizable) { - node.ssrOptimizableRoot = true - return - } else { - node.ssrOptimizableRoot = false - } - if (node.children) { - for (let i = 0, l = node.children.length; i < l; i++) { - markOptimizableTrees(node.children[i]) - } - } - if (node.ifConditions) { - for (let i = 1, l = node.ifConditions.length; i < l; i++) { - markOptimizableTrees(node.ifConditions[i].block) - } - } - } +// only need to check built-in dirs with runtime +const isBuiltInDir = makeMap('model,show') + +function hasCustomDirective (node: ASTNode): ?boolean { + return ( + node.type === 1 && + node.directives && + node.directives.some(d => !isBuiltInDir(d.name)) + ) } diff --git a/src/server/render.js b/src/server/render.js index 76335a91..838096cb 100644 --- a/src/server/render.js +++ b/src/server/render.js @@ -38,8 +38,8 @@ const normalizeRender = vm => { } function renderNode (node, isRoot, context) { - if (node.isTextNode) { - renderTextNode(node, context) + if (node.isString) { + renderStringNode(node, context) } else if (isDef(node.componentOptions)) { renderComponent(node, isRoot, context) } else { @@ -145,6 +145,18 @@ function renderComponentWithCache (node, isRoot, key, context) { renderComponentInner(node, isRoot, context) } +function StringNode (open, close, children) { + this.isString = true + this.open = open + this.close = close + this.children = children +} + +function createStringNode (id, children) { + const { open, close } = this.$options.stringRenderFns[id] + return new StringNode(open, close, children) +} + function renderComponentInner (node, isRoot, context) { const prevActive = context.activeInstance // expose userContext on vnode @@ -154,6 +166,7 @@ function renderComponentInner (node, isRoot, context) { context.activeInstance ) normalizeRender(child) + child._ss = createStringNode const childNode = child._render() childNode.parent = node context.renderStates.push({ @@ -163,7 +176,7 @@ function renderComponentInner (node, isRoot, context) { renderNode(childNode, isRoot, context) } -function renderTextNode (el, context) { +function renderStringNode (el, context) { const { write, next } = context if (isUndef(el.children) || el.children.length === 0) { write(el.open() + (el.close || ''), next)