diff options
| author | ruki <waruqi@gmail.com> | 2018-11-08 00:38:48 +0800 |
|---|---|---|
| committer | ruki <waruqi@gmail.com> | 2018-11-07 21:53:09 +0800 |
| commit | 26105034da4fcce7ac883c899d781f016559310d (patch) | |
| tree | c459a5dc4e3aa0972d9919033ece511ce76dd129 /node_modules/vue/src/server/optimizing-compiler | |
| parent | 2c77f00f1a7ecb6c8192f9c16d3b2001b254a107 (diff) | |
| download | xmake-docs-26105034da4fcce7ac883c899d781f016559310d.tar.gz xmake-docs-26105034da4fcce7ac883c899d781f016559310d.zip | |
switch to vuepress
Diffstat (limited to 'node_modules/vue/src/server/optimizing-compiler')
5 files changed, 696 insertions, 0 deletions
diff --git a/node_modules/vue/src/server/optimizing-compiler/codegen.js b/node_modules/vue/src/server/optimizing-compiler/codegen.js new file mode 100644 index 00000000..26097c07 --- /dev/null +++ b/node_modules/vue/src/server/optimizing-compiler/codegen.js @@ -0,0 +1,260 @@ +/* @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) { + segments.push({ type: RAW, value: escape(c.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('+') +} diff --git a/node_modules/vue/src/server/optimizing-compiler/index.js b/node_modules/vue/src/server/optimizing-compiler/index.js new file mode 100644 index 00000000..893630d6 --- /dev/null +++ b/node_modules/vue/src/server/optimizing-compiler/index.js @@ -0,0 +1,20 @@ +/* @flow */ + +import { parse } from 'compiler/parser/index' +import { generate } from './codegen' +import { optimize } from './optimizer' +import { createCompilerCreator } from 'compiler/create-compiler' + +export const createCompiler = createCompilerCreator(function baseCompile ( + template: string, + options: CompilerOptions +): CompiledResult { + const ast = parse(template.trim(), options) + optimize(ast, options) + const code = generate(ast, options) + return { + ast, + render: code.render, + staticRenderFns: code.staticRenderFns + } +}) diff --git a/node_modules/vue/src/server/optimizing-compiler/modules.js b/node_modules/vue/src/server/optimizing-compiler/modules.js new file mode 100644 index 00000000..69a7be87 --- /dev/null +++ b/node_modules/vue/src/server/optimizing-compiler/modules.js @@ -0,0 +1,126 @@ +/* @flow */ + +import { + RAW, + // INTERPOLATION, + EXPRESSION +} from './codegen' + +import { + propsToAttrMap, + isRenderableAttr +} from 'web/server/util' + +import { + isBooleanAttr, + isEnumeratedAttr +} from 'web/util/attrs' + +import type { StringSegment } from './codegen' +import type { CodegenState } from 'compiler/codegen/index' + +type Attr = { name: string; value: string }; + +const plainStringRE = /^"(?:[^"\\]|\\.)*"$|^'(?:[^'\\]|\\.)*'$/ + +// let the model AST transform translate v-model into appropriate +// props bindings +export function applyModelTransform (el: ASTElement, state: CodegenState) { + if (el.directives) { + for (let i = 0; i < el.directives.length; i++) { + const dir = el.directives[i] + if (dir.name === 'model') { + state.directives.model(el, dir, state.warn) + // remove value for textarea as its converted to text + if (el.tag === 'textarea' && el.props) { + el.props = el.props.filter(p => p.name !== 'value') + } + break + } + } + } +} + +export function genAttrSegments ( + attrs: Array<Attr> +): Array<StringSegment> { + return attrs.map(({ name, value }) => genAttrSegment(name, value)) +} + +export function genDOMPropSegments ( + props: Array<Attr>, + attrs: ?Array<Attr> +): Array<StringSegment> { + const segments = [] + props.forEach(({ name, value }) => { + name = propsToAttrMap[name] || name.toLowerCase() + if (isRenderableAttr(name) && + !(attrs && attrs.some(a => a.name === name)) + ) { + segments.push(genAttrSegment(name, value)) + } + }) + return segments +} + +function genAttrSegment (name: string, value: string): StringSegment { + if (plainStringRE.test(value)) { + // force double quote + value = value.replace(/^'|'$/g, '"') + // force enumerated attr to "true" + if (isEnumeratedAttr(name) && value !== `"false"`) { + value = `"true"` + } + return { + type: RAW, + value: isBooleanAttr(name) + ? ` ${name}="${name}"` + : value === '""' + ? ` ${name}` + : ` ${name}="${JSON.parse(value)}"` + } + } else { + return { + type: EXPRESSION, + value: `_ssrAttr(${JSON.stringify(name)},${value})` + } + } +} + +export function genClassSegments ( + staticClass: ?string, + classBinding: ?string +): Array<StringSegment> { + if (staticClass && !classBinding) { + return [{ type: RAW, value: ` class=${staticClass}` }] + } else { + return [{ + type: EXPRESSION, + value: `_ssrClass(${staticClass || 'null'},${classBinding || 'null'})` + }] + } +} + +export function genStyleSegments ( + staticStyle: ?string, + parsedStaticStyle: ?string, + styleBinding: ?string, + vShowExpression: ?string +): Array<StringSegment> { + if (staticStyle && !styleBinding && !vShowExpression) { + return [{ type: RAW, value: ` style=${JSON.stringify(staticStyle)}` }] + } else { + return [{ + type: EXPRESSION, + value: `_ssrStyle(${ + parsedStaticStyle || 'null' + },${ + styleBinding || 'null' + }, ${ + vShowExpression + ? `{ display: (${vShowExpression}) ? '' : 'none' }` + : 'null' + })` + }] + } +} diff --git a/node_modules/vue/src/server/optimizing-compiler/optimizer.js b/node_modules/vue/src/server/optimizing-compiler/optimizer.js new file mode 100644 index 00000000..d24acc55 --- /dev/null +++ b/node_modules/vue/src/server/optimizing-compiler/optimizer.js @@ -0,0 +1,140 @@ +/* @flow */ + +/** + * In SSR, the vdom tree is generated only once and never patched, so + * we can optimize most element / trees into plain string render functions. + * The SSR optimizer walks the AST tree to detect optimizable elements and trees. + * + * The criteria for SSR optimizability is quite a bit looser than static tree + * detection (which is designed for client re-render). In SSR we bail only for + * components/slots/custom directives. + */ + +import { no, makeMap, isBuiltInTag } from 'shared/util' + +// optimizability constants +export const optimizability = { + FALSE: 0, // whole sub tree un-optimizable + FULL: 1, // whole sub tree optimizable + SELF: 2, // self optimizable but has some un-optimizable children + CHILDREN: 3, // self un-optimizable but have fully optimizable children + PARTIAL: 4 // self un-optimizable with some un-optimizable children +} + +let isPlatformReservedTag + +export function optimize (root: ?ASTElement, options: CompilerOptions) { + if (!root) return + isPlatformReservedTag = options.isReservedTag || no + walk(root, true) +} + +function walk (node: ASTNode, isRoot?: boolean) { + if (isUnOptimizableTree(node)) { + node.ssrOptimizability = optimizability.FALSE + return + } + // root node or nodes with custom directives should always be a VNode + const selfUnoptimizable = isRoot || hasCustomDirective(node) + const check = child => { + if (child.ssrOptimizability !== optimizability.FULL) { + node.ssrOptimizability = selfUnoptimizable + ? optimizability.PARTIAL + : optimizability.SELF + } + } + if (selfUnoptimizable) { + node.ssrOptimizability = optimizability.CHILDREN + } + if (node.type === 1) { + for (let i = 0, l = node.children.length; i < l; i++) { + const child = node.children[i] + walk(child) + check(child) + } + if (node.ifConditions) { + for (let i = 1, l = node.ifConditions.length; i < l; i++) { + const block = node.ifConditions[i].block + walk(block, isRoot) + check(block) + } + } + if (node.ssrOptimizability == null || + (!isRoot && (node.attrsMap['v-html'] || node.attrsMap['v-text'])) + ) { + node.ssrOptimizability = optimizability.FULL + } else { + node.children = optimizeSiblings(node) + } + } else { + node.ssrOptimizability = optimizability.FULL + } +} + +function optimizeSiblings (el) { + const children = el.children + const optimizedChildren = [] + + let currentOptimizableGroup = [] + const pushGroup = () => { + if (currentOptimizableGroup.length) { + optimizedChildren.push({ + type: 1, + parent: el, + tag: 'template', + attrsList: [], + attrsMap: {}, + children: currentOptimizableGroup, + ssrOptimizability: optimizability.FULL + }) + } + currentOptimizableGroup = [] + } + + for (let i = 0; i < children.length; i++) { + const c = children[i] + if (c.ssrOptimizability === optimizability.FULL) { + currentOptimizableGroup.push(c) + } else { + // wrap fully-optimizable adjacent siblings inside a template tag + // so that they can be optimized into a single ssrNode by codegen + pushGroup() + optimizedChildren.push(c) + } + } + pushGroup() + return optimizedChildren +} + +function isUnOptimizableTree (node: ASTNode): boolean { + if (node.type === 2 || node.type === 3) { // text or expression + return false + } + return ( + isBuiltInTag(node.tag) || // built-in (slot, component) + !isPlatformReservedTag(node.tag) || // custom component + !!node.component || // "is" component + isSelectWithModel(node) // <select v-model> requires runtime inspection + ) +} + +const isBuiltInDir = makeMap('text,html,show,on,bind,model,pre,cloak,once') + +function hasCustomDirective (node: ASTNode): ?boolean { + return ( + node.type === 1 && + node.directives && + node.directives.some(d => !isBuiltInDir(d.name)) + ) +} + +// <select v-model> cannot be optimized because it requires a runtime check +// to determine proper selected option +function isSelectWithModel (node: ASTNode): boolean { + return ( + node.type === 1 && + node.tag === 'select' && + node.directives != null && + node.directives.some(d => d.name === 'model') + ) +} diff --git a/node_modules/vue/src/server/optimizing-compiler/runtime-helpers.js b/node_modules/vue/src/server/optimizing-compiler/runtime-helpers.js new file mode 100644 index 00000000..6f65de04 --- /dev/null +++ b/node_modules/vue/src/server/optimizing-compiler/runtime-helpers.js @@ -0,0 +1,150 @@ +/* @flow */ + +import { escape, isSSRUnsafeAttr } from 'web/server/util' +import { isObject, extend } from 'shared/util' +import { renderAttr } from 'web/server/modules/attrs' +import { renderClass } from 'web/util/class' +import { genStyle } from 'web/server/modules/style' +import { normalizeStyleBinding } from 'web/util/style' + +import { + normalizeChildren, + simpleNormalizeChildren +} from 'core/vdom/helpers/normalize-children' + +import { + propsToAttrMap, + isRenderableAttr +} from 'web/server/util' + +const ssrHelpers = { + _ssrEscape: escape, + _ssrNode: renderStringNode, + _ssrList: renderStringList, + _ssrAttr: renderAttr, + _ssrAttrs: renderAttrs, + _ssrDOMProps: renderDOMProps, + _ssrClass: renderSSRClass, + _ssrStyle: renderSSRStyle +} + +export function installSSRHelpers (vm: Component) { + if (vm._ssrNode) { + return + } + let Vue = vm.constructor + while (Vue.super) { + Vue = Vue.super + } + extend(Vue.prototype, ssrHelpers) + if (Vue.FunctionalRenderContext) { + extend(Vue.FunctionalRenderContext.prototype, ssrHelpers) + } +} + +class StringNode { + isString: boolean; + open: string; + close: ?string; + children: ?Array<any>; + + constructor ( + open: string, + close?: string, + children?: Array<any>, + normalizationType?: number + ) { + this.isString = true + this.open = open + this.close = close + if (children) { + this.children = normalizationType === 1 + ? simpleNormalizeChildren(children) + : normalizationType === 2 + ? normalizeChildren(children) + : children + } else { + this.children = void 0 + } + } +} + +function renderStringNode ( + open: string, + close?: string, + children?: Array<any>, + normalizationType?: number +): StringNode { + return new StringNode(open, close, children, normalizationType) +} + +function renderStringList ( + val: any, + render: ( + val: any, + keyOrIndex: string | number, + index?: number + ) => string +): string { + let ret = '' + let i, l, keys, key + if (Array.isArray(val) || typeof val === 'string') { + for (i = 0, l = val.length; i < l; i++) { + ret += render(val[i], i) + } + } else if (typeof val === 'number') { + for (i = 0; i < val; i++) { + ret += render(i + 1, i) + } + } else if (isObject(val)) { + keys = Object.keys(val) + for (i = 0, l = keys.length; i < l; i++) { + key = keys[i] + ret += render(val[key], key, i) + } + } + return ret +} + +function renderAttrs (obj: Object): string { + let res = '' + for (const key in obj) { + if (isSSRUnsafeAttr(key)) { + continue + } + res += renderAttr(key, obj[key]) + } + return res +} + +function renderDOMProps (obj: Object): string { + let res = '' + for (const key in obj) { + const attr = propsToAttrMap[key] || key.toLowerCase() + if (isRenderableAttr(attr)) { + res += renderAttr(attr, obj[key]) + } + } + return res +} + +function renderSSRClass ( + staticClass: ?string, + dynamic: any +): string { + const res = renderClass(staticClass, dynamic) + return res === '' ? res : ` class="${escape(res)}"` +} + +function renderSSRStyle ( + staticStyle: ?Object, + dynamic: any, + extra: ?Object +): string { + const style = {} + if (staticStyle) extend(style, staticStyle) + if (dynamic) extend(style, normalizeStyleBinding(dynamic)) + if (extra) extend(style, extra) + const res = genStyle(style) + return res === '' ? res : ` style=${JSON.stringify(escape(res))}` +} |
